Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(384)

Unified Diff: tools/grit/grit/tool/build.py

Issue 1410853008: Move grit from DEPS into src. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: webview licenses Created 5 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « tools/grit/grit/tool/android2grd_unittest.py ('k') | tools/grit/grit/tool/build_unittest.py » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: tools/grit/grit/tool/build.py
diff --git a/tools/grit/grit/tool/build.py b/tools/grit/grit/tool/build.py
new file mode 100755
index 0000000000000000000000000000000000000000..65a966f5ac041b30a72a202a856c05f0dc025765
--- /dev/null
+++ b/tools/grit/grit/tool/build.py
@@ -0,0 +1,499 @@
+#!/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.
+
+'''The 'grit build' tool along with integration for this tool with the
+SCons build system.
+'''
+
+import codecs
+import filecmp
+import getopt
+import os
+import shutil
+import sys
+
+from grit import grd_reader
+from grit import util
+from grit.tool import interface
+from grit import shortcuts
+
+
+# It would be cleaner to have each module register itself, but that would
+# require importing all of them on every run of GRIT.
+'''Map from <output> node types to modules under grit.format.'''
+_format_modules = {
+ 'android': 'android_xml',
+ 'c_format': 'c_format',
+ 'chrome_messages_json': 'chrome_messages_json',
+ 'data_package': 'data_pack',
+ 'js_map_format': 'js_map_format',
+ 'rc_all': 'rc',
+ 'rc_translateable': 'rc',
+ 'rc_nontranslateable': 'rc',
+ 'rc_header': 'rc_header',
+ 'resource_map_header': 'resource_map',
+ 'resource_map_source': 'resource_map',
+ 'resource_file_map_source': 'resource_map',
+}
+_format_modules.update(
+ (type, 'policy_templates.template_formatter') for type in
+ [ 'adm', 'admx', 'adml', 'reg', 'doc', 'json',
+ 'plist', 'plist_strings', 'ios_plist', 'android_policy' ])
+
+
+def GetFormatter(type):
+ modulename = 'grit.format.' + _format_modules[type]
+ __import__(modulename)
+ module = sys.modules[modulename]
+ try:
+ return module.Format
+ except AttributeError:
+ return module.GetFormatter(type)
+
+
+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:
+
+ -a FILE Assert that the given file is an output. There can be
+ multiple "-a" flags listed for multiple outputs. If a "-a"
+ or "--assert-file-list" argument is present, then the list
+ of asserted files must match the output files or the tool
+ will fail. The use-case is for the build system to maintain
+ separate lists of output files and to catch errors if the
+ build system's list and the grit list are out-of-sync.
+
+ --assert-file-list Provide a file listing multiple asserted output files.
+ There is one file name per line. This acts like specifying
+ each file with "-a" on the command line, but without the
+ possibility of running into OS line-length limits for very
+ long lists.
+
+ -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 FIRSTIDSFILE Path to a python file that specifies the first id of
+ value to use for resources. A non-empty value here will
+ override the value specified in the <grit> node's
+ first_ids_file.
+
+ -w WHITELISTFILE Path to a file containing the string names of the
+ resources to include. Anything not listed is dropped.
+
+ -t PLATFORM Specifies the platform the build is targeting; defaults
+ to the value of sys.platform. The value provided via this
+ flag should match what sys.platform would report for your
+ target platform; see grit.node.base.EvaluateCondition.
+
+ -h HEADERFORMAT Custom format string to use for generating rc header files.
+ The string should have two placeholders: {textual_id}
+ and {numeric_id}. E.g. "#define {textual_id} {numeric_id}"
+ Otherwise it will use the default "#define SYMBOL 1234"
+
+ --output-all-resource-defines
+ --no-output-all-resource-defines If specified, overrides the value of the
+ output_all_resource_defines attribute of the root <grit>
+ element of the input .grd file.
+
+ --write-only-new flag
+ If flag is non-0, write output files to a temporary file
+ first, and copy it to the real output only if the new file
+ is different from the old file. This allows some build
+ systems to realize that dependent build steps might be
+ unnecessary, at the cost of comparing the output data at
+ grit time.
+
+ --depend-on-stamp
+ If specified along with --depfile and --depdir, the depfile
+ generated will depend on a stampfile instead of the first
+ output in the input .grd file.
+
+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_ids_file = None
+ whitelist_filenames = []
+ assert_output_files = []
+ target_platform = None
+ depfile = None
+ depdir = None
+ rc_header_format = None
+ output_all_resource_defines = None
+ write_only_new = False
+ depend_on_stamp = False
+ (own_opts, args) = getopt.getopt(args, 'a:o:D:E:f:w:t:h:',
+ ('depdir=','depfile=','assert-file-list=',
+ 'output-all-resource-defines',
+ 'no-output-all-resource-defines',
+ 'depend-on-stamp',
+ 'write-only-new='))
+ for (key, val) in own_opts:
+ if key == '-a':
+ assert_output_files.append(val)
+ elif key == '--assert-file-list':
+ with open(val) as f:
+ assert_output_files += f.read().splitlines()
+ elif key == '-o':
+ self.output_directory = val
+ elif key == '-D':
+ name, val = util.ParseDefine(val)
+ self.defines[name] = val
+ elif key == '-E':
+ (env_name, env_value) = val.split('=', 1)
+ os.environ[env_name] = env_value
+ elif key == '-f':
+ # TODO(joi@chromium.org): Remove this override once change
+ # lands in WebKit.grd to specify the first_ids_file in the
+ # .grd itself.
+ first_ids_file = val
+ elif key == '-w':
+ whitelist_filenames.append(val)
+ elif key == '--output-all-resource-defines':
+ output_all_resource_defines = True
+ elif key == '--no-output-all-resource-defines':
+ output_all_resource_defines = False
+ elif key == '-t':
+ target_platform = val
+ elif key == '-h':
+ rc_header_format = val
+ elif key == '--depdir':
+ depdir = val
+ elif key == '--depfile':
+ depfile = val
+ elif key == '--write-only-new':
+ write_only_new = val != '0'
+ elif key == '--depend-on-stamp':
+ depend_on_stamp = True
+
+ 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_contents = util.ReadFile(whitelist_filename, util.RAW_TEXT)
+ self.whitelist_names.update(whitelist_contents.strip().split('\n'))
+
+ self.write_only_new = write_only_new
+
+ self.res = grd_reader.Parse(opts.input,
+ debug=opts.extra_verbose,
+ first_ids_file=first_ids_file,
+ defines=self.defines,
+ target_platform=target_platform)
+
+ # If the output_all_resource_defines option is specified, override the value
+ # found in the grd file.
+ if output_all_resource_defines is not None:
+ self.res.SetShouldOutputAllResourceDefines(output_all_resource_defines)
+
+ # Set an output context so that conditionals can use defines during the
+ # gathering stage; we use a dummy language here since we are not outputting
+ # a specific language.
+ self.res.SetOutputLanguage('en')
+ if rc_header_format:
+ self.res.AssignRcHeaderFormat(rc_header_format)
+ self.res.RunGatherers()
+ self.Process()
+
+ if assert_output_files:
+ if not self.CheckAssertedOutputFiles(assert_output_files):
+ return 2
+
+ if depfile and depdir:
+ self.GenerateDepfile(depfile, depdir, first_ids_file, depend_on_stamp)
+
+ return 0
+
+ def __init__(self, defines=None):
+ # Default file-creation function is codecs.open(). Only done to allow
+ # overriding by unit test.
+ self.fo_create = codecs.open
+
+ # key/value pairs of C-preprocessor like defines that are used for
+ # conditional output of resources
+ self.defines = defines or {}
+
+ # 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
+
+ # Whether to compare outputs to their old contents before writing.
+ self.write_only_new = False
+
+ @staticmethod
+ 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
+ from grit.node import structure
+ for node in start_node:
+ # 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) or
+ isinstance(node, structure.StructureNode)):
+ text_ids = node.GetTextualIds()
+ # Mark the item to be skipped if it wasn't in the whitelist.
+ if text_ids and text_ids[0] not in whitelist_names:
+ node.SetWhitelistMarkedAsSkip(True)
+
+ @staticmethod
+ 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.OutputNode
+ outfile: open filehandle
+ '''
+ base_dir = util.dirname(output_node.GetOutputFilename())
+
+ formatter = GetFormatter(output_node.GetType())
+ formatted = formatter(node, output_node.GetLanguage(), output_dir=base_dir)
+ outfile.writelines(formatted)
+
+
+ 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 ('android', 'c_format', 'js_map_format', 'plist',
+ 'plist_strings', 'doc', 'json', 'android_policy'):
+ encoding = 'utf_8'
+ elif output.GetType() in ('chrome_messages_json'):
+ # Chrome Web Store currently expects BOM for UTF-8 files :-(
+ encoding = 'utf-8-sig'
+ else:
+ # TODO(gfeher) modify here to set utf-8 encoding for admx/adml
+ encoding = 'utf_16'
+
+ # Set the context, for conditional inclusion of resources
+ self.res.SetOutputLanguage(output.GetLanguage())
+ self.res.SetOutputContext(output.GetContext())
+ self.res.SetFallbackToDefaultLayout(output.GetFallbackToDefaultLayout())
+ self.res.SetDefines(self.defines)
+
+ # Make the output directory if it doesn't exist.
+ self.MakeDirectoriesTo(output.GetOutputFilename())
+
+ # 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)
+
+ # Iterate in-order through entire resource tree, calling formatters on
+ # the entry into a node and on exit out of it.
+ with outfile:
+ self.ProcessNode(self.res, output, outfile)
+
+ # 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, so only do
+ # it when opted in.
+ if not self.write_only_new:
+ write_file = True
+ else:
+ files_match = filecmp.cmp(output.GetOutputFilename(),
+ output.GetOutputFilename() + '.tmp')
+ write_file = not files_match
+ if write_file:
+ 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:
+ self.VerboseOut(warnings)
+ if self.res.UberClique().HasMissingTranslations():
+ print self.res.UberClique().missing_translations_
+ sys.exit(-1)
+
+
+ def CheckAssertedOutputFiles(self, assert_output_files):
+ '''Checks that the asserted output files are specified in the given list.
+
+ Returns true if the asserted files are present. If they are not, returns
+ False and prints the failure.
+ '''
+ # Compare the absolute path names, sorted.
+ asserted = sorted([os.path.abspath(i) for i in assert_output_files])
+ actual = sorted([
+ os.path.abspath(os.path.join(self.output_directory, i.GetFilename()))
+ for i in self.res.GetOutputFiles()])
+
+ if asserted != actual:
+ missing = list(set(actual) - set(asserted))
+ extra = list(set(asserted) - set(actual))
+ error = '''Asserted file list does not match.
+
+Expected output files:
+%s
+Actual output files:
+%s
+Missing output files:
+%s
+Extra output files:
+%s
+'''
+ print error % ('\n'.join(asserted), '\n'.join(actual), '\n'.join(missing),
+ '\n'.join(extra))
+ return False
+ return True
+
+
+ def GenerateDepfile(self, depfile, depdir, first_ids_file, depend_on_stamp):
+ '''Generate a depfile that contains the imlicit dependencies of the input
+ grd. The depfile will be in the same format as a makefile, and will contain
+ references to files relative to |depdir|. It will be put in |depfile|.
+
+ For example, supposing we have three files in a directory src/
+
+ src/
+ blah.grd <- depends on input{1,2}.xtb
+ input1.xtb
+ input2.xtb
+
+ and we run
+
+ grit -i blah.grd -o ../out/gen --depdir ../out --depfile ../out/gen/blah.rd.d
+
+ from the directory src/ we will generate a depfile ../out/gen/blah.grd.d
+ that has the contents
+
+ gen/blah.h: ../src/input1.xtb ../src/input2.xtb
+
+ Where "gen/blah.h" is the first output (Ninja expects the .d file to list
+ the first output in cases where there is more than one). If the flag
+ --depend-on-stamp is specified, "gen/blah.rd.d.stamp" will be used that is
+ 'touched' whenever a new depfile is generated.
+
+ Note that all paths in the depfile are relative to ../out, the depdir.
+ '''
+ depfile = os.path.abspath(depfile)
+ depdir = os.path.abspath(depdir)
+ infiles = self.res.GetInputFiles()
+
+ # We want to trigger a rebuild if the first ids change.
+ if first_ids_file is not None:
+ infiles.append(first_ids_file)
+
+ if (depend_on_stamp):
+ output_file = depfile + ".stamp"
+ # Touch the stamp file before generating the depfile.
+ with open(output_file, 'a'):
+ os.utime(output_file, None)
+ else:
+ # Get the first output file relative to the depdir.
+ outputs = self.res.GetOutputFiles()
+ output_file = os.path.join(self.output_directory,
+ outputs[0].GetFilename())
+
+ output_file = os.path.relpath(output_file, depdir)
+ # The path prefix to prepend to dependencies in the depfile.
+ prefix = os.path.relpath(os.getcwd(), depdir)
+ deps_text = ' '.join([os.path.join(prefix, i) for i in infiles])
+
+ depfile_contents = output_file + ': ' + deps_text
+ self.MakeDirectoriesTo(depfile)
+ outfile = self.fo_create(depfile, 'w', encoding='utf-8')
+ outfile.writelines(depfile_contents)
+
+ @staticmethod
+ def MakeDirectoriesTo(file):
+ '''Creates directories necessary to contain |file|.'''
+ dir = os.path.split(file)[0]
+ if not os.path.exists(dir):
+ os.makedirs(dir)
« no previous file with comments | « tools/grit/grit/tool/android2grd_unittest.py ('k') | tools/grit/grit/tool/build_unittest.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698