Coverage for apio / scons / apio_env.py: 98%
68 statements
« prev ^ index » next coverage.py v7.13.3, created at 2026-02-08 02:47 +0000
« prev ^ index » next coverage.py v7.13.3, created at 2026-02-08 02:47 +0000
1# -*- coding: utf-8 -*-
2# -- This file is part of the Apio project
3# -- (C) 2016-2018 FPGAwars
4# -- Author Jesús Arroyo
5# -- License GPLv2
6# -- Derived from:
7# ---- Platformio project
8# ---- (C) 2014-2016 Ivan Kravets <me@ikravets.com>
9# ---- License Apache v2
10"""A class with common services for the apio scons handlers."""
13import os
14from typing import List, Optional
15from SCons.Script.SConscript import SConsEnvironment
16from SCons.Environment import BuilderWrapper
17import SCons.Defaults
18from apio.common.apio_console import cout
19from apio.common.apio_styles import EMPH3
20from apio.common.common_util import env_build_path
21from apio.common.proto.apio_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 # -- Create the base target.
37 self.target = str(self.env_build_path / "hardware")
39 # -- Create the target for the graph files (.dot, .svg, etc)
40 self.graph_target = str(self.env_build_path / "graph")
42 # -- Initialized the scons default environment with no tools even
43 # -- though we don't use it. This is to avoid the issue reported
44 # -- at https://github.com/FPGAwars/apio/issues/802 in which scons
45 # -- triggers a gcc installation dialog box on MacOs.
46 #
47 # -- Note that DefaultEnvironment is a funny function that replaces
48 # -- itself with _fetch_DefaultEnvironment() after the first call.
49 SCons.Defaults.DefaultEnvironment(ENV=os.environ, tools=[])
51 # -- Create the underlying scons env.
52 self.scons_env = SConsEnvironment(ENV=os.environ, tools=[])
54 # -- Set the location of the scons incremental build database.
55 # -- By default it would be stored in project root dir.
56 self.scons_env.SConsignFile(
57 self.env_build_path.absolute() / "sconsign.dblite"
58 )
60 # Extra info for debugging.
61 if self.is_debug(2):
62 cout(f"command_line_targets: {command_line_targets}")
63 self.dump_env_vars()
65 @property
66 def env_name(self):
67 """Return the action apio env name for this invocation."""
68 return self.params.apio_env_params.env_name
70 @property
71 def env_build_path(self):
72 """Returns a relative path from the project dir to the env build
73 dir."""
74 return env_build_path(self.env_name)
76 @property
77 def is_windows(self):
78 """Returns True if we run on windows."""
79 return self.params.environment.is_windows
81 def is_debug(self, level: int):
82 """Returns true if we run in debug mode."""
83 return self.params.environment.debug_level >= level
85 @property
86 def platform_id(self):
87 """Returns the platform id."""
88 return self.params.environment.platform_id
90 @property
91 def scons_shell_id(self):
92 """Returns the shell id that scons is expected to use.."""
93 return self.params.environment.scons_shell_id
95 def targeting_one_of(self, *target_names) -> bool:
96 """Returns true if the any of the named target was specified in the
97 scons command line."""
98 for target_name in target_names:
99 if target_name in self.command_line_targets:
100 return True
101 return False
103 def builder(self, builder_id: str, builder):
104 """Append to the scons env a builder with given id. The env
105 adds it to the BUILDERS dict and also adds to itself an attribute with
106 that name that contains a wrapper to that builder."""
107 self.scons_env.Append(BUILDERS={builder_id: builder})
109 def builder_target(
110 self,
111 *,
112 builder_id: str,
113 target,
114 sources: List,
115 extra_dependencies: Optional[List] = None,
116 always_build: bool = False,
117 ):
118 """Creates an return a target that uses the builder with given id."""
120 # pylint: disable=too-many-arguments
122 # -- Scons wraps the builder with a wrapper. We use it to create the
123 # -- new target.
124 builder_wrapper: BuilderWrapper = getattr(self.scons_env, builder_id)
125 target = builder_wrapper(target, sources)
126 # -- Mark as 'always build' if requested.
127 if always_build:
128 self.scons_env.AlwaysBuild(target)
129 # -- Add extra dependencies, if any.
130 if extra_dependencies:
131 for dependency in extra_dependencies:
132 self.scons_env.Depends(target, dependency)
133 return target
135 def alias(self, name, *, source, action=None, always_build: bool = False):
136 """Creates a target with given dependencies"""
137 target = self.scons_env.Alias(name, source, action)
138 if always_build:
139 self.scons_env.AlwaysBuild(target)
140 return target
142 def dump_env_vars(self) -> None:
143 """Prints a list of the environment variables. For debugging."""
144 dictionary = self.scons_env.Dictionary()
145 keys = list(dictionary.keys())
146 keys.sort()
147 cout("")
148 cout(">>> Env vars BEGIN", style=EMPH3)
149 for key in keys:
150 cout(f"{key} = {self.scons_env[key]}")
151 cout("<<< Env vars END\n", style=EMPH3)