platypush/platypush/builder/_base.py

199 lines
5.9 KiB
Python

from abc import ABC, abstractmethod
import argparse
import inspect
import logging
import os
import pathlib
import sys
from typing import Final, Optional, Sequence
from platypush.config import Config
from platypush.utils.manifest import (
Dependencies,
InstallContext,
)
logging.basicConfig(stream=sys.stdout)
logger = logging.getLogger()
class BaseBuilder(ABC):
"""
Base interface and utility methods for Platypush builders.
A Platypush builder is a script/piece of logic that can build a Platypush
installation, with all its base and required extra dependencies, given a
configuration file.
This class is currently implemented by the :module:`platypush.platyvenv`
and :module:`platypush.platydock` modules/scripts.
"""
REPO_URL: Final[str] = 'https://github.com/BlackLight/platypush.git'
"""
We use the Github URL here rather than the self-hosted Gitea URL to prevent
too many requests to the Gitea server.
"""
def __init__(
self,
cfgfile: str,
gitref: str,
output: str,
install_context: InstallContext,
*_,
verbose: bool = False,
device_id: Optional[str] = None,
**__,
) -> None:
"""
:param cfgfile: The path to the configuration file.
:param gitref: The git reference to use. It can be a branch name, a tag
name or a commit hash.
:param output: The path to the output file or directory.
:param install_context: The installation context for this builder.
:param verbose: Whether to log debug traces.
:param device_id: A device name that will be used to uniquely identify
this installation.
"""
self.cfgfile = os.path.abspath(os.path.expanduser(cfgfile))
self.output = os.path.abspath(os.path.expanduser(output))
self.gitref = gitref
self.install_context = install_context
self.device_id = device_id
logger.setLevel(logging.DEBUG if verbose else logging.INFO)
@classmethod
@abstractmethod
def get_name(cls) -> str:
"""
:return: The name of the builder.
"""
@classmethod
@abstractmethod
def get_description(cls) -> str:
"""
:return: The description of the builder.
"""
@property
def deps(self) -> Dependencies:
"""
:return: The dependencies for this builder, given the configuration
file and the installation context.
"""
return Dependencies.from_config(
self.cfgfile,
install_context=self.install_context,
)
def _print_instructions(self, s: str) -> None:
GREEN = '\033[92m'
NORM = '\033[0m'
helper_lines = s.split('\n')
wrapper_line = '=' * max(len(t) for t in helper_lines)
helper = '\n' + '\n'.join([wrapper_line, *helper_lines, wrapper_line]) + '\n'
print(GREEN + helper + NORM)
@abstractmethod
def build(self):
"""
Builds the application. To be implemented by the subclasses.
"""
@classmethod
def _get_arg_parser(cls) -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(
prog=cls.get_name(),
add_help=False,
description=cls.get_description(),
)
parser.add_argument(
'-h', '--help', dest='show_usage', action='store_true', help='Show usage'
)
parser.add_argument(
'-v',
'--verbose',
dest='verbose',
action='store_true',
help='Enable debug traces',
)
parser.add_argument(
'-c',
'--config',
type=str,
dest='cfgfile',
required=False,
default=None,
help='The path to the configuration file. If not specified, a minimal '
'installation including only the base dependencies will be generated.',
)
parser.add_argument(
'-o',
'--output',
dest='output',
type=str,
required=False,
default='.',
help='Target directory (default: current directory). For Platydock, '
'this is the directory where the Dockerfile will be generated. For '
'Platyvenv, this is the base directory of the new virtual '
'environment.',
)
parser.add_argument(
'-d',
'--device-id',
dest='device_id',
type=str,
required=False,
default=None,
help='A name that will be used to uniquely identify this device. '
'Default: a random name for Docker containers, and the '
'hostname of the machine for virtual environments.',
)
parser.add_argument(
'-r',
'--ref',
dest='gitref',
required=False,
type=str,
default='master',
help='If the script is not run from a Platypush installation directory, '
'it will clone the sources via git. You can specify through this '
'option which branch, tag or commit hash to use. Defaults to master.',
)
return parser
@classmethod
def from_cmdline(cls, args: Sequence[str]):
"""
Create a builder instance from command line arguments.
:param args: Command line arguments.
:return: A builder instance.
"""
parser = cls._get_arg_parser()
opts, _ = parser.parse_known_args(args)
if opts.show_usage:
parser.print_help()
sys.exit(0)
if not opts.cfgfile:
opts.cfgfile = os.path.join(
str(pathlib.Path(inspect.getfile(Config)).parent),
'config.yaml',
)
logger.info('No configuration file specified. Using %s.', opts.cfgfile)
return cls(**vars(opts))