blob: 71d45fcaec2f3912abec2430e5d590d51b4a9b62 [file] [log] [blame]
# 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