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 256