platypush/platypush/platyvenv/__init__.py

211 lines
6.1 KiB
Python
Raw Normal View History

2023-08-23 11:53:14 +02:00
"""
Platyvenv is a helper script that allows you to automatically create a
virtual environment for Platypush starting from a configuration file.
"""
from contextlib import contextmanager
import logging
2023-08-23 11:53:14 +02:00
import os
import shutil
2023-08-23 11:53:14 +02:00
import subprocess
import sys
import tempfile
import textwrap
from typing import Generator, Sequence
2023-08-23 11:53:14 +02:00
import venv
from platypush.builder import BaseBuilder
2023-08-23 11:53:14 +02:00
from platypush.config import Config
from platypush.utils.manifest import (
Dependencies,
InstallContext,
)
logger = logging.getLogger()
2023-08-23 11:53:14 +02:00
class VenvBuilder(BaseBuilder):
2023-08-23 11:53:14 +02:00
"""
Build a virtual environment from on a configuration file.
"""
def __init__(self, *args, **kwargs) -> None:
kwargs['install_context'] = InstallContext.DOCKER
super().__init__(*args, **kwargs)
@classmethod
def get_name(cls):
return "platyvenv"
@classmethod
def get_description(cls):
return "Build a Platypush virtual environment from a configuration file."
2023-08-23 11:53:14 +02:00
@property
def _pip_cmd(self) -> Sequence[str]:
"""
:return: The pip install command to use for the selected environment.
"""
return (
os.path.join(self.output, 'bin', 'python'),
2023-08-23 11:53:14 +02:00
'-m',
'pip',
'install',
'-U',
'--no-cache-dir',
'--no-input',
)
def _install_system_packages(self, deps: Dependencies):
"""
Install the required system packages.
"""
for cmd in deps.to_pkg_install_commands():
logger.info('Installing system packages: %s', cmd)
subprocess.run(cmd, shell=True, check=True)
def _run_before_commands(self, deps: Dependencies):
"""
Runs any commands that should be executed before the installation.
"""
for cmd in deps.before:
logger.info('Running: %s', cmd)
subprocess.run(cmd, shell=True, check=True)
def _run_after_commands(self, deps: Dependencies):
"""
Runs any commands that should be executed after the installation.
"""
for cmd in deps.after:
logger.info('Running: %s', cmd)
subprocess.run(cmd, shell=True, check=True)
2023-08-23 11:53:14 +02:00
@contextmanager
def _prepare_src_dir(self) -> Generator[str, None, None]:
"""
Prepare the source directory used to install the virtual enviornment.
If platyvenv is launched from a local checkout of the Platypush source
code, then that checkout will be used.
Otherwise, the source directory will be cloned from git into a
temporary folder.
"""
setup_py_path = os.path.join(os.getcwd(), 'setup.py')
if os.path.isfile(setup_py_path):
logger.info('Using local checkout of the Platypush source code')
yield os.getcwd()
else:
checkout_dir = tempfile.mkdtemp(prefix='platypush-', suffix='.git')
logger.info('Cloning Platypush source code from git into %s', checkout_dir)
subprocess.call(
[
'git',
'clone',
'--recursive',
'https://github.com/BlackLight/platypush.git',
checkout_dir,
]
)
pwd = os.getcwd()
os.chdir(checkout_dir)
subprocess.call(['git', 'checkout', self.gitref])
yield checkout_dir
os.chdir(pwd)
logger.info('Cleaning up %s', checkout_dir)
shutil.rmtree(checkout_dir, ignore_errors=True)
2023-08-23 11:53:14 +02:00
def _prepare_venv(self) -> None:
"""
Install the virtual environment under the configured output.
2023-08-23 11:53:14 +02:00
"""
logger.info('Creating virtual environment under %s...', self.output)
2023-08-23 11:53:14 +02:00
venv.create(
self.output,
2023-08-23 11:53:14 +02:00
symlinks=True,
with_pip=True,
upgrade_deps=True,
)
logger.info('Installing base Python dependencies under %s...', self.output)
subprocess.call([*self._pip_cmd, 'pip', '.'])
2023-08-23 11:53:14 +02:00
# Add <output>/bin to the PATH
os.environ['PATH'] = os.path.join(self.output, 'bin') + ':' + os.environ['PATH']
2023-08-23 11:53:14 +02:00
def _install_extra_pip_packages(self, deps: Dependencies):
"""
Install the extra pip dependencies inferred from the configured
extensions.
2023-08-23 11:53:14 +02:00
"""
pip_deps = list(deps.to_pip_install_commands(full_command=False))
if not pip_deps:
return
logger.info(
'Installing extra pip dependencies under %s: %s',
self.output,
' '.join(pip_deps),
2023-08-23 11:53:14 +02:00
)
subprocess.call([*self._pip_cmd, *pip_deps])
def build(self):
"""
Build a Dockerfile based on a configuration file.
"""
Config.init(self.cfgfile)
deps = Dependencies.from_config(
self.cfgfile,
install_context=InstallContext.VENV,
)
self._run_before_commands(deps)
2023-08-23 11:53:14 +02:00
self._install_system_packages(deps)
with self._prepare_src_dir():
self._prepare_venv()
2023-08-23 11:53:14 +02:00
self._install_extra_pip_packages(deps)
self._run_after_commands(deps)
self._print_instructions(
textwrap.dedent(
f"""
Virtual environment created under {self.output}.
To run the application:
source {self.output}/bin/activate
platypush -c {self.cfgfile} {
"--device_id " + self.device_id if self.device_id else ""
}
Platypush requires a Redis instance. If you don't want to use a
stand-alone server, you can pass the --start-redis option to
the executable (optionally with --redis-port).
Platypush will then start its own local instance and it will
terminate it once the application is stopped.
"""
2023-08-23 11:53:14 +02:00
)
)
2023-08-23 11:53:14 +02:00
def main():
"""
Generates a virtual environment based on the configuration file.
"""
VenvBuilder.from_cmdline(sys.argv[1:]).build()
return 0
2023-08-23 11:53:14 +02:00
if __name__ == '__main__':
sys.exit(main())
# vim:sw=4:ts=4:et: