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

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 

8 

9"""A class with common services for the apio scons handlers.""" 

10 

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 

22 

23 

24class ApioEnv: 

25 """Provides abstracted scons env and other user services.""" 

26 

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 

35 

36 # -- Map platform id to an ApioPlatform object. 

37 self.apio_platform = apio_platforms.get_apio_platforms()[ 

38 self.params.environment.platform_id 

39 ] 

40 

41 # -- Create the base target. 

42 self.target = str(self.env_build_path / "hardware") 

43 

44 # -- Create the target for the graph files (.dot, .svg, etc) 

45 self.graph_target = str(self.env_build_path / "graph") 

46 

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=[]) 

56 

57 # -- Compute tools env. 

58 tools_env = os.environ.copy() 

59 

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) 

62 

63 subprocess_env.apply_env_mutations( 

64 self.params.environment.tools_env_mutations, tools_env 

65 ) 

66 

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) 

69 

70 # -- Create the underlying scons env with the tools env. 

71 self.scons_env = SConsEnvironment(ENV=tools_env, tools=[]) 

72 

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"] == [] 

77 

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 ) 

83 

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() 

88 

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 

93 

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) 

99 

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 

104 

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 

112 

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}) 

118 

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.""" 

129 

130 # pylint: disable=too-many-arguments 

131 

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 

144 

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 

153 

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)