Coverage for apio/scons/apio_env.py: 80%
73 statements
« prev ^ index » next coverage.py v7.17.2, created at 2026-11-07 05:08 +0000
« prev ^ index » next coverage.py v7.17.2, created at 2026-11-07 05:08 +0000
1# This file is part of the Apio project
2# (C) 2016-present FPGAwars
3# Project authors:
4# * Jesús Arroyo (Jesus89) (2016-2019)
5# * Juan Gonzalez (obijuan) (2019-2024)
6# * The Apio Team (2024-present)
7# License: GPLv2
9"""A class with common services for the apio scons handlers."""
11import os
12from typing import Any
13from SCons.Script.SConscript import SConsEnvironment
14from SCons.Environment import BuilderWrapper
15import SCons.Defaults
16from apio.common import apio_platforms, subprocess_env
17from apio.common.debug_util import is_debug
18from apio.common.apio_console import cout
19from apio.common.apio_styles import EMPH3
20from apio.common.common_util import env_build_path, dump_env
21from apio.common.proto.stubs.apio_scons_pb2 import SconsParams
24class ApioEnv:
25 """Provides abstracted scons env and other user services."""
27 def __init__(
28 self,
29 command_line_targets: list[str],
30 scons_params: SconsParams,
31 ):
32 # -- Save the arguments.
33 self.command_line_targets = command_line_targets
34 self.params = scons_params
36 # -- Map platform id to an ApioPlatform object.
37 self.apio_platform = apio_platforms.get_apio_platforms()[
38 self.params.environment.platform_id
39 ]
41 # -- Create the base target.
42 self.target = str(self.env_build_path / "hardware")
44 # -- Create the target for the graph files (.dot, .svg, etc)
45 self.graph_target = str(self.env_build_path / "graph")
47 # -- Initialized the scons default environment with no tools even
48 # -- though we don't use it. This is to avoid the issue reported
49 # -- at https://github.com/FPGAwars/apio/issues/802 in which scons
50 # -- triggers a gcc installation dialog box on MacOs.
51 #
52 # -- Note that DefaultEnvironment is a funny function that replaces
53 # -- itself with _fetch_DefaultEnvironment() after the first call.
54 # SCons.Defaults.DefaultEnvironment(ENV=os.environ, tools=[])
55 SCons.Defaults.DefaultEnvironment(tools=[])
57 # -- Compute tools env.
58 tools_env = os.environ.copy()
60 if is_debug(2): 60 ↛ 61line 60 didn't jump to line 61 because the condition on line 60 was never true
61 dump_env("*** Original scons env:", tools_env)
63 subprocess_env.apply_env_mutations(
64 self.params.environment.tools_env_mutations, tools_env
65 )
67 if is_debug(2): 67 ↛ 68line 67 didn't jump to line 68 because the condition on line 67 was never true
68 dump_env("*** Tools env:", tools_env)
70 # -- Create the underlying scons env with the tools env.
71 self.scons_env = SConsEnvironment(ENV=tools_env, tools=[])
73 # -- Sanity checks
74 assert self.scons_env is not SCons.Defaults.DefaultEnvironment()
75 assert SCons.Defaults.DefaultEnvironment()["TOOLS"] == []
76 assert self.scons_env["TOOLS"] == []
78 # -- Set the location of the scons incremental build database.
79 # -- By default it would be stored in project root dir.
80 self.scons_env.SConsignFile(
81 self.env_build_path.absolute() / "sconsign.dblite"
82 )
84 # Extra info for debugging.
85 if is_debug(2): 85 ↛ 86line 85 didn't jump to line 86 because the condition on line 85 was never true
86 cout(f"command_line_targets: {command_line_targets}")
87 self.dump_scons_env_vars()
89 @property
90 def env_name(self):
91 """Return the action apio env name for this invocation."""
92 return self.params.apio_env_params.env_name
94 @property
95 def env_build_path(self):
96 """Returns a relative path from the project dir to the env build
97 dir."""
98 return env_build_path(self.env_name)
100 @property
101 def scons_shell_id(self):
102 """Returns the shell id that scons is expected to use.."""
103 return self.params.environment.scons_shell_id
105 def targeting_one_of(self, *target_names) -> bool:
106 """Returns true if the any of the named target was specified in the
107 scons command line."""
108 for target_name in target_names:
109 if target_name in self.command_line_targets:
110 return True
111 return False
113 def add_builder(self, builder_id: str, builder):
114 """Append to the scons env a builder with given id. The env
115 adds it to the BUILDERS dict and also adds to itself an attribute with
116 that name that contains a wrapper to that builder."""
117 self.scons_env.Append(BUILDERS={builder_id: builder})
119 def add_builder_target(
120 self,
121 *,
122 builder_id: str,
123 target,
124 sources: list[Any],
125 extra_dependencies: list | None = None,
126 always_build: bool = False,
127 ):
128 """Creates an return a target that uses the builder with given id."""
130 # pylint: disable=too-many-arguments
132 # -- Scons wraps the builder with a wrapper. We use it to create the
133 # -- new target.
134 builder_wrapper: BuilderWrapper = getattr(self.scons_env, builder_id)
135 target = builder_wrapper(target, sources)
136 # -- Mark as 'always build' if requested.
137 if always_build:
138 self.scons_env.AlwaysBuild(target)
139 # -- Add extra dependencies, if any.
140 if extra_dependencies:
141 for dependency in extra_dependencies:
142 self.scons_env.Depends(target, dependency)
143 return target
145 def add_alias(
146 self, name, *, source, action=None, always_build: bool = False
147 ):
148 """Creates a target with given dependencies"""
149 target = self.scons_env.Alias(name, source, action)
150 if always_build:
151 self.scons_env.AlwaysBuild(target)
152 return target
154 def dump_scons_env_vars(self) -> None:
155 """Prints a list of the environment variables of the scons environment.
156 This can be different from the environment of this Apio scons
157 process. For debugging."""
158 sc = self.scons_env
159 dictionary: dict = sc.Dictionary()
160 keys = list(dictionary.keys())
161 keys.sort()
162 cout("")
163 cout(">>> Env vars BEGIN", style=EMPH3)
164 for key in keys:
165 cout(f"{key} = {self.scons_env[key]}")
166 cout("<<< Env vars END\n", style=EMPH3)