1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184
|
# projects.py - module containing per-project package mapping primitives
#
# Copyright (C) 2017-2020 Red Hat, Inc.
#
# SPDX-License-Identifier: GPL-2.0-or-later
import logging
import yaml
from pathlib import Path
from pkg_resources import resource_filename
from lcitool import util, LcitoolError
from lcitool.packages import PyPIPackage, CPANPackage
log = logging.getLogger(__name__)
class ProjectError(LcitoolError):
"""
Global exception type for the projects module.
Functions/methods in this module will raise either this exception or its
subclass on failure.
"""
def __init__(self, message):
super().__init__(message, "Project")
class Projects:
"""
Attributes:
:ivar names: list of all project names
:ivar public: dictionary from project names to ``Project`` objects for public projects
:ivar internal: dictionary from project names to ``Project`` objects for internal projects
"""
@property
def public(self):
if self._public is None:
self._load_public()
return self._public
@property
def names(self):
return list(self.public.keys())
@property
def internal(self):
if self._internal is None:
self._load_internal()
return self._internal
def __init__(self):
self._public = None
self._internal = None
def _load_projects_from_path(self, path):
projects = {}
for item in path.iterdir():
if not item.is_file() or item.suffix != ".yml":
continue
projects[item.stem] = Project(self, item.stem, item)
return projects
def _load_public(self):
source = Path(resource_filename(__name__, "facts/projects"))
projects = self._load_projects_from_path(source)
if util.get_extra_data_dir() is not None:
source = Path(util.get_extra_data_dir()).joinpath("projects")
projects.update(self._load_projects_from_path(source))
self._public = projects
def _load_internal(self):
source = Path(resource_filename(__name__, "facts/projects/internal"))
self._internal = self._load_projects_from_path(source)
def expand_names(self, pattern):
try:
return util.expand_pattern(pattern, self.names, "project")
except Exception as ex:
log.debug(f"Failed to expand '{pattern}'")
raise ProjectError(f"Failed to expand '{pattern}': {ex}")
def get_packages(self, projects, target):
packages = {}
for proj in projects:
try:
obj = self.public[proj]
except KeyError:
obj = self.internal[proj]
packages.update(obj.get_packages(target))
return packages
def eval_generic_packages(self, target, generic_packages):
pkgs = {}
needs_pypi = False
needs_cpan = False
for mapping in generic_packages:
pkg = target.get_package(mapping)
if pkg is None:
continue
pkgs[pkg.mapping] = pkg
if isinstance(pkg, PyPIPackage):
needs_pypi = True
elif isinstance(pkg, CPANPackage):
needs_cpan = True
# The get_packages eval_generic_packages cycle is deliberate and
# harmless since we'll only ever hit it with the following internal
# projects
if needs_pypi:
proj = self.internal["python-pip"]
pkgs.update(proj.get_packages(target))
if needs_cpan:
proj = self.internal["perl-cpan"]
pkgs.update(proj.get_packages(target))
return pkgs
class Project:
"""
Attributes:
:ivar name: project name
:ivar generic_packages: list of generic packages needed by the project
to build successfully
:ivar projects: parent ``Projects`` instance
"""
@property
def generic_packages(self):
# lazy evaluation: load per-project generic package list when we actually need it
if self._generic_packages is None:
self._generic_packages = self._load_generic_packages()
return self._generic_packages
def __init__(self, projects, name, path):
self.projects = projects
self.name = name
self.path = path
self._generic_packages = None
self._target_packages = {}
def _load_generic_packages(self):
log.debug(f"Loading generic package list for project '{self.name}'")
try:
with open(self.path, "r") as infile:
yaml_packages = yaml.safe_load(infile)
return yaml_packages["packages"]
except Exception as ex:
log.debug(f"Can't load pacakges for '{self.name}'")
raise ProjectError(f"Can't load packages for '{self.name}': {ex}")
def get_packages(self, target):
osname = target.facts["os"]["name"]
osversion = target.facts["os"]["version"]
target_name = f"{osname.lower()}-{osversion.lower()}"
if target.cross_arch is None:
target_name = f"{target_name}-x86_64"
else:
try:
util.validate_cross_platform(target.cross_arch, osname)
except ValueError as ex:
raise ProjectError(ex)
target_name = f"{target_name}-{target.cross_arch}"
# lazy evaluation + caching of package names for a given distro
if self._target_packages.get(target_name) is None:
self._target_packages[target_name] = self.projects.eval_generic_packages(target,
self.generic_packages)
return self._target_packages[target_name]
|