OLD | NEW |
| (Empty) |
1 #!/usr/bin/env python | |
2 # Copyright (c) 2012 The Chromium Authors. All rights reserved. | |
3 # Use of this source code is governed by a BSD-style license that can be | |
4 # found in the LICENSE file. | |
5 | |
6 '''SCons integration for GRIT. | |
7 ''' | |
8 | |
9 # NOTE: DO NOT IMPORT ANY GRIT STUFF HERE - we import lazily so that | |
10 # grit and its dependencies aren't imported until actually needed. | |
11 | |
12 import os | |
13 import types | |
14 | |
15 def _IsDebugEnabled(): | |
16 return 'GRIT_DEBUG' in os.environ and os.environ['GRIT_DEBUG'] == '1' | |
17 | |
18 def _SourceToFile(source): | |
19 '''Return the path to the source file, given the 'source' argument as provided | |
20 by SCons to the _Builder or _Emitter functions. | |
21 ''' | |
22 # Get the filename of the source. The 'source' parameter can be a string, | |
23 # a "node", or a list of strings or nodes. | |
24 if isinstance(source, types.ListType): | |
25 source = str(source[0]) | |
26 else: | |
27 source = str(source) | |
28 return source | |
29 | |
30 | |
31 def _ParseRcFlags(flags): | |
32 """Gets a mapping of defines. | |
33 | |
34 Args: | |
35 flags: env['RCFLAGS']; the input defines. | |
36 | |
37 Returns: | |
38 A tuple of (defines, res_file): | |
39 defines: A mapping of {name: val} | |
40 res_file: None, or the specified res file for static file dependencies. | |
41 """ | |
42 from grit import util | |
43 | |
44 defines = {} | |
45 res_file = None | |
46 # Get the CPP defines from the environment. | |
47 res_flag = '--res_file=' | |
48 for flag in flags: | |
49 if flag.startswith(res_flag): | |
50 res_file = flag[len(res_flag):] | |
51 continue | |
52 if flag.startswith('/D'): | |
53 flag = flag[2:] | |
54 name, val = util.ParseDefine(flag) | |
55 # Only apply to first instance of a given define | |
56 if name not in defines: | |
57 defines[name] = val | |
58 return (defines, res_file) | |
59 | |
60 | |
61 def _Builder(target, source, env): | |
62 print _SourceToFile(source) | |
63 | |
64 from grit import grit_runner | |
65 from grit.tool import build | |
66 options = grit_runner.Options() | |
67 # This sets options to default values | |
68 options.ReadOptions([]) | |
69 options.input = _SourceToFile(source) | |
70 | |
71 # TODO(joi) Check if we can get the 'verbose' option from the environment. | |
72 | |
73 builder = build.RcBuilder(defines=_ParseRcFlags(env['RCFLAGS'])[0]) | |
74 | |
75 # To ensure that our output files match what we promised SCons, we | |
76 # use the list of targets provided by SCons and update the file paths in | |
77 # our .grd input file with the targets. | |
78 builder.scons_targets = [str(t) for t in target] | |
79 builder.Run(options, []) | |
80 return None # success | |
81 | |
82 | |
83 def _GetOutputFiles(grd, base_dir): | |
84 """Processes outputs listed in the grd into rc_headers and rc_alls. | |
85 | |
86 Note that anything that's not an rc_header is classified as an rc_all. | |
87 | |
88 Args: | |
89 grd: An open GRD reader. | |
90 | |
91 Returns: | |
92 A tuple of (rc_headers, rc_alls, lang_folders): | |
93 rc_headers: Outputs marked as rc_header. | |
94 rc_alls: All other outputs. | |
95 lang_folders: The output language folders. | |
96 """ | |
97 rc_headers = [] | |
98 rc_alls = [] | |
99 lang_folders = {} | |
100 | |
101 # Explicit output files. | |
102 for output in grd.GetOutputFiles(): | |
103 path = os.path.join(base_dir, output.GetFilename()) | |
104 if (output.GetType() == 'rc_header'): | |
105 rc_headers.append(path) | |
106 else: | |
107 rc_alls.append(path) | |
108 if _IsDebugEnabled(): | |
109 print 'GRIT: Added target %s' % path | |
110 if output.attrs['lang'] != '': | |
111 lang_folders[output.attrs['lang']] = os.path.dirname(path) | |
112 | |
113 return (rc_headers, rc_alls, lang_folders) | |
114 | |
115 | |
116 def _ProcessNodes(grd, base_dir, lang_folders): | |
117 """Processes the GRD nodes to figure out file dependencies. | |
118 | |
119 Args: | |
120 grd: An open GRD reader. | |
121 base_dir: The base directory for filenames. | |
122 lang_folders: THe output language folders. | |
123 | |
124 Returns: | |
125 A tuple of (structure_outputs, translated_files, static_files): | |
126 structure_outputs: Structures marked as sconsdep. | |
127 translated_files: Files that are structures or skeletons, and get | |
128 translated by GRIT. | |
129 static_files: Files that are includes, and are used directly by res files. | |
130 """ | |
131 structure_outputs = [] | |
132 translated_files = [] | |
133 static_files = [] | |
134 | |
135 # Go through nodes, figuring out resources. Also output certain resources | |
136 # as build targets, based on the sconsdep flag. | |
137 for node in grd.ActiveDescendants(): | |
138 with node: | |
139 file = node.ToRealPath(node.GetInputPath()) | |
140 if node.name == 'structure': | |
141 translated_files.append(os.path.abspath(file)) | |
142 # TODO(joi) Should remove the "if sconsdep is true" thing as it is a | |
143 # hack - see grit/node/structure.py | |
144 if node.HasFileForLanguage() and node.attrs['sconsdep'] == 'true': | |
145 for lang in lang_folders: | |
146 path = node.FileForLanguage(lang, lang_folders[lang], | |
147 create_file=False, | |
148 return_if_not_generated=False) | |
149 if path: | |
150 structure_outputs.append(path) | |
151 if _IsDebugEnabled(): | |
152 print 'GRIT: Added target %s' % path | |
153 elif (node.name == 'skeleton' or (node.name == 'file' and node.parent and | |
154 node.parent.name == 'translations')): | |
155 translated_files.append(os.path.abspath(file)) | |
156 elif node.name == 'include': | |
157 # If it's added by file name and the file isn't easy to find, don't make | |
158 # it a dependency. This could add some build flakiness, but it doesn't | |
159 # work otherwise. | |
160 if node.attrs['filenameonly'] != 'true' or os.path.exists(file): | |
161 static_files.append(os.path.abspath(file)) | |
162 # If it's output from mk, look in the output directory. | |
163 elif node.attrs['mkoutput'] == 'true': | |
164 static_files.append(os.path.join(base_dir, os.path.basename(file))) | |
165 | |
166 return (structure_outputs, translated_files, static_files) | |
167 | |
168 | |
169 def _SetDependencies(env, base_dir, res_file, rc_alls, translated_files, | |
170 static_files): | |
171 """Sets dependencies in the environment. | |
172 | |
173 Args: | |
174 env: The SCons environment. | |
175 base_dir: The base directory for filenames. | |
176 res_file: The res_file specified in the RC flags. | |
177 rc_alls: All non-rc_header outputs. | |
178 translated_files: Files that are structures or skeletons, and get | |
179 translated by GRIT. | |
180 static_files: Files that are includes, and are used directly by res files. | |
181 """ | |
182 if res_file: | |
183 env.Depends(os.path.join(base_dir, res_file), static_files) | |
184 else: | |
185 # Make a best effort dependency setup when no res file is specified. | |
186 translated_files.extend(static_files) | |
187 | |
188 for rc_all in rc_alls: | |
189 env.Depends(rc_all, translated_files) | |
190 | |
191 | |
192 def _Emitter(target, source, env): | |
193 """Modifies the list of targets to include all outputs. | |
194 | |
195 Note that this also sets up the dependencies, even though it's an emitter | |
196 rather than a scanner. This is so that the resource header file doesn't show | |
197 as having dependencies. | |
198 | |
199 Args: | |
200 target: The list of targets to emit for. | |
201 source: The source or list of sources for the target. | |
202 env: The SCons environment. | |
203 | |
204 Returns: | |
205 A tuple of (targets, sources). | |
206 """ | |
207 from grit import grd_reader | |
208 from grit import util | |
209 | |
210 (defines, res_file) = _ParseRcFlags(env['RCFLAGS']) | |
211 | |
212 grd = grd_reader.Parse(_SourceToFile(source), debug=_IsDebugEnabled()) | |
213 # TODO(jperkins): This is a hack to get an output context set for the reader. | |
214 # This should really be smarter about the language. | |
215 grd.SetOutputLanguage('en') | |
216 grd.SetDefines(defines) | |
217 | |
218 base_dir = util.dirname(str(target[0])) | |
219 (rc_headers, rc_alls, lang_folders) = _GetOutputFiles(grd, base_dir) | |
220 (structure_outputs, translated_files, static_files) = _ProcessNodes(grd, | |
221 base_dir, lang_folders) | |
222 | |
223 rc_alls.extend(structure_outputs) | |
224 _SetDependencies(env, base_dir, res_file, rc_alls, translated_files, | |
225 static_files) | |
226 | |
227 targets = rc_headers | |
228 targets.extend(rc_alls) | |
229 | |
230 # Return target and source lists. | |
231 return (targets, source) | |
232 | |
233 | |
234 # Function name is mandated by newer versions of SCons. | |
235 def generate(env): | |
236 # Importing this module should be possible whenever this function is invoked | |
237 # since it should only be invoked by SCons. | |
238 import SCons.Builder | |
239 import SCons.Action | |
240 | |
241 # The varlist parameter tells SCons that GRIT needs to be invoked again | |
242 # if RCFLAGS has changed since last compilation. | |
243 build_action = SCons.Action.FunctionAction(_Builder, varlist=['RCFLAGS']) | |
244 emit_action = SCons.Action.FunctionAction(_Emitter, varlist=['RCFLAGS']) | |
245 | |
246 builder = SCons.Builder.Builder(action=build_action, emitter=emit_action, | |
247 src_suffix='.grd') | |
248 | |
249 # Add our builder and scanner to the environment. | |
250 env.Append(BUILDERS = {'GRIT': builder}) | |
251 | |
252 | |
253 # Function name is mandated by newer versions of SCons. | |
254 def exists(env): | |
255 return 1 | |
OLD | NEW |