예제 #1
0
 def get_python_version(self):
     """
     Get Python version of the prefix (unless set_config_reference() is set
     to pybombs).
     """
     return {
         'pybombs': sysutils.get_interpreter_version(),
         'prefix': self.get('python_ver'),
     }[self._config_reference]
예제 #2
0
 def get_python_version(self):
     """
     Get Python version of the prefix (unless set_config_reference() is set
     to pybombs).
     """
     return {
         'pybombs': sysutils.get_interpreter_version(),
         'prefix': self.get('python_ver'),
     }[self._config_reference]
예제 #3
0
 def _detect_python_version(self):
     """
     Detect the Python version used in the prefix. Note this is *not*
     necessarily the version of Python used to execute this script.
     """
     if self.get('python_ver'):
         self.log.debug("Python version set by config file.")
         return
     # Put all the smart stuff here, were we detect the actual Python version
     # used in the prefix TODO
     else:
         # If there is no indication which Python version is used in the
         # prefix, we'll assume it's be using the same Python version as
         # is currently running
         self.log.debug("Python version derived from current interpreter.")
         self.set('python_ver', sysutils.get_interpreter_version())
예제 #4
0
 def _detect_python_version(self):
     """
     Detect the Python version used in the prefix. Note this is *not*
     necessarily the version of Python used to execute this script.
     """
     if self.get('python_ver'):
         self.log.debug("Python version set by config file.")
         return
     # Put all the smart stuff here, were we detect the actual Python version
     # used in the prefix TODO
     else:
         # If there is no indication which Python version is used in the
         # prefix, we'll assume it's be using the same Python version as
         # is currently running
         self.log.debug("Python version derived from current interpreter.")
         self.set('python_ver', sysutils.get_interpreter_version())
