# Copyright (c) 2016, Yahoo Inc.
# Copyrights licensed under the BSD License
# See the accompanying LICENSE.txt file for terms.
"""
Functions to enable packaging plugin functionality
"""
import logging
import os
import shutil
import subprocess
import sys
from jinja2 import Template
from .config import get_configuration_dict, get_configuration, generate_parsed_config_file
from .contextmanager import InTemporaryDirectory, working_dir
from .utility import find_executable, update_recursive, csv_list
logger = logging.getLogger(__name__) # pylint: disable=C0103
[docs]class InvirtualenvPlugin(object):
package_formats = []
config_default = ""
config_types = {}
default_config_filename = 'invirtualenv.configuration'
package_template = ''
hash = None # PIP hash algorithm to use, can be sha256, sha384, sha512 or None (no hashing)
def __init__(self, config_file='deploy.conf'):
self.config_file = config_file
self.config = get_configuration_dict(configuration=config_file)
self.loaded_configuration = get_configuration(configuration=config_file)
# Methods that need to be written for each plugin type
[docs] def run_package_command(self, package_hashes, wheel_dir='wheels'):
"""
Run the command to generate the package based on the hash
"""
pass
[docs] def system_requirements_ok(self):
"""
Check if all the system requirements for this plugin are met.
Returns
-------
bool
True if requirements are met, False otherwise
"""
return True
@property
def pip_cmd(self):
"""
Get the pip command used to create wheels of packages.
The intention is to use the same version of pip to build the wheels
as would be used to deploy them.
The full path to the python interpreter is used to avoid shebang
line length issues.
Returns
-------
list
Command that can be used to invoke pip
"""
basepython = self.config['global'].get('basepython', 'python3')
python_executable = find_executable(basepython)
if not python_executable:
python_executable = sys.executable
bin_dir = os.path.dirname(python_executable)
pip_command = os.path.join(bin_dir, 'pip')
return [python_executable, pip_command]
[docs] def create_package(self, package_type):
"""
Generate a package of the specified type
Parameters
----------
str: package_type
The type of package to generate
"""
if package_type not in self.supported_formats():
return None
original_directory = os.getcwd()
with InTemporaryDirectory():
tempdir = os.getcwd()
wheel_dir = 'wheels'
os.makedirs(wheel_dir)
hashes = self.generate_wheel_packages(wheel_dir)
self.generate_wheel_archive()
deps = []
for package_name, package_hash in hashes.items():
deps.append('{package_name} --hash={package_hash}'.format(package_name=package_name, package_hash=package_hash))
self.config['pip']['deps'] = deps
if self.hash:
self.loaded_configuration['pip']['deps'] = '\n'.join(deps)
with open('deploy.conf.unparsed', 'w') as deploy_conf_handle:
self.loaded_configuration.write(deploy_conf_handle)
generate_parsed_config_file('deploy.conf.unparsed', 'deploy.conf')
package = self.run_package_command(hashes, wheel_dir=wheel_dir) # pylint: disable=E1128,E1111
if package:
source = package
dest = os.path.join(original_directory, os.path.basename(package))
shutil.copyfile(source, dest)
return dest
[docs] def generate_wheel_archive(self, filename=None):
if not filename:
filename = 'wheels.tar.gz'
subprocess.check_call(['tar', '-czf', filename, 'wheels'])
[docs] def generate_wheel_packages(self, wheeldir):
"""
Generate wheel packages for all dependencies
Parameters
----------
wheeldir: str
The directory path to store the generated wheel packages
Returns
-------
dict of filename, pip requirements line
"""
if not self.config['pip'].get('deps'):
return {}
hashes = {}
with working_dir(wheeldir):
# deps = self.config['pip'].get('deps', []) + ['invirtualenv']
deps = self.config['pip'].get('deps', []) + ['invirtualenv']
cmd = self.pip_cmd + ['-q', 'wheel', '-w', '.'] + deps
logger.debug('Running pip command %r to generate wheel packages', cmd)
subprocess.check_call(cmd)
for filename in os.listdir('.'):
if filename.endswith('.whl'):
cmd = self.pip_cmd + ['hash']
if self.hash:
cmd += ['-a', self.hash]
cmd += [filename]
logger.debug('Running pip command %r to generate package hash for %r', cmd, filename)
hashes[filename] = '='.join(subprocess.check_output(cmd).decode().split(os.linesep)[1].split('=')[1:])
logger.debug('Got requirements line %r', hashes[filename])
return hashes
[docs] def render_template_with_config(self, template_str=None):
if not template_str:
template_str = self.package_template
template = Template(template_str)
return template.render(self.config)