109380295ba73501a205346becac22c6978e4671dTorne (Richard Coles)#!/usr/bin/env python 209380295ba73501a205346becac22c6978e4671dTorne (Richard Coles)# Copyright (c) 2012 The Chromium Authors. All rights reserved. 309380295ba73501a205346becac22c6978e4671dTorne (Richard Coles)# Use of this source code is governed by a BSD-style license that can be 409380295ba73501a205346becac22c6978e4671dTorne (Richard Coles)# found in the LICENSE file. 509380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) 609380295ba73501a205346becac22c6978e4671dTorne (Richard Coles)'''SCons integration for GRIT. 709380295ba73501a205346becac22c6978e4671dTorne (Richard Coles)''' 809380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) 909380295ba73501a205346becac22c6978e4671dTorne (Richard Coles)# NOTE: DO NOT IMPORT ANY GRIT STUFF HERE - we import lazily so that 1009380295ba73501a205346becac22c6978e4671dTorne (Richard Coles)# grit and its dependencies aren't imported until actually needed. 1109380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) 1209380295ba73501a205346becac22c6978e4671dTorne (Richard Coles)import os 1309380295ba73501a205346becac22c6978e4671dTorne (Richard Coles)import types 1409380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) 1509380295ba73501a205346becac22c6978e4671dTorne (Richard Coles)def _IsDebugEnabled(): 1609380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) return 'GRIT_DEBUG' in os.environ and os.environ['GRIT_DEBUG'] == '1' 1709380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) 1809380295ba73501a205346becac22c6978e4671dTorne (Richard Coles)def _SourceToFile(source): 1909380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) '''Return the path to the source file, given the 'source' argument as provided 2009380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) by SCons to the _Builder or _Emitter functions. 2109380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) ''' 2209380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) # Get the filename of the source. The 'source' parameter can be a string, 2309380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) # a "node", or a list of strings or nodes. 2409380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) if isinstance(source, types.ListType): 2509380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) source = str(source[0]) 2609380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) else: 2709380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) source = str(source) 2809380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) return source 2909380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) 3009380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) 3109380295ba73501a205346becac22c6978e4671dTorne (Richard Coles)def _ParseRcFlags(flags): 3209380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) """Gets a mapping of defines. 3309380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) 3409380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) Args: 3509380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) flags: env['RCFLAGS']; the input defines. 3609380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) 37c1847b1379d12d0e05df27436bf19a9b1bf12deaTorne (Richard Coles) Returns: 3809380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) A tuple of (defines, res_file): 3909380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) defines: A mapping of {name: val} 4007a852d8c1953036774d8f3b65d18dcfea3bb4a2Ben Murdoch res_file: None, or the specified res file for static file dependencies. 4109380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) """ 4209380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) from grit import util 4309380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) 4409380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) defines = {} 4509380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) res_file = None 4609380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) # Get the CPP defines from the environment. 4709380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) res_flag = '--res_file=' 4809380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) for flag in flags: 4909380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) if flag.startswith(res_flag): 5009380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) res_file = flag[len(res_flag):] 5107a852d8c1953036774d8f3b65d18dcfea3bb4a2Ben Murdoch continue 5209380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) if flag.startswith('/D'): 5309380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) flag = flag[2:] 5409380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) name, val = util.ParseDefine(flag) 5509380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) # Only apply to first instance of a given define 5609380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) if name not in defines: 5709380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) defines[name] = val 5809380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) return (defines, res_file) 5909380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) 6009380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) 6109380295ba73501a205346becac22c6978e4671dTorne (Richard Coles)def _Builder(target, source, env): 6209380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) print _SourceToFile(source) 6309380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) 6409380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) from grit import grit_runner 6509380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) from grit.tool import build 6609380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) options = grit_runner.Options() 6709380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) # This sets options to default values 6809380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) options.ReadOptions([]) 6909380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) options.input = _SourceToFile(source) 7009380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) 7109380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) # TODO(joi) Check if we can get the 'verbose' option from the environment. 7209380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) 7309380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) builder = build.RcBuilder(defines=_ParseRcFlags(env['RCFLAGS'])[0]) 7409380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) 7509380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) # To ensure that our output files match what we promised SCons, we 7609380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) # use the list of targets provided by SCons and update the file paths in 7709380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) # our .grd input file with the targets. 7809380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) builder.scons_targets = [str(t) for t in target] 7909380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) builder.Run(options, []) 8009380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) return None # success 8109380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) 8209380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) 8309380295ba73501a205346becac22c6978e4671dTorne (Richard Coles)def _GetOutputFiles(grd, base_dir): 8409380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) """Processes outputs listed in the grd into rc_headers and rc_alls. 85d6cdb82654e8f3343a693ca752d5c4cee0324e17Torne (Richard Coles) 8609380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) Note that anything that's not an rc_header is classified as an rc_all. 8709380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) 8809380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) Args: 8909380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) grd: An open GRD reader. 9009380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) 9109380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) Returns: 9209380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) A tuple of (rc_headers, rc_alls, lang_folders): 9307a852d8c1953036774d8f3b65d18dcfea3bb4a2Ben Murdoch rc_headers: Outputs marked as rc_header. 9409380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) rc_alls: All other outputs. 9509380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) lang_folders: The output language folders. 9609380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) """ 9709380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) rc_headers = [] 9809380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) rc_alls = [] 9909380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) lang_folders = {} 10009380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) 10109380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) # Explicit output files. 10209380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) for output in grd.GetOutputFiles(): 10309380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) path = os.path.join(base_dir, output.GetFilename()) 10409380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) if (output.GetType() == 'rc_header'): 10509380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) rc_headers.append(path) 10609380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) else: 10709380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) rc_alls.append(path) 10809380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) if _IsDebugEnabled(): 10907a852d8c1953036774d8f3b65d18dcfea3bb4a2Ben Murdoch print 'GRIT: Added target %s' % path 11009380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) if output.attrs['lang'] != '': 11109380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) lang_folders[output.attrs['lang']] = os.path.dirname(path) 11209380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) 11309380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) return (rc_headers, rc_alls, lang_folders) 11409380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) 11509380295ba73501a205346becac22c6978e4671dTorne (Richard Coles) 116def _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 169def _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 192def _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. 235def 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. 254def exists(env): 255 return 1 256