Coverage for apio/apio_context.py: 89%
282 statements
« prev ^ index » next coverage.py v7.11.0, created at 2025-11-06 10:20 +0000
« prev ^ index » next coverage.py v7.11.0, created at 2025-11-06 10:20 +0000
1"""The apio context."""
3# -*- coding: utf-8 -*-
4# -- This file is part of the Apio project
5# -- (C) 2016-2019 FPGAwars
6# -- Author Jesús Arroyo
7# -- License GPLv2
9import os
10import sys
11import json
12import platform
13from dataclasses import dataclass
14from enum import Enum
15from pathlib import Path
16from typing import List, Optional, Dict, Tuple
17from apio.common.apio_console import cout, cerror, cstyle
18from apio.common.apio_styles import EMPH3, INFO, EMPH1
19from apio.common.common_util import env_build_path
20from apio.profile import Profile, RemoteConfigPolicy
21from apio.utils import jsonc, util, env_options
22from apio.managers.project import Project, load_project_from_file
23from apio.managers import packages
24from apio.managers.packages import PackagesContext
25from apio.utils.resource_util import (
26 ProjectResources,
27 collect_project_resources,
28 validate_project_resources,
29 validate_config,
30 validate_platforms,
31 validate_packages,
32)
35# ---------- RESOURCES
36RESOURCES_DIR = "resources"
38# ---------------------------------------
39# ---- File: resources/platforms.jsonc
40# --------------------------------------
41# -- This file contains the information regarding the supported platforms
42# -- and their attributes.
43PLATFORMS_JSONC = "platforms.jsonc"
45# ---------------------------------------
46# ---- File: resources/packages.jsonc
47# --------------------------------------
48# -- This file contains all the information regarding the available apio
49# -- packages: Repository, version, name...
50PACKAGES_JSONC = "packages.jsonc"
52# -----------------------------------------
53# ---- File: resources/boards.jsonc
54# -----------------------------------------
55# -- Information about all the supported boards
56# -- names, fpga family, programmer, ftdi description, vendor id, product id
57BOARDS_JSONC = "boards.jsonc"
59# -----------------------------------------
60# ---- File: resources/fpgas.jsonc
61# -----------------------------------------
62# -- Information about all the supported fpgas
63# -- arch, type, size, packaging
64FPGAS_JSONC = "fpgas.jsonc"
66# -----------------------------------------
67# ---- File: resources/programmers.jsonc
68# -----------------------------------------
69# -- Information about all the supported programmers
70# -- name, command to execute, arguments...
71PROGRAMMERS_JSONC = "programmers.jsonc"
73# -----------------------------------------
74# ---- File: resources/config.jsonc
75# -----------------------------------------
76# -- General config information.
77CONFIG_JSONC = "config.jsonc"
80@dataclass(frozen=True)
81class ApioDefinitions:
82 """Contains the apio definitions in the form of json dictionaries."""
84 # -- A json dir with the content of boards.jsonc
85 boards: dict
86 # -- A json dir with the content of fpgas.jsonc
87 fpgas: dict
88 # -- A json dir with the content of programmers.jsonc.
89 programmers: dict
91 def __post_init__(self):
92 """Assert that all fields initialized to actual values."""
93 assert self.boards
94 assert self.fpgas
95 assert self.programmers
98@dataclass(frozen=True)
99class EnvMutations:
100 """Contains mutations to the system env."""
102 # -- PATH items to add.
103 paths: List[str]
104 # -- Vars name/value pairs.
105 vars_list: List[Tuple[str, str]]
108class ProjectPolicy(Enum):
109 """Represents the possible context policies regarding loading apio.ini.
110 and project related information."""
112 # -- Project information is not loaded.
113 NO_PROJECT = 1
114 # -- Project information is loaded if apio.ini is found.
115 PROJECT_OPTIONAL = 2
116 # -- Apio.ini is required and project information must be loaded.
117 PROJECT_REQUIRED = 3
120class PackagesPolicy(Enum):
121 """Represents the possible context policies regarding loading apio.ini.
122 and project related information."""
124 # -- Do not change the package state, they may exist or not, updated or
125 # -- not. This policy requires project policy NO_PROJECT and with it,
126 # -- the definitions are not loaded.
127 IGNORE_PACKAGES = 1
128 # -- Normal policy, verify that the packages are installed correctly and
129 # -- update them if needed.
130 ENSURE_PACKAGES = 2
133class ApioContext:
134 """Apio context. Class for accessing apio resources and configurations."""
136 # pylint: disable=too-many-instance-attributes
138 # -- List of allowed instance vars.
139 __slots__ = (
140 "project_policy",
141 "apio_home_dir",
142 "apio_packages_dir",
143 "config",
144 "profile",
145 "platforms",
146 "platform_id",
147 "all_packages",
148 "required_packages",
149 "env_was_already_set",
150 "_project_dir",
151 "_project",
152 "_project_resources",
153 "_definitions",
154 )
156 def __init__(
157 self,
158 *,
159 project_policy: ProjectPolicy,
160 remote_config_policy: RemoteConfigPolicy,
161 packages_policy: PackagesPolicy,
162 project_dir_arg: Optional[Path] = None,
163 env_arg: Optional[str] = None,
164 report_env=True,
165 ):
166 """Initializes the ApioContext object.
168 'project_policy', 'config_policy', and 'packages_policy' are modifiers
169 that controls the initialization of the context.
171 'project_dir_arg' is an optional user specification of the project dir.
172 Must be None if project_policy is NO_PROJECT.
174 'env_arg' is an optional command line option value that select the
175 apio.ini env if the project is loaded. it makes sense only when
176 project_policy is PROJECT_REQUIRED (enforced by an assertion).
178 If an apio.ini project is loaded, the method prints to the user the
179 selected env and board, unless if report_env = False.
180 """
182 # pylint: disable=too-many-arguments
183 # pylint: disable=too-many-statements
184 # pylint: disable=too-many-locals
186 # -- Sanity check the policies.
187 assert isinstance(project_policy, ProjectPolicy)
188 assert isinstance(remote_config_policy, RemoteConfigPolicy)
189 assert isinstance(packages_policy, PackagesPolicy)
191 if packages_policy == PackagesPolicy.IGNORE_PACKAGES:
192 assert project_policy == ProjectPolicy.NO_PROJECT
194 # -- Inform as soon as possible about the list of apio env options
195 # -- that modify its default behavior.
196 defined_env_options = env_options.get_defined()
197 if defined_env_options: 197 ↛ 204line 197 didn't jump to line 204 because the condition on line 197 was always true
198 cout(
199 f"Active env options [{', '.join(defined_env_options)}].",
200 style=INFO,
201 )
203 # -- Store the project_policy
204 assert isinstance(
205 project_policy, ProjectPolicy
206 ), "Not an ApioContextScope"
207 self.project_policy = project_policy
209 # -- Sanity check, env_arg makes sense only when project_policy is
210 # -- PROJECT_REQUIRED.
211 if env_arg is not None:
212 assert project_policy == ProjectPolicy.PROJECT_REQUIRED
214 # -- A flag to indicate if the system env was already set in this
215 # -- apio session. Used to avoid multiple repeated settings that
216 # -- make the path longer and longer.
217 self.env_was_already_set = False
219 # -- Determine if we need to load the project, and if so, set
220 # -- self._project_dir to the project dir, otherwise, leave it None.
221 self._project_dir: Path = None
222 if project_policy == ProjectPolicy.PROJECT_REQUIRED:
223 self._project_dir = util.user_directory_or_cwd(
224 project_dir_arg, description="Project", must_exist=True
225 )
226 elif project_policy == ProjectPolicy.PROJECT_OPTIONAL:
227 project_dir = util.user_directory_or_cwd(
228 project_dir_arg, description="Project", must_exist=False
229 )
230 if (project_dir / "apio.ini").exists():
231 self._project_dir = project_dir
232 else:
233 assert (
234 project_policy == ProjectPolicy.NO_PROJECT
235 ), f"Unexpected project policy: {project_policy}"
236 assert (
237 project_dir_arg is None
238 ), "project_dir_arg specified for project policy None"
240 # -- Determine apio home and packages dirs
241 self.apio_home_dir: Path = util.resolve_home_dir()
242 self.apio_packages_dir: Path = util.resolve_packages_dir(
243 self.apio_home_dir
244 )
246 # -- Get the jsonc source dirs.
247 resources_dir = util.get_path_in_apio_package(RESOURCES_DIR)
249 # -- Read and validate the config information
250 self.config = self._load_resource(CONFIG_JSONC, resources_dir)
251 validate_config(self.config)
253 # -- Profile information, from ~/.apio/profile.json. We provide it with
254 # -- the remote config url template from distribution.jsonc such that
255 # -- can it fetch the remote config on demand.
256 remote_config_url = env_options.get(
257 env_options.APIO_REMOTE_CONFIG_URL,
258 default=self.config["remote-config-url"],
259 )
260 remote_config_ttl_days = self.config["remote-config-ttl-days"]
261 remote_config_retry_minutes = self.config[
262 "remote-config-retry-minutes"
263 ]
264 self.profile = Profile(
265 self.apio_home_dir,
266 self.apio_packages_dir,
267 remote_config_url,
268 remote_config_ttl_days,
269 remote_config_retry_minutes,
270 remote_config_policy,
271 )
273 # -- Read the platforms information.
274 self.platforms = self._load_resource(PLATFORMS_JSONC, resources_dir)
275 validate_platforms(self.platforms)
277 # -- Determine the platform_id for this APIO session.
278 self.platform_id = self._determine_platform_id(self.platforms)
280 # -- Read the apio packages information
281 self.all_packages = self._load_resource(PACKAGES_JSONC, resources_dir)
282 validate_packages(self.all_packages)
284 # -- Expand in place the env templates in all_packages.
285 ApioContext._resolve_package_envs(
286 self.all_packages, self.apio_packages_dir
287 )
289 # The subset of packages that are applicable to this platform.
290 self.required_packages = self._select_required_packages_for_platform(
291 self.all_packages, self.platform_id, self.platforms
292 )
294 # -- Case 1: IGNORE_PACKAGES
295 if packages_policy == PackagesPolicy.IGNORE_PACKAGES:
296 self._definitions = None
298 # -- Case 2: ENSURE_PACKAGES
299 else:
300 assert packages_policy == PackagesPolicy.ENSURE_PACKAGES
302 # -- Install missing packages. At this point, the fields that are
303 # -- required by self.packages_context are already initialized.
304 packages.install_missing_packages_on_the_fly(
305 self.packages_context, verbose=False
306 )
308 # -- Load the definitions from the definitions file with possible
309 # -- override by the optional project file.
310 definitions_dir = self.apio_packages_dir / "definitions"
311 boards = self._load_resource(
312 BOARDS_JSONC, definitions_dir, self._project_dir
313 )
314 fpgas = self._load_resource(
315 FPGAS_JSONC, definitions_dir, self._project_dir
316 )
317 programmers = self._load_resource(
318 PROGRAMMERS_JSONC, definitions_dir, self._project_dir
319 )
320 self._definitions = ApioDefinitions(boards, fpgas, programmers)
322 # -- If we determined that we need to load the project, load the
323 # -- apio.ini data.
324 self._project: Optional[Project] = None
325 self._project_resources: ProjectResources = None
327 if self._project_dir:
328 # -- Load the project object
329 self._project = load_project_from_file(
330 self._project_dir, env_arg, self.boards
331 )
332 assert self.has_project, "init(): project not loaded"
333 # -- Inform the user about the active env, if needed..
334 if report_env:
335 self.report_env()
336 # -- Collect and validate the project resources.
337 # -- The project is already validated to have the required "board.
338 self._project_resources = collect_project_resources(
339 self._project.get_str_option("board"),
340 self.boards,
341 self.fpgas,
342 self.programmers,
343 )
344 # -- Validate the project resources.
345 validate_project_resources(self._project_resources)
346 else:
347 assert not self.has_project, "init(): project loaded"
349 def report_env(self):
350 """Report to the user the env and board used. Asserts that the
351 project is loaded."""
352 # -- Do not call if project is not loaded.
353 assert self.has_project
355 # -- Env name string in color
356 styled_env_name = cstyle(self.project.env_name, style=EMPH1)
358 # -- Board id string in color
359 styled_board_id = cstyle(
360 self.project.env_options["board"], style=EMPH1
361 )
363 # -- Report.
364 cout(f"Using env {styled_env_name} ({styled_board_id})")
366 @property
367 def has_project(self):
368 """Returns True if the project is loaded."""
369 return self._project is not None
371 @property
372 def project_dir(self):
373 """Returns the project dir. Should be called only if has_project_loaded
374 is true."""
375 assert self.has_project, "project_dir(): project is not loaded"
376 assert self._project_dir, "project_dir(): missing value."
377 return self._project_dir
379 @property
380 def project(self) -> Project:
381 """Return the project. Should be called only if has_project() is
382 True."""
383 # -- Failure here is a programming error, not a user error.
384 assert self.has_project, "project(): project is not loaded"
385 return self._project
387 @property
388 def project_resources(self) -> ProjectResources:
389 """Return the project resources. Should be called only if
390 has_project() is True."""
391 # -- Failure here is a programming error, not a user error.
392 assert self.has_project, "project(): project is not loaded"
393 return self._project_resources
395 @property
396 def definitions(self) -> ApioDefinitions:
397 """Return apio definitions."""
398 assert self._definitions, "Apio context as no definitions"
399 return self._definitions
401 @property
402 def boards(self) -> dict:
403 """Returns the apio board definitions"""
404 return self.definitions.boards
406 @property
407 def fpgas(self) -> dict:
408 """Returns the apio fpgas definitions"""
409 return self.definitions.fpgas
411 @property
412 def programmers(self) -> dict:
413 """Returns the apio programmers definitions"""
414 return self.definitions.programmers
416 @property
417 def env_build_path(self) -> str:
418 """Returns the relative path of the current env build directory from
419 the project dir. Should be called only when has_project is True."""
420 assert self.has_project, "project(): project is not loaded"
421 return env_build_path(self.project.env_name)
423 def _load_resource(
424 self, name: str, standard_dir: Path, custom_dir: Optional[Path] = None
425 ) -> dict:
426 """Load a jsonc file. Try first from custom_dir, if given, and then
427 from standard dir. This method is called for resource files in
428 apio/resources and definitions files in the definitions packages.
429 """
430 # -- First try to load from custom dir.
431 if custom_dir:
432 filepath = custom_dir / name
433 if filepath.exists():
434 cout(f"Loading custom '{name}'.")
435 return self._load_resource_file(filepath)
437 # -- Else, load from the default dir.
438 filepath = standard_dir / name
439 return self._load_resource_file(filepath)
441 @staticmethod
442 def _load_resource_file(filepath: Path) -> dict:
443 """Load the resources from a given jsonc file path
444 * OUTPUT: A dictionary with the jsonc file data
445 In case of error it raises an exception and finish
446 """
448 # -- Read the jsonc file
449 try:
450 with filepath.open(encoding="utf8") as file:
452 # -- Read the json with comments file
453 data_jsonc = file.read()
455 # -- The jsonc file NOT FOUND! This is an apio system error
456 # -- It should never occur unless there is a bug in the
457 # -- apio system files, or a bug when calling this function
458 # -- passing a wrong file
459 except FileNotFoundError as exc:
461 # -- Display error information
462 cerror("[Internal] .jsonc file not found", f"{exc}")
464 # -- Abort!
465 sys.exit(1)
467 # -- Convert the jsonc to json by removing '//' comments.
468 data_json = jsonc.to_json(data_jsonc)
470 # -- Parse the json format!
471 try:
472 resource = json.loads(data_json)
474 # -- Invalid json format! This is an apio system error
475 # -- It should never occur unless a developer has
476 # -- made a mistake when changing the jsonc file
477 except json.decoder.JSONDecodeError as exc:
479 # -- Display Main error
480 cerror("Invalid .jsonc file", f"{exc}")
481 cout(f"File: {filepath}", style=INFO)
483 # -- Abort!
484 sys.exit(1)
486 # -- Return the object for the resource
487 return resource
489 @staticmethod
490 def _expand_env_template(template: str, package_path: Path) -> str:
491 """Fills a packages env value template as they appear in
492 packages.jsonc. Currently it recognizes only a single place holder
493 '%p' representing the package absolute path. The '%p" can appear only
494 at the beginning of the template.
496 E.g. '%p/bin' -> '/users/user/.apio/packages/drivers/bin'
498 NOTE: This format is very basic but is sufficient for the current
499 needs. If needed, extend or modify it.
500 """
502 # Case 1: No place holder.
503 if "%p" not in template: 503 ↛ 504line 503 didn't jump to line 504 because the condition on line 503 was never true
504 return template
506 # Case 2: The template contains only the placeholder.
507 if template == "%p": 507 ↛ 508line 507 didn't jump to line 508 because the condition on line 507 was never true
508 return str(package_path)
510 # Case 3: The place holder is the prefix of the template's path.
511 if template.startswith("%p/"): 511 ↛ 515line 511 didn't jump to line 515 because the condition on line 511 was always true
512 return str(package_path / template[3:])
514 # Case 4: Unsupported.
515 raise RuntimeError(f"Invalid env template: [{template}]")
517 @staticmethod
518 def _resolve_package_envs(
519 packages_: Dict[str, Dict], packages_dir: Path
520 ) -> None:
521 """Resolve in place the path and var value templates in the
522 given packages dictionary. For example, %p is replaced with
523 the package's absolute path."""
525 for package_name, package_config in packages_.items():
527 # -- Get the package root dir.
528 package_path = packages_dir / package_name
530 # -- Get the json 'env' section. We require it, even if empty,
531 # -- for clarity reasons.
532 assert "env" in package_config
533 package_env = package_config["env"]
535 # -- Expand the values in the "path" section, if any.
536 path_section = package_env.get("path", [])
537 for i, path_template in enumerate(path_section):
538 path_section[i] = ApioContext._expand_env_template(
539 path_template, package_path
540 )
542 # -- Expand the values in the "vars" section, if any.
543 vars_section = package_env.get("vars", {})
544 for var_name, val_template in vars_section.items():
545 vars_section[var_name] = ApioContext._expand_env_template(
546 val_template, package_path
547 )
549 def get_required_package_info(self, package_name: str) -> str:
550 """Returns the information of the package with given name.
551 The information is a JSON dict originated at packages.json().
552 Exits with an error message if the package is not defined.
553 """
554 package_info = self.required_packages.get(package_name, None)
555 if package_info is None: 555 ↛ 556line 555 didn't jump to line 556 because the condition on line 555 was never true
556 cerror(f"Unknown package '{package_name}'")
557 sys.exit(1)
559 return package_info
561 def get_package_dir(self, package_name: str) -> Path:
562 """Returns the root path of a package with given name."""
564 return self.apio_packages_dir / package_name
566 def get_tmp_dir(self, create: bool = True) -> Path:
567 """Return the tmp dir under the apio home dir. If 'create' is true
568 create the dir and its parents if they do not exist."""
569 tmp_dir = self.apio_home_dir / "tmp"
570 if create:
571 tmp_dir.mkdir(parents=True, exist_ok=True)
572 return tmp_dir
574 @staticmethod
575 def _determine_platform_id(platforms: Dict[str, Dict]) -> str:
576 """Determines and returns the platform io based on system info and
577 optional override."""
578 # -- Use override and get from the underlying system.
579 platform_id_override = env_options.get(env_options.APIO_PLATFORM)
580 if platform_id_override: 580 ↛ 581line 580 didn't jump to line 581 because the condition on line 580 was never true
581 platform_id = platform_id_override
582 else:
583 platform_id = ApioContext._get_system_platform_id()
585 # Stick to the naming conventions we use for boards, fpgas, etc.
586 platform_id = platform_id.replace("_", "-")
588 # -- Verify it's valid. This can be a user error if the override
589 # -- is invalid.
590 if platform_id not in platforms.keys(): 590 ↛ 591line 590 didn't jump to line 591 because the condition on line 590 was never true
591 cerror(f"Unknown platform id: [{platform_id}]")
592 cout(
593 "For the list of supported platforms "
594 "type 'apio system platforms'.",
595 style=INFO,
596 )
597 sys.exit(1)
599 # -- All done ok.
600 return platform_id
602 @property
603 def packages_context(self) -> PackagesContext:
604 """Return a PackagesContext with info extracted from this
605 ApioContext."""
606 return PackagesContext(
607 profile=self.profile,
608 required_packages=self.required_packages,
609 platform_id=self.platform_id,
610 packages_dir=self.apio_packages_dir,
611 )
613 @staticmethod
614 def _select_required_packages_for_platform(
615 all_packages: Dict[str, Dict],
616 platform_id: str,
617 platforms: Dict[str, Dict],
618 ) -> Dict:
619 """Given a dictionary with the packages.jsonc packages infos,
620 returns subset dictionary with packages that are available for
621 'platform_id'.
622 """
624 # -- If fails, this is a programming error.
625 assert platform_id in platforms, platform
627 # -- Final dict with the output packages
628 filtered_packages = {}
630 # -- Check all the packages
631 for package_name in all_packages.keys():
633 # -- Get the package info.
634 package_info = all_packages[package_name]
636 # -- Get the list of platforms ids on which this package is
637 # -- available. The package is available on all platforms unless
638 # -- restricted by the ""restricted-to-platforms" field.
639 required_for_platforms = package_info.get(
640 "restricted-to-platforms", platforms.keys()
641 )
643 # -- Sanity check that all platform ids are valid. If fails it's
644 # -- a programming error.
645 for p in required_for_platforms:
646 assert p in platforms.keys(), platform
648 # -- If available for 'platform_id', add it.
649 if platform_id in required_for_platforms:
650 filtered_packages[package_name] = all_packages[package_name]
652 # -- Return the subset dict with the packages for 'platform_id'.
653 return filtered_packages
655 @staticmethod
656 def _get_system_platform_id() -> str:
657 """Return a String with the current platform:
658 ex. linux-x86-64
659 ex. windows-amd64"""
661 # -- Get the platform: linux, windows, darwin
662 type_ = platform.system().lower()
663 platform_str = f"{type_}"
665 # -- Get the architecture
666 arch = platform.machine().lower()
668 # -- Special case for windows
669 if type_ == "windows": 669 ↛ 671line 669 didn't jump to line 671 because the condition on line 669 was never true
670 # -- Assume all the windows to be 64-bits
671 arch = "amd64"
673 # -- Add the architecture, if it exists
674 if arch: 674 ↛ 678line 674 didn't jump to line 678 because the condition on line 674 was always true
675 platform_str += f"_{arch}"
677 # -- Return the full platform
678 return platform_str
680 @property
681 def is_linux(self) -> bool:
682 """Returns True iff platform_id indicates linux."""
683 return "linux" in self.platform_id
685 @property
686 def is_darwin(self) -> bool:
687 """Returns True iff platform_id indicates Mac OSX."""
688 return "darwin" in self.platform_id
690 @property
691 def is_windows(self) -> bool:
692 """Returns True iff platform_id indicates windows."""
693 return "windows" in self.platform_id
695 def _get_env_mutations_for_packages(self) -> EnvMutations:
696 """Collects the env mutation for each of the defined packages,
697 in the order they are defined."""
699 result = EnvMutations([], [])
700 for _, package_config in self.required_packages.items():
701 # -- Get the json 'env' section. We require it, even if it's empty,
702 # -- for clarity reasons.
703 assert "env" in package_config
704 package_env = package_config["env"]
706 # -- Collect the path values.
707 path_list = package_env.get("path", [])
708 result.paths.extend(path_list)
710 # -- Collect the env vars (name, value) pairs.
711 vars_section = package_env.get("vars", {})
712 for var_name, var_value in vars_section.items():
713 result.vars_list.append((var_name, var_value))
715 return result
717 def _dump_env_mutations(self, mutations: EnvMutations) -> None:
718 """Dumps a user friendly representation of the env mutations."""
719 cout("Environment settings:", style=EMPH3)
721 # -- Print PATH mutations.
722 windows = self.is_windows
723 for p in reversed(mutations.paths):
724 styled_name = cstyle("PATH", style=EMPH3)
725 if windows: 725 ↛ 726line 725 didn't jump to line 726 because the condition on line 725 was never true
726 cout(f"set {styled_name}={p};%PATH%")
727 else:
728 cout(f'{styled_name}="{p}:$PATH"')
730 # -- Print vars mutations.
731 for name, val in mutations.vars_list:
732 styled_name = cstyle(name, style=EMPH3)
733 if windows: 733 ↛ 734line 733 didn't jump to line 734 because the condition on line 733 was never true
734 cout(f"set {styled_name}={val}")
735 else:
736 cout(f'{styled_name}="{val}"')
738 def _apply_env_mutations(self, mutations: EnvMutations) -> None:
739 """Apply a given set of env mutations, while preserving their order."""
741 # -- Apply the path mutations, while preserving order.
742 old_val = os.environ["PATH"]
743 items = mutations.paths + [old_val]
744 new_val = os.pathsep.join(items)
745 os.environ["PATH"] = new_val
747 # -- Apply the vars mutations, while preserving order.
748 for name, value in mutations.vars_list:
749 os.environ[name] = value
751 def set_env_for_packages(
752 self, *, quiet: bool = False, verbose: bool = False
753 ) -> None:
754 """Sets the environment variables for using all the that are
755 available for this platform, even if currently not installed.
757 The function sets the environment only on first call and in latter
758 calls skips the operation silently.
760 If quite is set, no output is printed. When verbose is set, additional
761 output such as the env vars mutations are printed, otherwise, a minimal
762 information is printed to make the user aware that they commands they
763 see are executed in a modified env settings.
764 """
766 # -- If this fails, this is a programming error. Quiet and verbose
767 # -- cannot be combined.
768 assert not (quiet and verbose), "Can't have both quite and verbose."
770 # -- Collect the env mutations for all packages.
771 mutations = self._get_env_mutations_for_packages()
773 if verbose:
774 self._dump_env_mutations(mutations)
776 # -- If this is the first call in this apio invocation, apply the
777 # -- mutations. These mutations are temporary for the lifetime of this
778 # -- process and does not affect the user's shell environment.
779 # -- The mutations are also inherited by child processes such as the
780 # -- scons processes.
781 if not self.env_was_already_set: 781 ↛ exitline 781 didn't return from function 'set_env_for_packages' because the condition on line 781 was always true
782 self._apply_env_mutations(mutations)
783 self.env_was_already_set = True
784 if not verbose and not quiet:
785 cout("Setting shell vars.")