Home | History | Annotate | Download | only in format
      1 #!/usr/bin/env python
      2 # Copyright (c) 2012 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 '''Item formatters for RC headers.
      7 '''
      8 
      9 from grit import exception
     10 from grit import util
     11 from grit.extern import FP
     12 
     13 
     14 def Format(root, lang='en', output_dir='.'):
     15   yield '''\
     16 // This file is automatically generated by GRIT. Do not edit.
     17 
     18 #pragma once
     19 '''
     20   # Check for emit nodes under the rc_header. If any emit node
     21   # is present, we assume it means the GRD file wants to override
     22   # the default header, with no includes.
     23   default_includes = ['#include <atlres.h>', '']
     24   emit_lines = []
     25   for output_node in root.GetOutputFiles():
     26     if output_node.GetType() == 'rc_header':
     27       for child in output_node.children:
     28         if child.name == 'emit' and child.attrs['emit_type'] == 'prepend':
     29           emit_lines.append(child.GetCdata())
     30   for line in emit_lines or default_includes:
     31     yield line + '\n'
     32 
     33   for line in FormatDefines(root, root.ShouldOutputAllResourceDefines()):
     34     yield line
     35 
     36 
     37 def FormatDefines(root, output_all_resource_defines=True):
     38   '''Yields #define SYMBOL 1234 lines.
     39 
     40   Args:
     41     root: A GritNode.
     42     output_all_resource_defines: If False, output only the symbols used in the
     43       current output configuration.
     44   '''
     45   from grit.node import message
     46   tids = GetIds(root)
     47 
     48   if output_all_resource_defines:
     49     items = root.Preorder()
     50   else:
     51     items = root.ActiveDescendants()
     52 
     53   seen = set()
     54   for item in items:
     55     if not isinstance(item, message.MessageNode):
     56       with item:
     57         for tid in item.GetTextualIds():
     58           if tid in tids and tid not in seen:
     59             seen.add(tid)
     60             yield '#define %s %d\n' % (tid, tids[tid])
     61   # Temporarily mimic old behavior: MessageNodes were only output if active,
     62   # even with output_all_resource_defines set. TODO(benrg): Remove this after
     63   # fixing problems in the Chrome tree.
     64   for item in root.ActiveDescendants():
     65     if isinstance(item, message.MessageNode):
     66       with item:
     67         for tid in item.GetTextualIds():
     68           if tid in tids and tid not in seen:
     69             seen.add(tid)
     70             yield '#define %s %d\n' % (tid, tids[tid])
     71 
     72 
     73 _cached_ids = {}
     74 
     75 
     76 def GetIds(root):
     77   '''Return a dictionary mapping textual ids to numeric ids for the given tree.
     78 
     79   Args:
     80     root: A GritNode.
     81   '''
     82   # TODO(benrg): Since other formatters use this, it might make sense to move it
     83   # and _ComputeIds to GritNode and store the cached ids as an attribute. On the
     84   # other hand, GritNode has too much random stuff already.
     85   if root not in _cached_ids:
     86     _cached_ids[root] = _ComputeIds(root)
     87   return _cached_ids[root]
     88 
     89 
     90 def _ComputeIds(root):
     91   from grit.node import empty, include, message, misc, structure
     92 
     93   ids = {}  # Maps numeric id to textual id
     94   tids = {}  # Maps textual id to numeric id
     95   id_reasons = {}  # Maps numeric id to text id and a human-readable explanation
     96   group = None
     97   last_id = None
     98 
     99   for item in root:
    100     if isinstance(item, empty.GroupingNode):
    101       # Note: this won't work if any GroupingNode can be contained inside
    102       # another.
    103       group = item
    104       last_id = None
    105       continue
    106 
    107     assert not item.GetTextualIds() or isinstance(item,
    108         (include.IncludeNode, message.MessageNode,
    109          misc.IdentifierNode, structure.StructureNode))
    110 
    111     # Resources that use the RES protocol don't need
    112     # any numerical ids generated, so we skip them altogether.
    113     # This is accomplished by setting the flag 'generateid' to false
    114     # in the GRD file.
    115     if item.attrs.get('generateid', 'true') == 'false':
    116       continue
    117 
    118     for tid in item.GetTextualIds():
    119       if util.SYSTEM_IDENTIFIERS.match(tid):
    120         # Don't emit a new ID for predefined IDs
    121         continue
    122 
    123       if tid in tids:
    124         continue
    125 
    126       # Some identifier nodes can provide their own id,
    127       # and we use that id in the generated header in that case.
    128       if hasattr(item, 'GetId') and item.GetId():
    129         id = long(item.GetId())
    130         reason = 'returned by GetId() method'
    131 
    132       elif ('offset' in item.attrs and group and
    133             group.attrs.get('first_id', '') != ''):
    134          offset_text = item.attrs['offset']
    135          parent_text = group.attrs['first_id']
    136 
    137          try:
    138           offset_id = long(offset_text)
    139          except ValueError:
    140           offset_id = tids[offset_text]
    141 
    142          try:
    143           parent_id = long(parent_text)
    144          except ValueError:
    145           parent_id = tids[parent_text]
    146 
    147          id = parent_id + offset_id
    148          reason = 'first_id %d + offset %d' % (parent_id, offset_id)
    149 
    150       # We try to allocate IDs sequentially for blocks of items that might
    151       # be related, for instance strings in a stringtable (as their IDs might be
    152       # used e.g. as IDs for some radio buttons, in which case the IDs must
    153       # be sequential).
    154       #
    155       # We do this by having the first item in a section store its computed ID
    156       # (computed from a fingerprint) in its parent object.  Subsequent children
    157       # of the same parent will then try to get IDs that sequentially follow
    158       # the currently stored ID (on the parent) and increment it.
    159       elif last_id is None:
    160         # First check if the starting ID is explicitly specified by the parent.
    161         if group and group.attrs.get('first_id', '') != '':
    162           id = long(group.attrs['first_id'])
    163           reason = "from parent's first_id attribute"
    164         else:
    165           # Automatically generate the ID based on the first clique from the
    166           # first child of the first child node of our parent (i.e. when we
    167           # first get to this location in the code).
    168 
    169           # According to
    170           # http://msdn.microsoft.com/en-us/library/t2zechd4(VS.71).aspx
    171           # the safe usable range for resource IDs in Windows is from decimal
    172           # 101 to 0x7FFF.
    173 
    174           id = FP.UnsignedFingerPrint(tid)
    175           id = id % (0x7FFF - 101) + 101
    176           reason = 'chosen by random fingerprint -- use first_id to override'
    177 
    178         last_id = id
    179       else:
    180         id = last_id = last_id + 1
    181         reason = 'sequentially assigned'
    182 
    183       reason = "%s (%s)" % (tid, reason)
    184       # Don't fail when 'offset' is specified, as the base and the 0th
    185       # offset will have the same ID.
    186       if id in id_reasons and not 'offset' in item.attrs:
    187         raise exception.IdRangeOverlap('ID %d was assigned to both %s and %s.'
    188                                        % (id, id_reasons[id], reason))
    189 
    190       if id < 101:
    191         print ('WARNING: Numeric resource IDs should be greater than 100 to\n'
    192                'avoid conflicts with system-defined resource IDs.')
    193 
    194       ids[id] = tid
    195       tids[tid] = id
    196       id_reasons[id] = reason
    197 
    198   return tids
    199