def __init__(self, name, image, log=None): super(_Distro, self).__init__() self._image = image self._logger = log or logger.Logger(__name__) self._local_runner = LocalCommandRunner(log=self._logger) self._container_name = name self._data_container_name = '{}-data'.format(self._container_name)
def __init__(self, package_type, repo_path): super(Runner, self).__init__() self._click_runner = CliRunner() self._local_runner = LocalCommandRunner() self._package_type = package_type self._packager = Packager.create(path=repo_path, target_dir=tempfile.mkdtemp()) self.log = logger.get_logger( '{0}.tests.shell.commands:Runner'.format(PROGRAM_NAME))
def __init__(self, repo_path, log=None): self.repo_path = repo_path self.version = test_utils.patch_setup_py(self.repo_path) self._logger = log or logger.Logger(__name__) self._click_runner = CliRunner() self._local_runner = LocalCommandRunner() self._packager = Packager.create(path=repo_path, target_dir=repo_path)
def __init__(self, package_type, repo_path): super(Runner, self).__init__() self._click_runner = CliRunner() self._local_runner = LocalCommandRunner() self._package_type = package_type self._packager = Packager.create(path=repo_path, target_dir=tempfile.mkdtemp()) self.log = log.Logger('{0}.tests.shell.commands:Runner'.format(PROGRAM_NAME))
def __init__(self, username, password, repository_url=None, test=False): if not username: raise exceptions.InvalidArgumentsException( 'username cannot be None') if not password: raise exceptions.InvalidArgumentsException( 'password cannot be None') if repository_url and test: raise exceptions.InvalidArgumentsException( 'either repository_url or test is allowed') self.test = test self.repository_url = 'https://test.pypi.org/legacy/' if self.test else repository_url self.username = username self.password = password self._runner = LocalCommandRunner() self._site = 'test.pypi.org' if self.test else 'pypi.org' self._logger = logger.Logger(__name__) self._log_ctx = { 'test': self.test, 'repository_url': self.repository_url, 'site': self._site }
def __init__(self, repo=None, sha=None, path=None, target_dir=None, python=None): if sha and not repo: raise exceptions.InvalidArgumentsException( 'Must pass repo as well when passing sha') if sha and path: raise exceptions.InvalidArgumentsException( "Either 'sha' or 'path' is allowed") if repo and path: raise exceptions.InvalidArgumentsException( "Either 'repo' or 'path' is allowed") if not sha and not path: raise exceptions.InvalidArgumentsException( "Either 'sha' or 'path' is required") if target_dir: utils.validate_directory_exists(target_dir) if path: utils.validate_directory_exists(path) self._repo_location = path if path else '{}@{}'.format(repo, sha) self._python = python self._target_dir = target_dir or os.getcwd() self._logger = logger.Logger(__name__) self._runner = LocalCommandRunner(log=self._logger) self._log_ctx = { 'repo': self._repo_location, } self._repo_dir = self._create_repo(path, sha, repo)
class _Distro(object): def __init__(self, name, image, log=None): super(_Distro, self).__init__() self._image = image self._logger = log or logger.Logger(__name__) self._local_runner = LocalCommandRunner(log=self._logger) self._container_name = name self._data_container_name = '{}-data'.format(self._container_name) @abc.abstractproperty def python_version(self): pass def boot(self): self._logger.info('Booting up {}...'.format(self._image)) self._create_data_container() def add(self, resource_path): remote_path = '/data/{}'.format(os.path.basename(resource_path)) self._logger.info('Copying {} to distro {}:/{}...'.format( resource_path, self._image, remote_path)) self._local_runner.run('docker cp {} {}:{}'.format( resource_path, self._data_container_name, remote_path)) self._logger.info('Finished copying {} to distro {}:/{}...'.format( resource_path, self._image, remote_path)) return remote_path def run(self, command, exit_on_failure=True): docker_command = 'docker run --volumes-from {} {} /bin/bash -c "{}"'.format( self._data_container_name, self._image, command) self._logger.info("Running command '{}' on {}...".format( command, self._image)) run = self._local_runner.run(docker_command, exit_on_failure=exit_on_failure) self._logger.info("Finished running command '{}' on {}...".format( command, self._image)) return run def shutdown(self): self._logger.info('Shutting down {}...'.format(self._image)) self._local_runner.run('docker rm -vf {}'.format( self._data_container_name), exit_on_failure=False) self._local_runner.run('docker rm -vf {}'.format(self._container_name), exit_on_failure=False) def binary(self, local_repo_path): assert self.python_version is not None container_repo_path = self.add(local_repo_path) pack_command = '{pip_install} {repo_path} && {pyci} pack ' \ '--path {repo_path} ' \ '--target-dir {repo_path} ' \ 'binary ' \ '--entrypoint {spec}' \ .format(pip_install=self._pip_install(), repo_path=container_repo_path, pyci=self._pyci(), spec=conftest.SPEC_FILE) self.run(pack_command) expected_binary_name = 'py-ci-x86_64-Linux' container_binary_path = os.path.join(container_repo_path, expected_binary_name) self._local_runner.run('docker cp {}:{} {}'.format( self._data_container_name, container_binary_path, local_repo_path)) return os.path.join(local_repo_path, expected_binary_name) def _pyci(self): return 'pyci --debug' if self._logger.isEnabledFor( logging.DEBUG) else 'pyci' def _pip_install(self): return 'pip install -e' if self._logger.isEnabledFor( logging.DEBUG) else 'pip install' def _create_data_container(self): self._local_runner.run('docker create -v /data --name {} {}'.format( self._data_container_name, self._image))
class PyCI(object): def __init__(self, repo_path, log=None): self.repo_path = repo_path self.version = test_utils.patch_setup_py(self.repo_path) self._logger = log or logger.Logger(__name__) self._click_runner = CliRunner() self._local_runner = LocalCommandRunner() self._packager = Packager.create(path=repo_path, target_dir=repo_path) def run(self, command, binary=False, catch_exceptions=False): if self._logger.isEnabledFor(logging.DEBUG): command = '--debug {}'.format(command) if binary: response = self._run_binary(command=command) else: try: os.environ[CLICK_ISOLATION] = 'True' response = self._run_source(command=command) finally: del os.environ[CLICK_ISOLATION] if response.return_code != 0 and not catch_exceptions: raise exceptions.CommandExecutionException( command=command, error=response.std_err, output=response.std_out, code=response.return_code) return response def _run_source(self, command): args = split(command) self._logger.info('Invoking command: {} [cwd={}]'.format( command, os.getcwd())) result = self._click_runner.invoke(app, args, catch_exceptions=True) exception = result.exception return CommandExecutionResponse( command=command, std_out=result.output, std_err=str(exception) if exception else None, return_code=result.exit_code) def _run_binary(self, command): command = '{} {}'.format(self.binary_path, command) self._logger.info('Invoking command: {}. [cwd={}]'.format( command, os.getcwd())) return self._local_runner.run( command, exit_on_failure=False, execution_env={'PYCI_INTERACTIVE': 'False'}) @cachedproperty def binary_path(self): # pylint: disable=cyclic-import from pyci.tests import conftest package_path = os.environ.get('PYCI_BINARY_PATH', None) if not package_path: self._logger.info('Creating binary package... [cwd={}]'.format( os.getcwd())) package_path = self._packager.binary(entrypoint=conftest.SPEC_FILE) self._logger.info('Created binary package: {} [cwd={}]'.format( package_path, os.getcwd())) return package_path @cachedproperty def wheel_path(self): self._logger.info('Creating wheel package... [cwd={}]'.format( os.getcwd())) package_path = self._packager.wheel() self._logger.info('Created wheel package: {} [cwd={}]'.format( package_path, os.getcwd())) return package_path
class Runner(object): def __init__(self, package_type, repo_path): super(Runner, self).__init__() self._click_runner = CliRunner() self._local_runner = LocalCommandRunner() self._package_type = package_type self._packager = Packager.create(path=repo_path, target_dir=tempfile.mkdtemp()) self.log = log.Logger('{0}.tests.shell.commands:Runner'.format(PROGRAM_NAME)) def run(self, command, catch_exceptions=False, escape=False): if DEBUG: command = '--debug {}'.format(command) if self._package_type == 'binary': response = self._run_binary(command=command, escape=escape) else: response = self._run_source(command=command, escape=escape) click.echo(response.std_out) if response.return_code != 0 and not catch_exceptions: raise click.ClickException(response.std_err) return response def _run_source(self, command, escape): possix = platform.system().lower() != 'windows' if possix and escape: command = command.replace('"', '\\"') args = shlex.split(command, posix=possix) self.log.info('Invoking command: {} [cwd={}]'.format(command, os.getcwd())) result = self._click_runner.invoke(app, args, catch_exceptions=True) exception = result.exception return CommandExecutionResponse(command=command, std_out=result.output, std_err=str(exception) if exception else None, return_code=result.exit_code) def _run_binary(self, command, escape): possix = platform.system().lower() != 'windows' if possix and escape: command = command.replace('"', '\\"') command = '{} {}'.format(self._binary_path, command) args = shlex.split(command, posix=possix) self.log.info('Invoking command: {}. [cwd={}]'.format(command, os.getcwd())) return self._local_runner.run(args, exit_on_failure=False, pipe=True) @cachedproperty def _binary_path(self): self.log.info('Creating binary package... [cwd={}]'.format(os.getcwd())) package_path = self._packager.binary() self.log.info('Created binary package: {} [cwd={}]'.format(package_path, os.getcwd())) return package_path
class Runner(object): def __init__(self, package_type, repo_path): super(Runner, self).__init__() self._click_runner = CliRunner() self._local_runner = LocalCommandRunner() self._package_type = package_type self._packager = Packager.create(path=repo_path, target_dir=tempfile.mkdtemp()) self.log = logger.get_logger( '{0}.tests.shell.commands:Runner'.format(PROGRAM_NAME)) def run(self, command, catch_exceptions=False, escape=False): if DEBUG: command = '--debug {}'.format(command) if self._package_type == 'binary': response = self._run_binary(command=command, escape=escape) else: response = self._run_source(command=command, escape=escape) click.echo(response.std_out) if response.return_code != 0 and not catch_exceptions: raise click.ClickException(response.std_err) return response def _run_source(self, command, escape): possix = platform.system().lower() != 'windows' if possix and escape: command = command.replace('"', '\\"') args = shlex.split(command, posix=possix) self.log.info('Invoking command: {} [cwd={}]'.format( command, os.getcwd())) result = self._click_runner.invoke(app, args, catch_exceptions=True) exception = result.exception return CommandExecutionResponse( command=command, std_out=result.output, std_err=str(exception) if exception else None, return_code=result.exit_code) def _run_binary(self, command, escape): possix = platform.system().lower() != 'windows' if possix and escape: command = command.replace('"', '\\"') command = '{} {}'.format(self._binary_path, command) args = shlex.split(command, posix=possix) self.log.info('Invoking command: {}. [cwd={}]'.format( command, os.getcwd())) return self._local_runner.run(args, exit_on_failure=False, pipe=True) @cachedproperty def _binary_path(self): self.log.info('Creating binary package... [cwd={}]'.format( os.getcwd())) package_path = self._packager.binary() self.log.info('Created binary package: {} [cwd={}]'.format( package_path, os.getcwd())) return package_path
class Packager(object): """ Provides packaging capabilities. A packager instance is associated with a specific version of your repository, and is capable of packing various formats of it. If you specify a sha (and repo), the packager will download your repository from that sha and operate on it. If you specify a local path, it will operate directly on that path, in which case, the sha and repo arguments are irrelevant. Args: repo (:str, optional): The repository full name. sha (:str, optional): The of the repository. path (:str, optional): The path to your local working copy of the repository. target_dir (:str, optional): Target directory where packages will be created. python (:str, optional): The python interpreter to use for packaging. If not specified, the first 'python' program found in your PATH will be used. """ def __init__(self, repo=None, sha=None, path=None, target_dir=None, python=None): if sha and not repo: raise exceptions.InvalidArgumentsException( 'Must pass repo as well when passing sha') if sha and path: raise exceptions.InvalidArgumentsException( "Either 'sha' or 'path' is allowed") if repo and path: raise exceptions.InvalidArgumentsException( "Either 'repo' or 'path' is allowed") if not sha and not path: raise exceptions.InvalidArgumentsException( "Either 'sha' or 'path' is required") if target_dir: utils.validate_directory_exists(target_dir) if path: utils.validate_directory_exists(path) self._repo_location = path if path else '{}@{}'.format(repo, sha) self._python = python self._target_dir = target_dir or os.getcwd() self._logger = logger.Logger(__name__) self._runner = LocalCommandRunner(log=self._logger) self._log_ctx = { 'repo': self._repo_location, } self._repo_dir = self._create_repo(path, sha, repo) def _create_repo(self, path, sha, repo): if path: repo_dir = path else: self._debug('Downloading {}...'.format(self._repo_location)) repo_dir = utils.download_repo(repo, sha) return repo_dir @staticmethod def create(repo=None, sha=None, path=None, target_dir=None, python=None): return Packager(repo=repo, sha=sha, path=path, target_dir=target_dir, python=python) def binary(self, base_name=None, entrypoint=None, pyinstaller_version=None): """ Create a binary executable. This method will create a self-contained, platform dependent, executable file. Under the hood, this uses the PyInstaller project. For more information please visit https://www.pyinstaller.org/ Args: base_name (:str, optional): The base name of the target file. The final name will be in the form of: <name>-<platform-machine>-<platform-system> (e.g pyci-x86_64-Darwin). Defaults to the 'name' specified in your setup.py file. entrypoint (:str, optional): Path to a script file from which the executable is built. This can either by a .py or a .spec file. By default, the packager will look for the entry point specified in setup.py. pyinstaller_version (:str, optional): Which PyInstaller version to use. Defaults to 3.4. Raises: BinaryExistsException: Destination file already exists. DirectoryDoesntExistException: Destination directory does not exist. DefaultEntrypointNotFoundException: A custom entrypoint is not provided and the default entry-points pyci looks for are also missing. EntrypointNotFoundException: The provided custom entrypoint provided does not exist in the repository. MultipleDefaultEntrypointsFoundException: If setup.py contains multiple entrypoints specification. """ temp_dir = tempfile.mkdtemp() try: base_name = base_name or self._name entrypoint = entrypoint or self._entrypoint destination = os.path.join( self._target_dir, '{0}-{1}-{2}'.format(base_name, platform.machine(), platform.system())) if platform.system().lower() == 'windows': destination = '{0}.exe'.format(destination) try: utils.validate_file_does_not_exist(path=destination) except exceptions.FileExistException as e: raise exceptions.BinaryExistsException(path=e.path) dist_dir = os.path.join(temp_dir, 'dist') build_dir = os.path.join(temp_dir, 'build') script = os.path.join(self._repo_dir, entrypoint) if not os.path.exists(script): raise exceptions.EntrypointNotFoundException( repo=self._repo_location, entrypoint=entrypoint) with self._create_virtualenv(base_name) as virtualenv: self._logger.debug('Installing pyinstaller...') pip_path = utils.get_python_executable('pip', exec_home=virtualenv) self._runner.run('{} pyinstaller=={}'.format( self._pip_install(pip_path), pyinstaller_version or DEFAULT_PY_INSTALLER_VERSION), cwd=self._repo_dir) self._debug('Running pyinstaller...', entrypoint=entrypoint, destination=destination) pyinstaller_path = utils.get_python_executable( 'pyinstaller', exec_home=virtualenv) self._runner.run('{} ' '--onefile ' '--distpath {} ' '--workpath {} ' '--specpath {} {}'.format( self._pyinstaller(pyinstaller_path), dist_dir, build_dir, temp_dir, script)) self._debug('Finished running pyinstaller', entrypoint=entrypoint, destination=destination) actual_name = utils.lsf(dist_dir)[0] package_path = os.path.join(dist_dir, actual_name) self._debug('Copying package to destination...', src=package_path, dst=destination) shutil.copy(package_path, destination) self._debug('Packaged successfully.', package=destination) return os.path.abspath(destination) finally: utils.rmf(temp_dir) def wheel(self, universal=False, wheel_version=None): """ Create a wheel package. This method will create a wheel package, according the the regular python wheel standards. Under the hood, this uses the bdist_wheel command provided by the wheel project. For more information please visit https://pythonwheels.com/ Args: universal (:bool, optional): True if the created will should be universal, False otherwise. wheel_version (:str, optional): Which wheel version to use. Raises: WheelExistsException: Destination file already exists. DirectoryDoesntExistException: Destination directory does not exist. """ temp_dir = tempfile.mkdtemp() try: dist_dir = os.path.join(temp_dir, 'dist') bdist_dir = os.path.join(temp_dir, 'bdist') if not os.path.exists(self._setup_py_path): raise exceptions.SetupPyNotFoundException( repo=self._repo_location) name = self._name with self._create_virtualenv(name) as virtualenv: self._logger.debug('Installing wheel...') pip_path = utils.get_python_executable('pip', exec_home=virtualenv) self._runner.run('{} wheel=={}'.format( self._pip_install(pip_path), wheel_version or DEFAULT_WHEEL_VERSION), cwd=self._repo_dir) command = '{} {} bdist_wheel --bdist-dir {} --dist-dir {}'.format( utils.get_python_executable('python', exec_home=virtualenv), self._setup_py_path, bdist_dir, dist_dir) if universal: command = '{0} --universal'.format(command) self._debug('Running bdist_wheel...', universal=universal) self._runner.run(command, cwd=self._repo_dir) self._debug('Finished running bdist_wheel.', universal=universal) actual_name = utils.lsf(dist_dir)[0] destination = os.path.join(self._target_dir, actual_name) try: utils.validate_file_does_not_exist(path=destination) except exceptions.FileExistException as e: raise exceptions.WheelExistsException(path=e.path) shutil.copy(os.path.join(dist_dir, actual_name), destination) self._debug('Packaged successfully.', package=destination) return os.path.abspath(destination) finally: utils.rmf(temp_dir) # pylint: disable=too-many-locals # pylint: disable=too-many-statements def nsis(self, binary_path, version=None, output=None, author=None, url=None, copyright_string=None, description=None, license_path=None, program_files_dir=None): """ Create a windows installer package. This method will produce an executable installer (.exe) that, when executed, will install the provided binary into "Program Files". In addition, it will manipulate the system PATH variable on the target machine so that the binary can be executed from any directory. Under the hood, this uses the NSIS project. For more information please visit https://nsis.sourceforge.io/Main_Page Args: binary_path (:str): True if the created will should be universal, False otherwise. version (:str, optional): Version string metadata. Defaults to the 'version' argument in your setup.py file. output (:str, optional): Target file to create. Defaults to {binary-path-basename}-installer.exe author (:str, optional): Package author. Defaults to the value specified in setup.py. url (:str, optional): URL to the package website. Defaults to the value specified in setup.py. copyright_string (:str, optional): Copyright. Defaults to an empty string. description (:str, optional): Package description. Defaults to the value specified in setup.py. license_path (:str, optional): Path to a license file. Defaults to the value specified in setup.py. program_files_dir (:str, optional): Directory name inside Program Files where the app will be installed. Raises: LicenseNotFoundException: License file doesn't exist. BinaryFileDoesntExistException: The provided binary file doesn't exist. FileExistsException: Destination file already exists. DirectoryDoesntExistException: The destination directory does not exist. """ if not utils.is_windows(): raise exceptions.WrongPlatformException(expected='Windows') if not binary_path: raise exceptions.InvalidArgumentsException('Must pass binary_path') try: self._debug('Validating binary exists: {}'.format(binary_path)) utils.validate_file_exists(binary_path) except (exceptions.FileDoesntExistException, exceptions.FileIsADirectoryException): raise exceptions.BinaryDoesntExistException(binary_path) try: version = version or self._version self._debug('Validating version string: {}'.format(version)) utils.validate_nsis_version(version) except exceptions.InvalidNSISVersionException as err: tb = sys.exc_info()[2] try: # Auto-correction attempt for standard python versions version = '{}.0'.format(version) utils.validate_nsis_version(version) except exceptions.InvalidNSISVersionException: utils.raise_with_traceback(err, tb) installer_base_name = os.path.basename(binary_path).replace('.exe', '') try: name = self._name except BaseException as e: self._debug( 'Unable to extract default name from setup.py: {}. Using binary base name...' .format(str(e))) name = installer_base_name installer_name = '{}-installer'.format(installer_base_name) copyright_string = copyright_string or '' destination = os.path.abspath( output or '{}.exe'.format(os.path.join(self._target_dir, installer_name))) self._debug('Validating destination file does not exist: {}'.format( destination)) utils.validate_file_does_not_exist(destination) target_directory = os.path.abspath(os.path.join( destination, os.pardir)) self._debug( 'Validating target directory exists: {}'.format(target_directory)) utils.validate_directory_exists(target_directory) try: license_path = license_path or os.path.abspath( os.path.join(self._repo_dir, self._license)) self._debug( 'Validating license file exists: {}'.format(license_path)) utils.validate_file_exists(license_path) except (exceptions.FileDoesntExistException, exceptions.FileIsADirectoryException) as e: raise exceptions.LicenseNotFoundException(str(e)) author = author or self._author url = url or self._url description = description or self._description program_files_dir = program_files_dir or name config = { 'name': name, 'author': author, 'website': url, 'copyright': copyright_string, 'license_path': license_path, 'binary_path': binary_path, 'description': description, 'installer_name': installer_name, 'program_files_dir': program_files_dir } temp_dir = tempfile.mkdtemp() try: support = 'windows_support' template = get_text_resource( os.path.join(support, 'installer.nsi.jinja')) nsis_zip_resource = get_binary_resource( os.path.join(support, 'nsis-3.04.zip')) path_header_resource = get_text_resource( os.path.join(support, 'path.nsh')) self._debug('Rendering nsi template...') nsi = Template(template).render(**config) installer_path = os.path.join(temp_dir, 'installer.nsi') with open(installer_path, 'w') as f: f.write(nsi) self._debug( 'Finished rendering nsi template: {}'.format(installer_path)) self._debug('Writing path header file...') path_header_path = os.path.join(temp_dir, 'path.nsh') with open(path_header_path, 'w') as header: header.write(path_header_resource) self._debug('Finished writing path header file: {}'.format( path_header_path)) self._debug('Extracting NSIS from resources...') nsis_archive = os.path.join(temp_dir, 'nsis.zip') with open(nsis_archive, 'wb') as _w: _w.write(nsis_zip_resource) utils.unzip(nsis_archive, target_dir=temp_dir) self._debug( 'Finished extracting makensis.exe from resources: {}'.format( nsis_archive)) makensis_path = os.path.join(temp_dir, 'nsis-3.04', 'makensis.exe') command = '{} -DVERSION={} {}'.format(makensis_path, version, installer_path) # The installer expects the binary to be located in the working directory # and be named {{ name }}.exe. # See installer.nsi.jinja#L85 expected_binary_path = os.path.join(temp_dir, '{}.exe'.format(name)) self._debug('Copying binary to expected location: {}'.format( expected_binary_path)) shutil.copyfile(src=binary_path, dst=expected_binary_path) self._debug('Creating installer...') self._runner.run(command, cwd=temp_dir) out_file = os.path.join(temp_dir, '{}.exe'.format(installer_name)) self._debug('Copying {} to target path...'.format(out_file)) shutil.copyfile(out_file, destination) self._debug('Finished copying installer to target path: {}'.format( destination)) self._debug('Packaged successfully.', package=destination) return destination finally: utils.rmf(temp_dir) @cachedproperty def _name(self): return self._setup_py_argument('name') @cachedproperty def _author(self): return self._setup_py_argument('author') @cachedproperty def _version(self): return self._setup_py_argument('version') @cachedproperty def _description(self): return self._setup_py_argument('description') @cachedproperty def _license(self): return self._setup_py_argument('license') @cachedproperty def _url(self): return self._setup_py_argument('url') @cachedproperty def _entrypoint(self): entry_points = self._setup_py_argument('entry_points') console_scripts = entry_points['console_scripts'] if len(console_scripts) > 1: raise exceptions.MultipleDefaultEntrypointsFoundException( repo=self._repo_location, entrypoints=console_scripts) script = console_scripts[0].split('=')[1].strip().split( ':')[0].replace('.', os.sep) script_path = os.path.join(self._repo_dir, '{}.py'.format(script)) if not os.path.exists(script_path): raise exceptions.ConsoleScriptNotFoundException( repo=self._repo_location, script=script_path) return script_path @cachedproperty def _setup_py_path(self): return os.path.join(self._repo_dir, 'setup.py') @cachedproperty def _setup_py(self): with open(self._setup_py_path) as f: setup_py = f.read() kwargs = {} def _setup(*_, **__): kwargs.update(__) import setuptools setup_func = setuptools.setup try: with tempfile.NamedTemporaryFile() as errors: setuptools.setup = _setup obj = compile(setup_py, errors.name, mode='exec') # pylint: disable=eval-used eval(obj) return kwargs finally: setuptools.setup = setup_func @cachedproperty def _interpreter(self): return self._python or self._lookup_interpreter() def _lookup_interpreter(self): if utils.is_pyinstaller(): self._debug( 'Running inside a frozen package...Looking up python interpreter from PATH' ) interpreter = utils.which('python') else: self._debug( 'Running inside a python environment...Looking up python interpreter locally' ) interpreter = utils.get_python_executable('python') if not interpreter: raise exceptions.PythonNotFoundException() self._debug('Python interpreter: {}'.format(interpreter)) return interpreter # pylint: disable=too-many-branches @contextlib.contextmanager def _create_virtualenv(self, name): temp_dir = tempfile.mkdtemp() virtualenv_path = os.path.join(temp_dir, name) self._debug('Creating virtualenv {}'.format(virtualenv_path)) def _create_virtualenv_dist(): dist_directory = os.path.join(temp_dir, 'virtualenv-dist') support_directory = os.path.join(dist_directory, 'virtualenv_support') os.makedirs(dist_directory) os.makedirs(support_directory) _virtualenv_py = os.path.join(dist_directory, 'virtualenv.py') def _write_support_wheel(_wheel): with open(os.path.join(support_directory, _wheel), 'wb') as _w: _w.write( get_binary_resource( os.path.join('virtualenv_support', _wheel))) with open(_virtualenv_py, 'w') as venv_py: venv_py.write(get_text_resource('virtualenv.py')) _write_support_wheel('pip-19.1.1-py2.py3-none-any.whl') _write_support_wheel('setuptools-41.0.1-py2.py3-none-any.whl') return _virtualenv_py virtualenv_py = _create_virtualenv_dist() create_virtualenv_command = '{} {} --no-wheel {}'.format( self._interpreter, virtualenv_py, virtualenv_path) requirements_file = os.path.join(self._repo_dir, 'requirements.txt') self._runner.run(create_virtualenv_command, cwd=self._repo_dir) pip_path = utils.get_python_executable('pip', exec_home=virtualenv_path) install_command = None if os.path.exists(requirements_file): self._debug( 'Using requirements file: {}'.format(requirements_file)) install_command = '{} -r {}'.format(self._pip_install(pip_path), requirements_file) elif os.path.exists(self._setup_py_path): self._debug('Using install_requires from setup.py: {}'.format( self._setup_py_path)) requires = self._setup_py.get('install_requires') install_command = '{} {}'.format(self._pip_install(pip_path), ' '.join(requires)) if install_command: self._debug('Installing {} requirements...'.format(name)) self._runner.run(install_command, cwd=self._repo_dir) self._debug( 'Successfully created virtualenv {}'.format(virtualenv_path)) try: yield virtualenv_path finally: try: utils.rmf(temp_dir) except BaseException as e: if utils.is_windows(): # The temp_dir was populated with files written by a different process # (pip install) On windows, this causes a [Error 5] Access is denied error. # Eventually I will have to fix this - until then, sorry windows users... self._debug( "Failed cleaning up temporary directory after creating virtualenv " "{}: {} - You might have some leftovers because of this..." .format(temp_dir, str(e))) else: raise def _setup_py_argument(self, argument): if not os.path.exists(self._setup_py_path): err = exceptions.SetupPyNotFoundException(repo=self._repo_location) else: self._debug('Reading {} from setup.py...'.format(argument)) value = self._setup_py.get(argument) if value is None: err = exceptions.MissingSetupPyArgumentException( repo=self._repo_location, argument=argument) else: return value raise exceptions.FailedDetectingPackageMetadataException( argument=argument, reason=err) def _debug(self, message, **kwargs): kwargs = copy.deepcopy(kwargs) kwargs.update(self._log_ctx) self._logger.debug(message, **kwargs) def _pip_install(self, pip_path): command = '{} install'.format(pip_path) if self._logger.isEnabledFor(logging.DEBUG): command = '{} -v'.format(command) return command def _pyinstaller(self, pyinstaller_path): command = pyinstaller_path if self._logger.isEnabledFor(logging.DEBUG): command = '{} --log-level DEBUG'.format(command) return command
def _runner(): yield LocalCommandRunner()
def __init__(self, repo, sha): self._repo = repo self._sha = sha self._runner = LocalCommandRunner() self._logger = logger.Logger(__name__) self._log_ctx = {'repo': self._repo.repo.full_name, 'sha': self._sha}