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