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