1
+ # MIT License
2
+ #
3
+ # Copyright The SCons Foundation
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining
6
+ # a copy of this software and associated documentation files (the
7
+ # "Software"), to deal in the Software without restriction, including
8
+ # without limitation the rights to use, copy, modify, merge, publish,
9
+ # distribute, sublicense, and/or sell copies of the Software, and to
10
+ # permit persons to whom the Software is furnished to do so, subject to
11
+ # the following conditions:
12
+ #
13
+ # The above copyright notice and this permission notice shall be included
14
+ # in all copies or substantial portions of the Software.
15
+ #
16
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
17
+ # KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
18
+ # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23
+
24
+ import os
25
+ import shlex
26
+ import textwrap
27
+
28
+ import SCons
29
+ from SCons .Tool .ninja import NINJA_CUSTOM_HANDLERS , NINJA_RULES , NINJA_POOLS
30
+ from SCons .Tool .ninja .Globals import __NINJA_RULE_MAPPING
31
+ from SCons .Tool .ninja .Utils import get_targets_sources , get_dependencies , get_order_only , get_outputs , get_inputs , \
32
+ get_rule , get_path , generate_command , get_command_env , get_comstr
33
+
34
+
35
+ def register_custom_handler (env , name , handler ):
36
+ """Register a custom handler for SCons function actions."""
37
+ env [NINJA_CUSTOM_HANDLERS ][name ] = handler
38
+
39
+
40
+ def register_custom_rule_mapping (env , pre_subst_string , rule ):
41
+ """Register a function to call for a given rule."""
42
+ SCons .Tool .ninja .Globals .__NINJA_RULE_MAPPING [pre_subst_string ] = rule
43
+
44
+
45
+ def register_custom_rule (env , rule , command , description = "" , deps = None , pool = None , use_depfile = False , use_response_file = False , response_file_content = "$rspc" ):
46
+ """Allows specification of Ninja rules from inside SCons files."""
47
+ rule_obj = {
48
+ "command" : command ,
49
+ "description" : description if description else "{} $out" .format (rule ),
50
+ }
51
+
52
+ if use_depfile :
53
+ rule_obj ["depfile" ] = os .path .join (get_path (env ['NINJA_DIR' ]), '$out.depfile' )
54
+
55
+ if deps is not None :
56
+ rule_obj ["deps" ] = deps
57
+
58
+ if pool is not None :
59
+ rule_obj ["pool" ] = pool
60
+
61
+ if use_response_file :
62
+ rule_obj ["rspfile" ] = "$out.rsp"
63
+ rule_obj ["rspfile_content" ] = response_file_content
64
+
65
+ env [NINJA_RULES ][rule ] = rule_obj
66
+
67
+
68
+ def register_custom_pool (env , pool , size ):
69
+ """Allows the creation of custom Ninja pools"""
70
+ env [NINJA_POOLS ][pool ] = size
71
+
72
+
73
+ def set_build_node_callback (env , node , callback ):
74
+ if not node .is_conftest ():
75
+ node .attributes .ninja_build_callback = callback
76
+
77
+
78
+ def get_generic_shell_command (env , node , action , targets , sources , executor = None ):
79
+ return (
80
+ "GENERATED_CMD" ,
81
+ {
82
+ "cmd" : generate_command (env , node , action , targets , sources , executor = executor ),
83
+ "env" : get_command_env (env ),
84
+ },
85
+ # Since this function is a rule mapping provider, it must return a list of dependencies,
86
+ # and usually this would be the path to a tool, such as a compiler, used for this rule.
87
+ # However this function is to generic to be able to reliably extract such deps
88
+ # from the command, so we return a placeholder empty list. It should be noted that
89
+ # generally this function will not be used solely and is more like a template to generate
90
+ # the basics for a custom provider which may have more specific options for a provider
91
+ # function for a custom NinjaRuleMapping.
92
+ []
93
+ )
94
+
95
+
96
+ def CheckNinjaCompdbExpand (env , context ):
97
+ """ Configure check testing if ninja's compdb can expand response files"""
98
+
99
+ # TODO: When would this be false?
100
+ context .Message ('Checking if ninja compdb can expand response files... ' )
101
+ ret , output = context .TryAction (
102
+ action = 'ninja -f $SOURCE -t compdb -x CMD_RSP > $TARGET' ,
103
+ extension = '.ninja' ,
104
+ text = textwrap .dedent ("""
105
+ rule CMD_RSP
106
+ command = $cmd @$out.rsp > fake_output.txt
107
+ description = Building $out
108
+ rspfile = $out.rsp
109
+ rspfile_content = $rspc
110
+ build fake_output.txt: CMD_RSP fake_input.txt
111
+ cmd = echo
112
+ pool = console
113
+ rspc = "test"
114
+ """ ))
115
+ result = '@fake_output.txt.rsp' not in output
116
+ context .Result (result )
117
+ return result
118
+
119
+
120
+ def get_command (env , node , action ): # pylint: disable=too-many-branches
121
+ """Get the command to execute for node."""
122
+ if node .env :
123
+ sub_env = node .env
124
+ else :
125
+ sub_env = env
126
+ executor = node .get_executor ()
127
+ tlist , slist = get_targets_sources (node )
128
+
129
+ # Generate a real CommandAction
130
+ if isinstance (action , SCons .Action .CommandGeneratorAction ):
131
+ # pylint: disable=protected-access
132
+ action = action ._generate (tlist , slist , sub_env , 1 , executor = executor )
133
+
134
+ variables = {}
135
+
136
+ comstr = get_comstr (sub_env , action , tlist , slist )
137
+ if not comstr :
138
+ return None
139
+
140
+ provider = __NINJA_RULE_MAPPING .get (comstr , get_generic_shell_command )
141
+ rule , variables , provider_deps = provider (sub_env , node , action , tlist , slist , executor = executor )
142
+
143
+ # Get the dependencies for all targets
144
+ implicit = list ({dep for tgt in tlist for dep in get_dependencies (tgt )})
145
+
146
+ # Now add in the other dependencies related to the command,
147
+ # e.g. the compiler binary. The ninja rule can be user provided so
148
+ # we must do some validation to resolve the dependency path for ninja.
149
+ for provider_dep in provider_deps :
150
+
151
+ provider_dep = sub_env .subst (provider_dep )
152
+ if not provider_dep :
153
+ continue
154
+
155
+ # If the tool is a node, then SCons will resolve the path later, if its not
156
+ # a node then we assume it generated from build and make sure it is existing.
157
+ if isinstance (provider_dep , SCons .Node .Node ) or os .path .exists (provider_dep ):
158
+ implicit .append (provider_dep )
159
+ continue
160
+
161
+ # in some case the tool could be in the local directory and be supplied without the ext
162
+ # such as in windows, so append the executable suffix and check.
163
+ prog_suffix = sub_env .get ('PROGSUFFIX' , '' )
164
+ provider_dep_ext = provider_dep if provider_dep .endswith (prog_suffix ) else provider_dep + prog_suffix
165
+ if os .path .exists (provider_dep_ext ):
166
+ implicit .append (provider_dep_ext )
167
+ continue
168
+
169
+ # Many commands will assume the binary is in the path, so
170
+ # we accept this as a possible input from a given command.
171
+
172
+ provider_dep_abspath = sub_env .WhereIs (provider_dep ) or sub_env .WhereIs (provider_dep , path = os .environ ["PATH" ])
173
+ if provider_dep_abspath :
174
+ implicit .append (provider_dep_abspath )
175
+ continue
176
+
177
+ # Possibly these could be ignore and the build would still work, however it may not always
178
+ # rebuild correctly, so we hard stop, and force the user to fix the issue with the provided
179
+ # ninja rule.
180
+ raise Exception ("Could not resolve path for %s dependency on node '%s'" % (provider_dep , node ))
181
+
182
+ ninja_build = {
183
+ "order_only" : get_order_only (node ),
184
+ "outputs" : get_outputs (node ),
185
+ "inputs" : get_inputs (node ),
186
+ "implicit" : implicit ,
187
+ "rule" : get_rule (node , rule ),
188
+ "variables" : variables ,
189
+ }
190
+
191
+ # Don't use sub_env here because we require that NINJA_POOL be set
192
+ # on a per-builder call basis to prevent accidental strange
193
+ # behavior like env['NINJA_POOL'] = 'console' and sub_env can be
194
+ # the global Environment object if node.env is None.
195
+ # Example:
196
+ #
197
+ # Allowed:
198
+ #
199
+ # env.Command("ls", NINJA_POOL="ls_pool")
200
+ #
201
+ # Not allowed and ignored:
202
+ #
203
+ # env["NINJA_POOL"] = "ls_pool"
204
+ # env.Command("ls")
205
+ #
206
+ # TODO: Why not alloe env['NINJA_POOL'] ? (bdbaddog)
207
+ if node .env and node .env .get ("NINJA_POOL" , None ) is not None :
208
+ ninja_build ["pool" ] = node .env ["NINJA_POOL" ]
209
+
210
+ return ninja_build
211
+
212
+
213
+ def gen_get_response_file_command (env , rule , tool , tool_is_dynamic = False , custom_env = {}):
214
+ """Generate a response file command provider for rule name."""
215
+
216
+ # If win32 using the environment with a response file command will cause
217
+ # ninja to fail to create the response file. Additionally since these rules
218
+ # generally are not piping through cmd.exe /c any environment variables will
219
+ # make CreateProcess fail to start.
220
+ #
221
+ # On POSIX we can still set environment variables even for compile
222
+ # commands so we do so.
223
+ use_command_env = not env ["PLATFORM" ] == "win32"
224
+ if "$" in tool :
225
+ tool_is_dynamic = True
226
+
227
+ def get_response_file_command (env , node , action , targets , sources , executor = None ):
228
+ if hasattr (action , "process" ):
229
+ cmd_list , _ , _ = action .process (targets , sources , env , executor = executor )
230
+ cmd_list = [str (c ).replace ("$" , "$$" ) for c in cmd_list [0 ]]
231
+ else :
232
+ command = generate_command (
233
+ env , node , action , targets , sources , executor = executor
234
+ )
235
+ cmd_list = shlex .split (command )
236
+
237
+ if tool_is_dynamic :
238
+ tool_command = env .subst (
239
+ tool , target = targets , source = sources , executor = executor
240
+ )
241
+ else :
242
+ tool_command = tool
243
+
244
+ try :
245
+ # Add 1 so we always keep the actual tool inside of cmd
246
+ tool_idx = cmd_list .index (tool_command ) + 1
247
+ except ValueError :
248
+ raise Exception (
249
+ "Could not find tool {} in {} generated from {}" .format (
250
+ tool , cmd_list , get_comstr (env , action , targets , sources )
251
+ )
252
+ )
253
+
254
+ cmd , rsp_content = cmd_list [:tool_idx ], cmd_list [tool_idx :]
255
+ rsp_content = ['"' + rsp_content_item + '"' for rsp_content_item in rsp_content ]
256
+ rsp_content = " " .join (rsp_content )
257
+
258
+ variables = {"rspc" : rsp_content , rule : cmd }
259
+ if use_command_env :
260
+ variables ["env" ] = get_command_env (env )
261
+
262
+ for key , value in custom_env .items ():
263
+ variables ["env" ] += env .subst (
264
+ "export %s=%s;" % (key , value ), target = targets , source = sources , executor = executor
265
+ ) + " "
266
+ return rule , variables , [tool_command ]
267
+
268
+ return get_response_file_command
0 commit comments