예제 #5
0
class PrefixInfo(object):
    """
    Stores information about the current prefix being used.
    """
    prefix_conf_dir = '.pybombs'
    src_dir_name = 'src'
    env_prefix_var = 'PYBOMBS_PREFIX'
    env_srcdir_var = 'PYBOMBS_PREFIX_SRC'
    inv_file_name = 'inventory.yml'
    setup_env_key = 'setup_env'
    default_config_info = {
        'prefix_aliases': {},
        'prefix_config_dir': {},
        'env': {},
        'recipes': {},
        'packages': {'gnuradio': {'forcebuild': True}},
        'categories': {'common': {'forcebuild': True}},
        'python_ver': sysutils.get_interpreter_version(),
    }
    default_env_unix = { # These envs are non-portable
        'PATH': "{prefix_dir}/bin:$PATH",
        'PYTHONPATH': "{python_path}:$PYTHONPATH",
        'LD_LIBRARY_PATH': "{prefix_dir}/lib:{prefix_dir}/lib64/:$LD_LIBRARY_PATH",
        'LIBRARY_PATH': "{prefix_dir}/lib:{prefix_dir}/lib64/:$LIBRARY_PATH",
        'PKG_CONFIG_PATH':
            "{prefix_dir}/lib/pkgconfig:{prefix_dir}/lib64/pkgconfig:$PKG_CONFIG_PATH",
        'GRC_BLOCKS_PATH': "{prefix_dir}/share/gnuradio/grc/blocks:$GRC_BLOCKS_PATH",
        'PYBOMBS_PREFIX': "{prefix_dir}",
    }


    def __init__(self, args, cfg_list, select_prefix=None):
        self.log = pb_logging.logger.getChild("ConfigManager.PrefixInfo")
        self.prefix_dir = None
        self.prefix_cfg_dir = None
        self.prefix_src = None
        self.alias = None
        self.src_dir = None
        self.cfg_file = None
        self.inv_file = None
        self.inventory = None
        self.recipe_dir = None
        self.target_dir = None
        self.env = os.environ.copy()
        self.is_virtualenv = False
        self._cfg_info = OrderedDict(self.default_config_info)
        if select_prefix is not None:
            args.prefix = select_prefix
        # 1) Load the config info
        for cfg_file in reversed(cfg_list):
            self._cfg_info = \
                self._merge_config_info_from_file(cfg_file, self._cfg_info)
        # 2) Find the prefix directory
        self._find_prefix_dir(args)
        if self.prefix_dir is None:
            self.log.debug(
                "Cannot establish a prefix directory. "
                "This may cause issues down the line.")
            self._set_attrs()
            return
        assert self.prefix_dir is not None
        if self.alias is not None and self.alias in self._cfg_info['prefix_config_dir']:
            self.prefix_cfg_dir = npath(self._cfg_info['prefix_config_dir'][self.alias])
            self.log.debug("Choosing prefix config dir from alias: {0}"
                           .format(self.prefix_cfg_dir))
        elif self.prefix_dir in self._cfg_info['prefix_config_dir']:
            self.prefix_cfg_dir = npath(self._cfg_info['prefix_config_dir'][self.prefix_dir])
            self.log.debug(
                "Choosing prefix config dir from path lookup in prefix_config_dir: {0}"
                .format(self.prefix_cfg_dir))
        else:
            self.prefix_cfg_dir = npath(os.path.join(self.prefix_dir, self.prefix_conf_dir))
            self.log.debug("Choosing default prefix config dir: {0}".format(self.prefix_cfg_dir))
        if not os.path.isdir(self.prefix_cfg_dir):
            self.log.debug("Config dir does not yet exist.")
        self.is_virtualenv = sysutils.is_virtualenv(self.prefix_dir)
        if self.is_virtualenv:
            self.log.info("Prefix is a Python virtualenv.")
        # 3) Find the config file
        self.cfg_file = npath(os.path.join(self.prefix_cfg_dir, ConfigManager.cfg_file_name))
        config_section = {}
        if not os.path.isfile(self.cfg_file):
            self.log.debug(
                "Prefix configuration file not found: {0}, assuming empty."
                .format(self.cfg_file))
        else:
            config_section = PBConfigFile(self.cfg_file).get('config')
            self._cfg_info = self._merge_config_info_from_file(self.cfg_file, self._cfg_info)
        # 4) Find the src dir
        self.src_dir = npath(
            config_section.get('srcdir', os.path.join(self.prefix_dir, self.src_dir_name)))
        self.log.debug("Prefix source dir is: {0}".format(self.src_dir))
        if not os.path.isdir(self.src_dir):
            self.log.debug("Source dir does not exist.")
        # 5) Find the inventory file
        self.inv_file = npath(os.path.join(self.prefix_cfg_dir, self.inv_file_name))
        if not os.path.isfile(self.inv_file):
            self.log.debug("Prefix inventory file not found: {0}".format(self.inv_file))
        self.inventory = inventory.Inventory(inventory_file=self.inv_file)
        # 6) Prefix-specific recipes. There's two places for these:
        # - A 'recipes/' subdirectory
        # - Anything declared in the config.yml file inside the prefix
        self.recipe_dir = npath(
            config_section.get('recipes', os.path.join(self.prefix_cfg_dir, 'recipes')))
        if os.path.isdir(self.recipe_dir):
            self.log.debug("Prefix-local recipe dir is: {0}".format(self.recipe_dir))
        else:
            self.recipe_dir = None
        # 7) Detect Python version and library paths
        self._detect_python_version()
        self._detect_python_path()
        # 8) Load environment
        # If there's a setup_env option in the current config file, we use that
        if self.setup_env_key in config_section:
            self.log.debug(
                'Loading environment from shell script: {0}'
                .format(config_section[self.setup_env_key]))
            self.env = self._load_environ_from_script(config_section[self.setup_env_key])
        else:
            self.env = self._load_default_env(self.env)
        # Set env vars that we always need
        self.env[self.env_prefix_var] = self.prefix_dir
        self.env[self.env_srcdir_var] = self.src_dir
        # env: sections are always respected:
        old_env = os.environ  # Bit of an ugly hack to allow use of
        os.environ = self.env # os.path.expandvars() on self.env
        for k, v in iteritems(self._cfg_info['env']):
            self.env[k.upper()] = os.path.expandvars(v.strip())
        os.environ = old_env
        # 9) Keep relevant config sections as attributes
        self._set_attrs()

    def _set_attrs(self):
        """ Map the _cfg_info dict onto attributes. """
        for k, v in iteritems(self._cfg_info):
            if k == 'env' or not k in self.default_config_info.keys():
                continue
            setattr(self, k, v)

    def _merge_config_info_from_file(self, cfg_file, cfg_data):
        """
        Load a config file, load its contents, and merge it into cfg_info.
        Return the result.
        """
        try:
            self.log.debug('Inspecting config file: {0}'.format(cfg_file))
            cfg_data_new = PBConfigFile(cfg_file).get()
        except Exception:
            self.log.debug('Well, looks like that failed.')
            return cfg_data
        return dict_merge(cfg_data, cfg_data_new)

    def _find_prefix_dir(self, args):
        """
        Find the current prefix' directory.
        Order is:
        1) From the command line (-p switch; either an alias, or a directory)
        2) Environment variable (see env_prefix_var)
        3) CWD (if it has a .pybombs subdir and is not the home directory)
        4) The config option called 'default_prefix'

        If all of these fail, we have no prefix.
        """
        if args.prefix is not None:
            if args.prefix in self._cfg_info['prefix_aliases']:
                self.log.debug("Resolving prefix alias {0}.".format(args.prefix))
                self.alias = args.prefix
                args.prefix = self._cfg_info['prefix_aliases'][args.prefix]
            if not os.path.isdir(npath(args.prefix)):
                self.log.error("Not a prefix: {0}".format(args.prefix))
                raise PBException("Can't open prefix: {0}".format(args.prefix))
            self.prefix_dir = npath(args.prefix)
            self.prefix_src = 'cli'
            self.log.debug("Choosing prefix dir from command line: {0}".format(self.prefix_dir))
            return
        if self.env_prefix_var in os.environ and os.path.isdir(os.environ[self.env_prefix_var]):
            self.prefix_dir = npath(os.environ[self.env_prefix_var])
            self.prefix_src = 'env'
            self.log.debug(
                'Using environment variable {0} as prefix ({1})'
                .format(self.env_prefix_var, self.prefix_dir))
            return
        if os.getcwd() != os.path.expanduser('~') and \
                os.path.isdir(os.path.join('.', self.prefix_conf_dir)):
            self.prefix_dir = os.getcwd()
            self.prefix_src = 'cwd'
            self.log.debug('Using CWD as prefix ({0})'.format(self.prefix_dir))
            return
        if self._cfg_info.get('config', {}).get('default_prefix'):
            default_prefix = self._cfg_info['config']['default_prefix']
            if default_prefix in self._cfg_info['prefix_aliases']:
                self.log.debug("Resolving prefix alias `{0}'.".format(default_prefix))
                self.prefix_dir = npath(self._cfg_info['prefix_aliases'][default_prefix])
            else:
                self.prefix_dir = npath(default_prefix)
            self.log.debug('Using default_prefix as prefix ({0})'.format(self.prefix_dir))
            self.prefix_src = 'default'
            return
        self.prefix_src = None
        self.prefix_dir = None

    def _detect_python_version(self):
        """
        Detect the Python version used in the prefix. Note this is *not*
        necessarily the version of Python used to execute this script.
        """
        if 'python_ver' in self._cfg_info:
            self.log.debug("Python version set by config file.")
        else:
            # Put all the smart stuff here, were we detect the actual Python version
            # used in the prefix TODO
            # If there is no indication which Python version is used in the
            # prefix, we'll assume it's be using the same Python version as
            # is currently running
            self.log.debug("Python version derived from current interpreter.")
            self._cfg_info['python_ver'] = sysutils.get_interpreter_version()
        self.python_ver = self._cfg_info['python_ver']

    def _detect_python_path(self):
        """
        From the Python version, derive the Python paths within the prefix.
        """
        py_ver_major, py_ver_minor = self._cfg_info.get('python_ver').split('.')[0:2]
        conservative_guess = [
            '{prefix}/lib/python{major}/site-packages',
            '{prefix}/lib/python{major}/dist-packages',
            '{prefix}/lib/python{major}.{minor}/site-packages',
            '{prefix}/lib/python{major}.{minor}/dist-packages',
            '{prefix}/lib64/python{major}/site-packages',
            '{prefix}/lib64/python{major}/dist-packages',
            '{prefix}/lib64/python{major}.{minor}/site-packages',
            '{prefix}/lib64/python{major}.{minor}/dist-packages',
        ]
        self.python_path = ':'.join([
            x.format(prefix=self.prefix_dir,
                     major=py_ver_major,
                     minor=py_ver_minor)
            for x in conservative_guess
        ])

    def _load_environ_from_script(self, setup_env_file):
        """
        Run setup_env_file, return the new env
        FIXME make this portable!
        """
        self.log.debug('Loading environment from shell script: {0}'.format(setup_env_file))
        # It would be nice if we could do os.path.expandvars() with a custom
        # env, wouldn't it
        setup_env_file = setup_env_file.replace('${0}'.format(self.env_prefix_var), self.prefix_dir)
        setup_env_file = setup_env_file.replace('${{{0}}}'.format(self.env_prefix_var), self.prefix_dir)
        # TODO add some checks this is a legit script
        # Damn, I hate just running stuff :/
        # TODO unportable command:
        separator = '<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>'
        get_env_cmd = "source {env_file} && echo '{sep}' && env".format(env_file=setup_env_file, sep=separator)
        from pybombs.utils import subproc
        try:
            script_output = subproc.check_output(get_env_cmd, shell=True)
        except subproc.CalledProcessError:
            self.log.error("Trouble sourcing file {env_file}".format(env_file=setup_env_file))
            raise PBException("Could not source env file.")
        env_output = script_output.split(separator)[-1]
        # TODO assumption is that env_output now just holds the env output
        env_output = env_output.split('\n')
        env = {}
        for env_line in env_output:
            env_line = env_line.strip()
            if len(env_line) == 0:
                continue
            k, v = env_line.split('=', 1)
            env[k] = v
        return env

    def _load_default_env(self, env):
        """
        TODO: Make this portable
        """
        for k, v in iteritems(self.default_env_unix):
            env[k] = os.path.expandvars(v.strip().format(
                prefix_dir=self.prefix_dir,
                python_path=self.python_path,
            ))
        return env

    def get_prefix_cfg_dir_writable(self):
        " Returns self.prefix_cfg_dir, but if that doesn't exist, create it first. "
        if self.prefix_dir is None:
            raise PBException("Can't access prefix config dir if prefix does not exist.")
        sysutils.mkdir_writable(self.prefix_cfg_dir, self.log)
        return self.prefix_cfg_dir