#!/usr/bin/env python # Copyright (c) 2012 The Chromium Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. '''SCons integration for GRIT. ''' # NOTE: DO NOT IMPORT ANY GRIT STUFF HERE - we import lazily so that # grit and its dependencies aren't imported until actually needed. import os import types def _IsDebugEnabled(): return 'GRIT_DEBUG' in os.environ and os.environ['GRIT_DEBUG'] == '1' def _SourceToFile(source): '''Return the path to the source file, given the 'source' argument as provided by SCons to the _Builder or _Emitter functions. ''' # Get the filename of the source. The 'source' parameter can be a string, # a "node", or a list of strings or nodes. if isinstance(source, types.ListType): source = str(source[0]) else: source = str(source) return source def _ParseRcFlags(flags): """Gets a mapping of defines. Args: flags: env['RCFLAGS']; the input defines. Returns: A tuple of (defines, res_file): defines: A mapping of {name: val} res_file: None, or the specified res file for static file dependencies. """ from grit import util defines = {} res_file = None # Get the CPP defines from the environment. res_flag = '--res_file=' for flag in flags: if flag.startswith(res_flag): res_file = flag[len(res_flag):] continue if flag.startswith('/D'): flag = flag[2:] name, val = util.ParseDefine(flag) # Only apply to first instance of a given define if name not in defines: defines[name] = val return (defines, res_file) def _Builder(target, source, env): print _SourceToFile(source) from grit import grit_runner from grit.tool import build options = grit_runner.Options() # This sets options to default values options.ReadOptions([]) options.input = _SourceToFile(source) # TODO(joi) Check if we can get the 'verbose' option from the environment. builder = build.RcBuilder(defines=_ParseRcFlags(env['RCFLAGS'])[0]) # To ensure that our output files match what we promised SCons, we # use the list of targets provided by SCons and update the file paths in # our .grd input file with the targets. builder.scons_targets = [str(t) for t in target] builder.Run(options, []) return None # success def _GetOutputFiles(grd, base_dir): """Processes outputs listed in the grd into rc_headers and rc_alls. Note that anything that's not an rc_header is classified as an rc_all. Args: grd: An open GRD reader. Returns: A tuple of (rc_headers, rc_alls, lang_folders): rc_headers: Outputs marked as rc_header. rc_alls: All other outputs. lang_folders: The output language folders. """ rc_headers = [] rc_alls = [] lang_folders = {} # Explicit output files. for output in grd.GetOutputFiles(): path = os.path.join(base_dir, output.GetFilename()) if (output.GetType() == 'rc_header'): rc_headers.append(path) else: rc_alls.append(path) if _IsDebugEnabled(): print 'GRIT: Added target %s' % path if output.attrs['lang'] != '': lang_folders[output.attrs['lang']] = os.path.dirname(path) return (rc_headers, rc_alls, lang_folders) def _ProcessNodes(grd, base_dir, lang_folders): """Processes the GRD nodes to figure out file dependencies. Args: grd: An open GRD reader. base_dir: The base directory for filenames. lang_folders: THe output language folders. Returns: A tuple of (structure_outputs, translated_files, static_files): structure_outputs: Structures marked as sconsdep. translated_files: Files that are structures or skeletons, and get translated by GRIT. static_files: Files that are includes, and are used directly by res files. """ structure_outputs = [] translated_files = [] static_files = [] # Go through nodes, figuring out resources. Also output certain resources # as build targets, based on the sconsdep flag. for node in grd.ActiveDescendants(): with node: file = node.ToRealPath(node.GetInputPath()) if node.name == 'structure': translated_files.append(os.path.abspath(file)) # TODO(joi) Should remove the "if sconsdep is true" thing as it is a # hack - see grit/node/structure.py if node.HasFileForLanguage() and node.attrs['sconsdep'] == 'true': for lang in lang_folders: path = node.FileForLanguage(lang, lang_folders[lang], create_file=False, return_if_not_generated=False) if path: structure_outputs.append(path) if _IsDebugEnabled(): print 'GRIT: Added target %s' % path elif (node.name == 'skeleton' or (node.name == 'file' and node.parent and node.parent.name == 'translations')): translated_files.append(os.path.abspath(file)) elif node.name == 'include': # If it's added by file name and the file isn't easy to find, don't make # it a dependency. This could add some build flakiness, but it doesn't # work otherwise. if node.attrs['filenameonly'] != 'true' or os.path.exists(file): static_files.append(os.path.abspath(file)) # If it's output from mk, look in the output directory. elif node.attrs['mkoutput'] == 'true': static_files.append(os.path.join(base_dir, os.path.basename(file))) return (structure_outputs, translated_files, static_files) def _SetDependencies(env, base_dir, res_file, rc_alls, translated_files, static_files): """Sets dependencies in the environment. Args: env: The SCons environment. base_dir: The base directory for filenames. res_file: The res_file specified in the RC flags. rc_alls: All non-rc_header outputs. translated_files: Files that are structures or skeletons, and get translated by GRIT. static_files: Files that are includes, and are used directly by res files. """ if res_file: env.Depends(os.path.join(base_dir, res_file), static_files) else: # Make a best effort dependency setup when no res file is specified. translated_files.extend(static_files) for rc_all in rc_alls: env.Depends(rc_all, translated_files) def _Emitter(target, source, env): """Modifies the list of targets to include all outputs. Note that this also sets up the dependencies, even though it's an emitter rather than a scanner. This is so that the resource header file doesn't show as having dependencies. Args: target: The list of targets to emit for. source: The source or list of sources for the target. env: The SCons environment. Returns: A tuple of (targets, sources). """ from grit import grd_reader from grit import util (defines, res_file) = _ParseRcFlags(env['RCFLAGS']) grd = grd_reader.Parse(_SourceToFile(source), debug=_IsDebugEnabled()) # TODO(jperkins): This is a hack to get an output context set for the reader. # This should really be smarter about the language. grd.SetOutputLanguage('en') grd.SetDefines(defines) base_dir = util.dirname(str(target[0])) (rc_headers, rc_alls, lang_folders) = _GetOutputFiles(grd, base_dir) (structure_outputs, translated_files, static_files) = _ProcessNodes(grd, base_dir, lang_folders) rc_alls.extend(structure_outputs) _SetDependencies(env, base_dir, res_file, rc_alls, translated_files, static_files) targets = rc_headers targets.extend(rc_alls) # Return target and source lists. return (targets, source) # Function name is mandated by newer versions of SCons. def generate(env): # Importing this module should be possible whenever this function is invoked # since it should only be invoked by SCons. import SCons.Builder import SCons.Action # The varlist parameter tells SCons that GRIT needs to be invoked again # if RCFLAGS has changed since last compilation. build_action = SCons.Action.FunctionAction(_Builder, varlist=['RCFLAGS']) emit_action = SCons.Action.FunctionAction(_Emitter, varlist=['RCFLAGS']) builder = SCons.Builder.Builder(action=build_action, emitter=emit_action, src_suffix='.grd') # Add our builder and scanner to the environment. env.Append(BUILDERS = {'GRIT': builder}) # Function name is mandated by newer versions of SCons. def exists(env): return 1