OLD | NEW |
(Empty) | |
| 1 #!/usr/bin/python2.4 |
| 2 # Copyright (c) 2011 The Chromium Authors. All rights reserved. |
| 3 # Use of this source code is governed by a BSD-style license that can be |
| 4 # found in the LICENSE file. |
| 5 |
| 6 '''The 'grit build' tool along with integration for this tool with the |
| 7 SCons build system. |
| 8 ''' |
| 9 |
| 10 import filecmp |
| 11 import getopt |
| 12 import os |
| 13 import shutil |
| 14 import sys |
| 15 import types |
| 16 |
| 17 from grit import grd_reader |
| 18 from grit import util |
| 19 from grit.tool import interface |
| 20 from grit import shortcuts |
| 21 |
| 22 |
| 23 def ParseDefine(define): |
| 24 '''Parses a define that is either like "NAME" or "NAME=VAL" and |
| 25 returns its components, using True as the default value. Values of |
| 26 "1" and "0" are transformed to True and False respectively. |
| 27 ''' |
| 28 parts = [part.strip() for part in define.split('=')] |
| 29 assert len(parts) >= 1 |
| 30 name = parts[0] |
| 31 val = True |
| 32 if len(parts) > 1: |
| 33 val = parts[1] |
| 34 if val == "1": val = True |
| 35 elif val == "0": val = False |
| 36 return (name, val) |
| 37 |
| 38 |
| 39 class RcBuilder(interface.Tool): |
| 40 '''A tool that builds RC files and resource header files for compilation. |
| 41 |
| 42 Usage: grit build [-o OUTPUTDIR] [-D NAME[=VAL]]* |
| 43 |
| 44 All output options for this tool are specified in the input file (see |
| 45 'grit help' for details on how to specify the input file - it is a global |
| 46 option). |
| 47 |
| 48 Options: |
| 49 |
| 50 -o OUTPUTDIR Specify what directory output paths are relative to. |
| 51 Defaults to the current directory. |
| 52 |
| 53 -D NAME[=VAL] Specify a C-preprocessor-like define NAME with optional |
| 54 value VAL (defaults to 1) which will be used to control |
| 55 conditional inclusion of resources. |
| 56 |
| 57 -E NAME=VALUE Set environment variable NAME to VALUE (within grit). |
| 58 |
| 59 -f FIRSTIDFILE Path to a python file that specifies the first id of |
| 60 value to use for resources. Defaults to the file |
| 61 resources_ids next to grit.py. Set to an empty string |
| 62 if you don't want to use a first id file. |
| 63 |
| 64 -w WHITELISTFILE Path to a file containing the string names of the |
| 65 resources to include. Anything not listed is dropped. |
| 66 |
| 67 |
| 68 Conditional inclusion of resources only affects the output of files which |
| 69 control which resources get linked into a binary, e.g. it affects .rc files |
| 70 meant for compilation but it does not affect resource header files (that define |
| 71 IDs). This helps ensure that values of IDs stay the same, that all messages |
| 72 are exported to translation interchange files (e.g. XMB files), etc. |
| 73 ''' |
| 74 |
| 75 def ShortDescription(self): |
| 76 return 'A tool that builds RC files for compilation.' |
| 77 |
| 78 def Run(self, opts, args): |
| 79 self.output_directory = '.' |
| 80 first_id_filename = None |
| 81 whitelist_filenames = [] |
| 82 (own_opts, args) = getopt.getopt(args, 'o:D:E:f:w:') |
| 83 for (key, val) in own_opts: |
| 84 if key == '-o': |
| 85 self.output_directory = val |
| 86 elif key == '-D': |
| 87 name, val = ParseDefine(val) |
| 88 self.defines[name] = val |
| 89 elif key == '-E': |
| 90 (env_name, env_value) = val.split('=') |
| 91 os.environ[env_name] = env_value |
| 92 elif key == '-f': |
| 93 first_id_filename = val |
| 94 elif key == '-w': |
| 95 whitelist_filenames.append(val) |
| 96 |
| 97 if len(args): |
| 98 print "This tool takes no tool-specific arguments." |
| 99 return 2 |
| 100 self.SetOptions(opts) |
| 101 if self.scons_targets: |
| 102 self.VerboseOut('Using SCons targets to identify files to output.\n') |
| 103 else: |
| 104 self.VerboseOut('Output directory: %s (absolute path: %s)\n' % |
| 105 (self.output_directory, |
| 106 os.path.abspath(self.output_directory))) |
| 107 |
| 108 if whitelist_filenames: |
| 109 self.whitelist_names = set() |
| 110 for whitelist_filename in whitelist_filenames: |
| 111 self.VerboseOut('Using whitelist: %s\n' % whitelist_filename); |
| 112 whitelist_file = open(whitelist_filename) |
| 113 self.whitelist_names |= set(whitelist_file.read().strip().split('\n')) |
| 114 whitelist_file.close() |
| 115 |
| 116 self.res = grd_reader.Parse(opts.input, first_id_filename=first_id_filename, |
| 117 debug=opts.extra_verbose, defines=self.defines) |
| 118 self.res.RunGatherers(recursive = True) |
| 119 self.Process() |
| 120 return 0 |
| 121 |
| 122 def __init__(self): |
| 123 # Default file-creation function is built-in file(). Only done to allow |
| 124 # overriding by unit test. |
| 125 self.fo_create = file |
| 126 |
| 127 # key/value pairs of C-preprocessor like defines that are used for |
| 128 # conditional output of resources |
| 129 self.defines = {} |
| 130 |
| 131 # self.res is a fully-populated resource tree if Run() |
| 132 # has been called, otherwise None. |
| 133 self.res = None |
| 134 |
| 135 # Set to a list of filenames for the output nodes that are relative |
| 136 # to the current working directory. They are in the same order as the |
| 137 # output nodes in the file. |
| 138 self.scons_targets = None |
| 139 |
| 140 # The set of names that are whitelisted to actually be included in the |
| 141 # output. |
| 142 self.whitelist_names = None |
| 143 |
| 144 |
| 145 # static method |
| 146 def AddWhitelistTags(start_node, whitelist_names): |
| 147 # Walk the tree of nodes added attributes for the nodes that shouldn't |
| 148 # be written into the target files (skip markers). |
| 149 from grit.node import include |
| 150 from grit.node import message |
| 151 for node in start_node.inorder(): |
| 152 # Same trick data_pack.py uses to see what nodes actually result in |
| 153 # real items. |
| 154 if (isinstance(node, include.IncludeNode) or |
| 155 isinstance(node, message.MessageNode)): |
| 156 text_ids = node.GetTextualIds() |
| 157 # Mark the item to be skipped if it wasn't in the whitelist. |
| 158 if text_ids and not text_ids[0] in whitelist_names: |
| 159 node.SetWhitelistMarkedAsSkip(True) |
| 160 AddWhitelistTags = staticmethod(AddWhitelistTags) |
| 161 |
| 162 # static method |
| 163 def ProcessNode(node, output_node, outfile): |
| 164 '''Processes a node in-order, calling its formatter before and after |
| 165 recursing to its children. |
| 166 |
| 167 Args: |
| 168 node: grit.node.base.Node subclass |
| 169 output_node: grit.node.io.File |
| 170 outfile: open filehandle |
| 171 ''' |
| 172 # See if the node should be skipped by a whitelist. |
| 173 # Note: Some Format calls have side effects, so Format is always called |
| 174 # and the whitelist is used to only avoid the output. |
| 175 should_write = not node.WhitelistMarkedAsSkip() |
| 176 |
| 177 base_dir = util.dirname(output_node.GetOutputFilename()) |
| 178 |
| 179 try: |
| 180 formatter = node.ItemFormatter(output_node.GetType()) |
| 181 if formatter: |
| 182 formatted = formatter.Format(node, output_node.GetLanguage(), |
| 183 begin_item=True, output_dir=base_dir) |
| 184 if should_write: |
| 185 outfile.write(formatted) |
| 186 except: |
| 187 print u"Error processing node %s" % unicode(node) |
| 188 raise |
| 189 |
| 190 for child in node.children: |
| 191 RcBuilder.ProcessNode(child, output_node, outfile) |
| 192 |
| 193 try: |
| 194 if formatter: |
| 195 formatted = formatter.Format(node, output_node.GetLanguage(), |
| 196 begin_item=False, output_dir=base_dir) |
| 197 if should_write: |
| 198 outfile.write(formatted) |
| 199 except: |
| 200 print u"Error processing node %s" % unicode(node) |
| 201 raise |
| 202 ProcessNode = staticmethod(ProcessNode) |
| 203 |
| 204 |
| 205 def Process(self): |
| 206 # Update filenames with those provided by SCons if we're being invoked |
| 207 # from SCons. The list of SCons targets also includes all <structure> |
| 208 # node outputs, but it starts with our output files, in the order they |
| 209 # occur in the .grd |
| 210 if self.scons_targets: |
| 211 assert len(self.scons_targets) >= len(self.res.GetOutputFiles()) |
| 212 outfiles = self.res.GetOutputFiles() |
| 213 for ix in range(len(outfiles)): |
| 214 outfiles[ix].output_filename = os.path.abspath( |
| 215 self.scons_targets[ix]) |
| 216 else: |
| 217 for output in self.res.GetOutputFiles(): |
| 218 output.output_filename = os.path.abspath(os.path.join( |
| 219 self.output_directory, output.GetFilename())) |
| 220 |
| 221 # If there are whitelisted names, tag the tree once up front, this way |
| 222 # while looping through the actual output, it is just an attribute check. |
| 223 if self.whitelist_names: |
| 224 self.AddWhitelistTags(self.res, self.whitelist_names) |
| 225 |
| 226 for output in self.res.GetOutputFiles(): |
| 227 self.VerboseOut('Creating %s...' % output.GetFilename()) |
| 228 |
| 229 # Microsoft's RC compiler can only deal with single-byte or double-byte |
| 230 # files (no UTF-8), so we make all RC files UTF-16 to support all |
| 231 # character sets. |
| 232 if output.GetType() in ('rc_header', 'resource_map_header', |
| 233 'resource_map_source', 'resource_file_map_source'): |
| 234 encoding = 'cp1252' |
| 235 elif output.GetType() in ('js_map_format', 'plist', 'plist_strings', |
| 236 'doc', 'json'): |
| 237 encoding = 'utf_8' |
| 238 else: |
| 239 # TODO(gfeher) modify here to set utf-8 encoding for admx/adml |
| 240 encoding = 'utf_16' |
| 241 |
| 242 # Make the output directory if it doesn't exist. |
| 243 outdir = os.path.split(output.GetOutputFilename())[0] |
| 244 if not os.path.exists(outdir): |
| 245 os.makedirs(outdir) |
| 246 # Write the results to a temporary file and only overwrite the original |
| 247 # if the file changed. This avoids unnecessary rebuilds. |
| 248 outfile = self.fo_create(output.GetOutputFilename() + '.tmp', 'wb') |
| 249 |
| 250 if output.GetType() != 'data_package': |
| 251 outfile = util.WrapOutputStream(outfile, encoding) |
| 252 |
| 253 # Set the context, for conditional inclusion of resources |
| 254 self.res.SetOutputContext(output.GetLanguage(), self.defines) |
| 255 |
| 256 # TODO(joi) Handle this more gracefully |
| 257 import grit.format.rc_header |
| 258 grit.format.rc_header.Item.ids_ = {} |
| 259 |
| 260 # Iterate in-order through entire resource tree, calling formatters on |
| 261 # the entry into a node and on exit out of it. |
| 262 self.ProcessNode(self.res, output, outfile) |
| 263 outfile.close() |
| 264 |
| 265 # Now copy from the temp file back to the real output, but on Windows, |
| 266 # only if the real output doesn't exist or the contents of the file |
| 267 # changed. This prevents identical headers from being written and .cc |
| 268 # files from recompiling (which is painful on Windows). |
| 269 if not os.path.exists(output.GetOutputFilename()): |
| 270 os.rename(output.GetOutputFilename() + '.tmp', |
| 271 output.GetOutputFilename()) |
| 272 else: |
| 273 # CHROMIUM SPECIFIC CHANGE. |
| 274 # This clashes with gyp + vstudio, which expect the output timestamp |
| 275 # to change on a rebuild, even if nothing has changed. |
| 276 #files_match = filecmp.cmp(output.GetOutputFilename(), |
| 277 # output.GetOutputFilename() + '.tmp') |
| 278 #if (output.GetType() != 'rc_header' or not files_match |
| 279 # or sys.platform != 'win32'): |
| 280 shutil.copy2(output.GetOutputFilename() + '.tmp', |
| 281 output.GetOutputFilename()) |
| 282 os.remove(output.GetOutputFilename() + '.tmp') |
| 283 |
| 284 self.VerboseOut(' done.\n') |
| 285 |
| 286 # Print warnings if there are any duplicate shortcuts. |
| 287 warnings = shortcuts.GenerateDuplicateShortcutsWarnings( |
| 288 self.res.UberClique(), self.res.GetTcProject()) |
| 289 if warnings: |
| 290 print '\n'.join(warnings) |
| 291 |
| 292 # Print out any fallback warnings, and missing translation errors, and |
| 293 # exit with an error code if there are missing translations in a non-pseudo |
| 294 # and non-official build. |
| 295 warnings = (self.res.UberClique().MissingTranslationsReport(). |
| 296 encode('ascii', 'replace')) |
| 297 if warnings and self.defines.get('_google_chrome', False): |
| 298 print warnings |
| 299 if self.res.UberClique().HasMissingTranslations(): |
| 300 sys.exit(-1) |
OLD | NEW |