Index: grit/tool/build.py |
=================================================================== |
--- grit/tool/build.py (revision 0) |
+++ grit/tool/build.py (revision 0) |
@@ -0,0 +1,300 @@ |
+#!/usr/bin/python2.4 |
+# Copyright (c) 2011 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. |
+ |
+'''The 'grit build' tool along with integration for this tool with the |
+SCons build system. |
+''' |
+ |
+import filecmp |
+import getopt |
+import os |
+import shutil |
+import sys |
+import types |
+ |
+from grit import grd_reader |
+from grit import util |
+from grit.tool import interface |
+from grit import shortcuts |
+ |
+ |
+def ParseDefine(define): |
+ '''Parses a define that is either like "NAME" or "NAME=VAL" and |
+ returns its components, using True as the default value. Values of |
+ "1" and "0" are transformed to True and False respectively. |
+ ''' |
+ parts = [part.strip() for part in define.split('=')] |
+ assert len(parts) >= 1 |
+ name = parts[0] |
+ val = True |
+ if len(parts) > 1: |
+ val = parts[1] |
+ if val == "1": val = True |
+ elif val == "0": val = False |
+ return (name, val) |
+ |
+ |
+class RcBuilder(interface.Tool): |
+ '''A tool that builds RC files and resource header files for compilation. |
+ |
+Usage: grit build [-o OUTPUTDIR] [-D NAME[=VAL]]* |
+ |
+All output options for this tool are specified in the input file (see |
+'grit help' for details on how to specify the input file - it is a global |
+option). |
+ |
+Options: |
+ |
+ -o OUTPUTDIR Specify what directory output paths are relative to. |
+ Defaults to the current directory. |
+ |
+ -D NAME[=VAL] Specify a C-preprocessor-like define NAME with optional |
+ value VAL (defaults to 1) which will be used to control |
+ conditional inclusion of resources. |
+ |
+ -E NAME=VALUE Set environment variable NAME to VALUE (within grit). |
+ |
+ -f FIRSTIDFILE Path to a python file that specifies the first id of |
+ value to use for resources. Defaults to the file |
+ resources_ids next to grit.py. Set to an empty string |
+ if you don't want to use a first id file. |
+ |
+ -w WHITELISTFILE Path to a file containing the string names of the |
+ resources to include. Anything not listed is dropped. |
+ |
+ |
+Conditional inclusion of resources only affects the output of files which |
+control which resources get linked into a binary, e.g. it affects .rc files |
+meant for compilation but it does not affect resource header files (that define |
+IDs). This helps ensure that values of IDs stay the same, that all messages |
+are exported to translation interchange files (e.g. XMB files), etc. |
+''' |
+ |
+ def ShortDescription(self): |
+ return 'A tool that builds RC files for compilation.' |
+ |
+ def Run(self, opts, args): |
+ self.output_directory = '.' |
+ first_id_filename = None |
+ whitelist_filenames = [] |
+ (own_opts, args) = getopt.getopt(args, 'o:D:E:f:w:') |
+ for (key, val) in own_opts: |
+ if key == '-o': |
+ self.output_directory = val |
+ elif key == '-D': |
+ name, val = ParseDefine(val) |
+ self.defines[name] = val |
+ elif key == '-E': |
+ (env_name, env_value) = val.split('=') |
+ os.environ[env_name] = env_value |
+ elif key == '-f': |
+ first_id_filename = val |
+ elif key == '-w': |
+ whitelist_filenames.append(val) |
+ |
+ if len(args): |
+ print "This tool takes no tool-specific arguments." |
+ return 2 |
+ self.SetOptions(opts) |
+ if self.scons_targets: |
+ self.VerboseOut('Using SCons targets to identify files to output.\n') |
+ else: |
+ self.VerboseOut('Output directory: %s (absolute path: %s)\n' % |
+ (self.output_directory, |
+ os.path.abspath(self.output_directory))) |
+ |
+ if whitelist_filenames: |
+ self.whitelist_names = set() |
+ for whitelist_filename in whitelist_filenames: |
+ self.VerboseOut('Using whitelist: %s\n' % whitelist_filename); |
+ whitelist_file = open(whitelist_filename) |
+ self.whitelist_names |= set(whitelist_file.read().strip().split('\n')) |
+ whitelist_file.close() |
+ |
+ self.res = grd_reader.Parse(opts.input, first_id_filename=first_id_filename, |
+ debug=opts.extra_verbose, defines=self.defines) |
+ self.res.RunGatherers(recursive = True) |
+ self.Process() |
+ return 0 |
+ |
+ def __init__(self): |
+ # Default file-creation function is built-in file(). Only done to allow |
+ # overriding by unit test. |
+ self.fo_create = file |
+ |
+ # key/value pairs of C-preprocessor like defines that are used for |
+ # conditional output of resources |
+ self.defines = {} |
+ |
+ # self.res is a fully-populated resource tree if Run() |
+ # has been called, otherwise None. |
+ self.res = None |
+ |
+ # Set to a list of filenames for the output nodes that are relative |
+ # to the current working directory. They are in the same order as the |
+ # output nodes in the file. |
+ self.scons_targets = None |
+ |
+ # The set of names that are whitelisted to actually be included in the |
+ # output. |
+ self.whitelist_names = None |
+ |
+ |
+ # static method |
+ def AddWhitelistTags(start_node, whitelist_names): |
+ # Walk the tree of nodes added attributes for the nodes that shouldn't |
+ # be written into the target files (skip markers). |
+ from grit.node import include |
+ from grit.node import message |
+ for node in start_node.inorder(): |
+ # Same trick data_pack.py uses to see what nodes actually result in |
+ # real items. |
+ if (isinstance(node, include.IncludeNode) or |
+ isinstance(node, message.MessageNode)): |
+ text_ids = node.GetTextualIds() |
+ # Mark the item to be skipped if it wasn't in the whitelist. |
+ if text_ids and not text_ids[0] in whitelist_names: |
+ node.SetWhitelistMarkedAsSkip(True) |
+ AddWhitelistTags = staticmethod(AddWhitelistTags) |
+ |
+ # static method |
+ def ProcessNode(node, output_node, outfile): |
+ '''Processes a node in-order, calling its formatter before and after |
+ recursing to its children. |
+ |
+ Args: |
+ node: grit.node.base.Node subclass |
+ output_node: grit.node.io.File |
+ outfile: open filehandle |
+ ''' |
+ # See if the node should be skipped by a whitelist. |
+ # Note: Some Format calls have side effects, so Format is always called |
+ # and the whitelist is used to only avoid the output. |
+ should_write = not node.WhitelistMarkedAsSkip() |
+ |
+ base_dir = util.dirname(output_node.GetOutputFilename()) |
+ |
+ try: |
+ formatter = node.ItemFormatter(output_node.GetType()) |
+ if formatter: |
+ formatted = formatter.Format(node, output_node.GetLanguage(), |
+ begin_item=True, output_dir=base_dir) |
+ if should_write: |
+ outfile.write(formatted) |
+ except: |
+ print u"Error processing node %s" % unicode(node) |
+ raise |
+ |
+ for child in node.children: |
+ RcBuilder.ProcessNode(child, output_node, outfile) |
+ |
+ try: |
+ if formatter: |
+ formatted = formatter.Format(node, output_node.GetLanguage(), |
+ begin_item=False, output_dir=base_dir) |
+ if should_write: |
+ outfile.write(formatted) |
+ except: |
+ print u"Error processing node %s" % unicode(node) |
+ raise |
+ ProcessNode = staticmethod(ProcessNode) |
+ |
+ |
+ def Process(self): |
+ # Update filenames with those provided by SCons if we're being invoked |
+ # from SCons. The list of SCons targets also includes all <structure> |
+ # node outputs, but it starts with our output files, in the order they |
+ # occur in the .grd |
+ if self.scons_targets: |
+ assert len(self.scons_targets) >= len(self.res.GetOutputFiles()) |
+ outfiles = self.res.GetOutputFiles() |
+ for ix in range(len(outfiles)): |
+ outfiles[ix].output_filename = os.path.abspath( |
+ self.scons_targets[ix]) |
+ else: |
+ for output in self.res.GetOutputFiles(): |
+ output.output_filename = os.path.abspath(os.path.join( |
+ self.output_directory, output.GetFilename())) |
+ |
+ # If there are whitelisted names, tag the tree once up front, this way |
+ # while looping through the actual output, it is just an attribute check. |
+ if self.whitelist_names: |
+ self.AddWhitelistTags(self.res, self.whitelist_names) |
+ |
+ for output in self.res.GetOutputFiles(): |
+ self.VerboseOut('Creating %s...' % output.GetFilename()) |
+ |
+ # Microsoft's RC compiler can only deal with single-byte or double-byte |
+ # files (no UTF-8), so we make all RC files UTF-16 to support all |
+ # character sets. |
+ if output.GetType() in ('rc_header', 'resource_map_header', |
+ 'resource_map_source', 'resource_file_map_source'): |
+ encoding = 'cp1252' |
+ elif output.GetType() in ('js_map_format', 'plist', 'plist_strings', |
+ 'doc', 'json'): |
+ encoding = 'utf_8' |
+ else: |
+ # TODO(gfeher) modify here to set utf-8 encoding for admx/adml |
+ encoding = 'utf_16' |
+ |
+ # Make the output directory if it doesn't exist. |
+ outdir = os.path.split(output.GetOutputFilename())[0] |
+ if not os.path.exists(outdir): |
+ os.makedirs(outdir) |
+ # Write the results to a temporary file and only overwrite the original |
+ # if the file changed. This avoids unnecessary rebuilds. |
+ outfile = self.fo_create(output.GetOutputFilename() + '.tmp', 'wb') |
+ |
+ if output.GetType() != 'data_package': |
+ outfile = util.WrapOutputStream(outfile, encoding) |
+ |
+ # Set the context, for conditional inclusion of resources |
+ self.res.SetOutputContext(output.GetLanguage(), self.defines) |
+ |
+ # TODO(joi) Handle this more gracefully |
+ import grit.format.rc_header |
+ grit.format.rc_header.Item.ids_ = {} |
+ |
+ # Iterate in-order through entire resource tree, calling formatters on |
+ # the entry into a node and on exit out of it. |
+ self.ProcessNode(self.res, output, outfile) |
+ outfile.close() |
+ |
+ # Now copy from the temp file back to the real output, but on Windows, |
+ # only if the real output doesn't exist or the contents of the file |
+ # changed. This prevents identical headers from being written and .cc |
+ # files from recompiling (which is painful on Windows). |
+ if not os.path.exists(output.GetOutputFilename()): |
+ os.rename(output.GetOutputFilename() + '.tmp', |
+ output.GetOutputFilename()) |
+ else: |
+ # CHROMIUM SPECIFIC CHANGE. |
+ # This clashes with gyp + vstudio, which expect the output timestamp |
+ # to change on a rebuild, even if nothing has changed. |
+ #files_match = filecmp.cmp(output.GetOutputFilename(), |
+ # output.GetOutputFilename() + '.tmp') |
+ #if (output.GetType() != 'rc_header' or not files_match |
+ # or sys.platform != 'win32'): |
+ shutil.copy2(output.GetOutputFilename() + '.tmp', |
+ output.GetOutputFilename()) |
+ os.remove(output.GetOutputFilename() + '.tmp') |
+ |
+ self.VerboseOut(' done.\n') |
+ |
+ # Print warnings if there are any duplicate shortcuts. |
+ warnings = shortcuts.GenerateDuplicateShortcutsWarnings( |
+ self.res.UberClique(), self.res.GetTcProject()) |
+ if warnings: |
+ print '\n'.join(warnings) |
+ |
+ # Print out any fallback warnings, and missing translation errors, and |
+ # exit with an error code if there are missing translations in a non-pseudo |
+ # and non-official build. |
+ warnings = (self.res.UberClique().MissingTranslationsReport(). |
+ encode('ascii', 'replace')) |
+ if warnings and self.defines.get('_google_chrome', False): |
+ print warnings |
+ if self.res.UberClique().HasMissingTranslations(): |
+ sys.exit(-1) |
Property changes on: grit/tool/build.py |
___________________________________________________________________ |
Added: svn:eol-style |
+ LF |