Coverage for apio/scons/apio_env.py: 98%

69 statements  

« prev     ^ index     » next       coverage.py v7.14.3, created at 2026-06-24 03:51 +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.""" 

11 

12import os 

13from typing import List, Optional, Any 

14from SCons.Script.SConscript import SConsEnvironment 

15from SCons.Environment import BuilderWrapper 

16import SCons.Defaults 

17from apio.common.apio_console import cout 

18from apio.common.apio_styles import EMPH3 

19from apio.common.common_util import env_build_path 

20from apio.common.proto.apio_pb2 import SconsParams 

21 

22 

23class ApioEnv: 

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

25 

26 def __init__( 

27 self, 

28 command_line_targets: List[str], 

29 scons_params: SconsParams, 

30 ): 

31 # -- Save the arguments. 

32 self.command_line_targets = command_line_targets 

33 self.params = scons_params 

34 

35 # -- Create the base target. 

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

37 

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

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

40 

41 # -- Initialized the scons default environment with no tools even 

42 # -- though we don't use it. This is to avoid the issue reported 

43 # -- at https://github.com/FPGAwars/apio/issues/802 in which scons 

44 # -- triggers a gcc installation dialog box on MacOs. 

45 # 

46 # -- Note that DefaultEnvironment is a funny function that replaces 

47 # -- itself with _fetch_DefaultEnvironment() after the first call. 

48 SCons.Defaults.DefaultEnvironment(ENV=os.environ, tools=[]) 

49 

50 # -- Create the underlying scons env. 

51 self.scons_env = SConsEnvironment(ENV=os.environ, tools=[]) 

52 

53 # -- Set the location of the scons incremental build database. 

54 # -- By default it would be stored in project root dir. 

55 self.scons_env.SConsignFile( 

56 self.env_build_path.absolute() / "sconsign.dblite" 

57 ) 

58 

59 # Extra info for debugging. 

60 if self.is_debug(2): 

61 cout(f"command_line_targets: {command_line_targets}") 

62 self.dump_env_vars() 

63 

64 @property 

65 def env_name(self): 

66 """Return the action apio env name for this invocation.""" 

67 return self.params.apio_env_params.env_name 

68 

69 @property 

70 def env_build_path(self): 

71 """Returns a relative path from the project dir to the env build 

72 dir.""" 

73 return env_build_path(self.env_name) 

74 

75 @property 

76 def is_windows(self): 

77 """Returns True if we run on windows.""" 

78 return self.params.environment.is_windows 

79 

80 def is_debug(self, level: int): 

81 """Returns true if we run in debug mode.""" 

82 return self.params.environment.debug_level >= level 

83 

84 @property 

85 def platform_id(self): 

86 """Returns the platform id.""" 

87 return self.params.environment.platform_id 

88 

89 @property 

90 def scons_shell_id(self): 

91 """Returns the shell id that scons is expected to use..""" 

92 return self.params.environment.scons_shell_id 

93 

94 def targeting_one_of(self, *target_names) -> bool: 

95 """Returns true if the any of the named target was specified in the 

96 scons command line.""" 

97 for target_name in target_names: 

98 if target_name in self.command_line_targets: 

99 return True 

100 return False 

101 

102 def builder(self, builder_id: str, builder): 

103 """Append to the scons env a builder with given id. The env 

104 adds it to the BUILDERS dict and also adds to itself an attribute with 

105 that name that contains a wrapper to that builder.""" 

106 self.scons_env.Append(BUILDERS={builder_id: builder}) 

107 

108 def builder_target( 

109 self, 

110 *, 

111 builder_id: str, 

112 target, 

113 sources: List[Any], 

114 extra_dependencies: Optional[List] = None, 

115 always_build: bool = False, 

116 ): 

117 """Creates an return a target that uses the builder with given id.""" 

118 

119 # pylint: disable=too-many-arguments 

120 

121 # -- Scons wraps the builder with a wrapper. We use it to create the 

122 # -- new target. 

123 builder_wrapper: BuilderWrapper = getattr(self.scons_env, builder_id) 

124 target = builder_wrapper( 

125 target, sources # pyright: ignore[reportArgumentType] 

126 ) 

127 # -- Mark as 'always build' if requested. 

128 if always_build: 

129 self.scons_env.AlwaysBuild(target) 

130 # -- Add extra dependencies, if any. 

131 if extra_dependencies: 

132 for dependency in extra_dependencies: 

133 self.scons_env.Depends(target, dependency) 

134 return target 

135 

136 def alias(self, name, *, source, action=None, always_build: bool = False): 

137 """Creates a target with given dependencies""" 

138 target = self.scons_env.Alias(name, source, action) 

139 if always_build: 

140 self.scons_env.AlwaysBuild(target) 

141 return target 

142 

143 def dump_env_vars(self) -> None: 

144 """Prints a list of the environment variables. For debugging.""" 

145 sc = self.scons_env 

146 dictionary: dict = ( 

147 sc.Dictionary() # pyright: ignore[reportAssignmentType] 

148 ) 

149 keys = list(dictionary.keys()) 

150 keys.sort() 

151 cout("") 

152 cout(">>> Env vars BEGIN", style=EMPH3) 

153 for key in keys: 

154 cout(f"{key} = {self.scons_env[key]}") 

155 cout("<<< Env vars END\n", style=EMPH3)