File: projects.py

package info (click to toggle)
qemu 1%3A7.2%2Bdfsg-7%2Bdeb12u13
  • links: PTS, VCS
  • area: main
  • in suites: bookworm, bookworm-proposed-updates
  • size: 288,192 kB
  • sloc: ansic: 2,701,923; pascal: 112,708; python: 62,697; sh: 50,281; asm: 48,732; makefile: 17,260; cpp: 9,441; perl: 8,084; xml: 2,911; objc: 1,870; php: 1,299; tcl: 1,188; yacc: 604; lex: 363; sql: 71; awk: 35; sed: 11; javascript: 7
file content (184 lines) | stat: -rw-r--r-- 5,837 bytes parent folder | download
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]