195640e3a20adea634b4df4ccf8c93f411184c438joi@chromium.org#!/usr/bin/env python
295640e3a20adea634b4df4ccf8c93f411184c438joi@chromium.org# Copyright (c) 2012 The Chromium Authors. All rights reserved.
301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org# Use of this source code is governed by a BSD-style license that can be
401b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org# found in the LICENSE file.
501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org'''Item formatters for RC headers.
701b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org'''
801b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
901b3bc768461bd303bff39f8cd1663682254e407joi@chromium.orgfrom grit import exception
1001b3bc768461bd303bff39f8cd1663682254e407joi@chromium.orgfrom grit import util
1101b3bc768461bd303bff39f8cd1663682254e407joi@chromium.orgfrom grit.extern import FP
1201b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
1301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
14ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.orgdef Format(root, lang='en', output_dir='.'):
15ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org  yield '''\
1601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org// This file is automatically generated by GRIT. Do not edit.
1701b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
1801b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org#pragma once
19d4edc6c61f2135b39318e34f9b342b673133e6ccbenrg@chromium.org'''
20ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org  # Check for emit nodes under the rc_header. If any emit node
21ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org  # is present, we assume it means the GRD file wants to override
22ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org  # the default header, with no includes.
23ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org  default_includes = ['#include <atlres.h>', '']
24ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org  emit_lines = []
25ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org  for output_node in root.GetOutputFiles():
26ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org    if output_node.GetType() == 'rc_header':
27ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org      for child in output_node.children:
28ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org        if child.name == 'emit' and child.attrs['emit_type'] == 'prepend':
29ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org          emit_lines.append(child.GetCdata())
30ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org  for line in emit_lines or default_includes:
31ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org    yield line + '\n'
32ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org
33db2e077842c311c22eb33a13cc9eb9fadb7def5ajoi@chromium.org  for line in FormatDefines(root, root.ShouldOutputAllResourceDefines(),
34db2e077842c311c22eb33a13cc9eb9fadb7def5ajoi@chromium.org                            root.GetRcHeaderFormat()):
35ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org    yield line
36ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org
37ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org
38db2e077842c311c22eb33a13cc9eb9fadb7def5ajoi@chromium.orgdef FormatDefines(root, output_all_resource_defines=True,
39db2e077842c311c22eb33a13cc9eb9fadb7def5ajoi@chromium.org                  rc_header_format=None):
40ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org  '''Yields #define SYMBOL 1234 lines.
41ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org
42ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org  Args:
43ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org    root: A GritNode.
44ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org    output_all_resource_defines: If False, output only the symbols used in the
45ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org      current output configuration.
46ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org  '''
47ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org  from grit.node import message
48ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org  tids = GetIds(root)
49ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org
50ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org  if output_all_resource_defines:
51ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org    items = root.Preorder()
52ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org  else:
53ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org    items = root.ActiveDescendants()
54ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org
55db2e077842c311c22eb33a13cc9eb9fadb7def5ajoi@chromium.org  if not rc_header_format:
5677646803647fbfac846a54cc4e42cc265a320d6fjoi@chromium.org    rc_header_format = "#define {textual_id} {numeric_id}"
5777646803647fbfac846a54cc4e42cc265a320d6fjoi@chromium.org  rc_header_format += "\n"
58ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org  seen = set()
59ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org  for item in items:
60ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org    if not isinstance(item, message.MessageNode):
61ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org      with item:
62ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org        for tid in item.GetTextualIds():
63ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org          if tid in tids and tid not in seen:
64ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org            seen.add(tid)
65db2e077842c311c22eb33a13cc9eb9fadb7def5ajoi@chromium.org            yield rc_header_format.format(textual_id=tid,numeric_id=tids[tid])
66db2e077842c311c22eb33a13cc9eb9fadb7def5ajoi@chromium.org
67ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org  # Temporarily mimic old behavior: MessageNodes were only output if active,
68ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org  # even with output_all_resource_defines set. TODO(benrg): Remove this after
69ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org  # fixing problems in the Chrome tree.
70ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org  for item in root.ActiveDescendants():
71ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org    if isinstance(item, message.MessageNode):
72ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org      with item:
73ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org        for tid in item.GetTextualIds():
74ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org          if tid in tids and tid not in seen:
75ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org            seen.add(tid)
76db2e077842c311c22eb33a13cc9eb9fadb7def5ajoi@chromium.org            yield rc_header_format.format(textual_id=tid,numeric_id=tids[tid])
77ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org
78ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org
79ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org_cached_ids = {}
80ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org
81ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org
82ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.orgdef GetIds(root):
83ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org  '''Return a dictionary mapping textual ids to numeric ids for the given tree.
84ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org
85ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org  Args:
86ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org    root: A GritNode.
87ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org  '''
88ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org  # TODO(benrg): Since other formatters use this, it might make sense to move it
89ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org  # and _ComputeIds to GritNode and store the cached ids as an attribute. On the
90ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org  # other hand, GritNode has too much random stuff already.
91ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org  if root not in _cached_ids:
92ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org    _cached_ids[root] = _ComputeIds(root)
93ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org  return _cached_ids[root]
94ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org
95ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org
96ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.orgdef _ComputeIds(root):
97ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org  from grit.node import empty, include, message, misc, structure
98ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org
99ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org  ids = {}  # Maps numeric id to textual id
100ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org  tids = {}  # Maps textual id to numeric id
101ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org  id_reasons = {}  # Maps numeric id to text id and a human-readable explanation
102ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org  group = None
103ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org  last_id = None
104ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org
105ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org  for item in root:
106ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org    if isinstance(item, empty.GroupingNode):
107ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org      # Note: this won't work if any GroupingNode can be contained inside
108ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org      # another.
109ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org      group = item
110ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org      last_id = None
111ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org      continue
112ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org
113ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org    assert not item.GetTextualIds() or isinstance(item,
114ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org        (include.IncludeNode, message.MessageNode,
115ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org         misc.IdentifierNode, structure.StructureNode))
11601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
11701b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    # Resources that use the RES protocol don't need
11801b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    # any numerical ids generated, so we skip them altogether.
11901b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    # This is accomplished by setting the flag 'generateid' to false
12001b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org    # in the GRD file.
121ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org    if item.attrs.get('generateid', 'true') == 'false':
122ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org      continue
123ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org
124ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org    for tid in item.GetTextualIds():
12501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      if util.SYSTEM_IDENTIFIERS.match(tid):
12601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org        # Don't emit a new ID for predefined IDs
12701b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org        continue
12801b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
129ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org      if tid in tids:
130ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org        continue
131ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org
13201b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      # Some identifier nodes can provide their own id,
13301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      # and we use that id in the generated header in that case.
13401b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      if hasattr(item, 'GetId') and item.GetId():
13501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org        id = long(item.GetId())
136ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org        reason = 'returned by GetId() method'
13701b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
138ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org      elif ('offset' in item.attrs and group and
139ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org            group.attrs.get('first_id', '') != ''):
14001b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org         offset_text = item.attrs['offset']
141ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org         parent_text = group.attrs['first_id']
14201b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
14301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org         try:
14401b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org          offset_id = long(offset_text)
14501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org         except ValueError:
146ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org          offset_id = tids[offset_text]
14701b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
14801b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org         try:
14901b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org          parent_id = long(parent_text)
15001b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org         except ValueError:
151ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org          parent_id = tids[parent_text]
15201b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
15301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org         id = parent_id + offset_id
154ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org         reason = 'first_id %d + offset %d' % (parent_id, offset_id)
15501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
15601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      # We try to allocate IDs sequentially for blocks of items that might
15701b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      # be related, for instance strings in a stringtable (as their IDs might be
15801b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      # used e.g. as IDs for some radio buttons, in which case the IDs must
15901b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      # be sequential).
16001b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      #
16101b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      # We do this by having the first item in a section store its computed ID
16201b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      # (computed from a fingerprint) in its parent object.  Subsequent children
16301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      # of the same parent will then try to get IDs that sequentially follow
16401b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      # the currently stored ID (on the parent) and increment it.
165ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org      elif last_id is None:
16601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org        # First check if the starting ID is explicitly specified by the parent.
167ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org        if group and group.attrs.get('first_id', '') != '':
168ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org          id = long(group.attrs['first_id'])
169ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org          reason = "from parent's first_id attribute"
17001b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org        else:
17101b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org          # Automatically generate the ID based on the first clique from the
17201b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org          # first child of the first child node of our parent (i.e. when we
17301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org          # first get to this location in the code).
17401b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
17501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org          # According to
17601b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org          # http://msdn.microsoft.com/en-us/library/t2zechd4(VS.71).aspx
17701b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org          # the safe usable range for resource IDs in Windows is from decimal
17801b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org          # 101 to 0x7FFF.
17901b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
18001b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org          id = FP.UnsignedFingerPrint(tid)
181ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org          id = id % (0x7FFF - 101) + 101
182ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org          reason = 'chosen by random fingerprint -- use first_id to override'
18301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
184ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org        last_id = id
18501b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org      else:
186ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org        id = last_id = last_id + 1
187ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org        reason = 'sequentially assigned'
188ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org
189ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org      reason = "%s (%s)" % (tid, reason)
190528c23465b5b57bd81eab467504be053be6f1be0joi@chromium.org      # Don't fail when 'offset' is specified, as the base and the 0th
191528c23465b5b57bd81eab467504be053be6f1be0joi@chromium.org      # offset will have the same ID.
192528c23465b5b57bd81eab467504be053be6f1be0joi@chromium.org      if id in id_reasons and not 'offset' in item.attrs:
193ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org        raise exception.IdRangeOverlap('ID %d was assigned to both %s and %s.'
194ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org                                       % (id, id_reasons[id], reason))
195ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org
196ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org      if id < 101:
197ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org        print ('WARNING: Numeric resource IDs should be greater than 100 to\n'
198ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org               'avoid conflicts with system-defined resource IDs.')
199ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org
200ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org      ids[id] = tid
201ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org      tids[tid] = id
202ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org      id_reasons[id] = reason
20301b3bc768461bd303bff39f8cd1663682254e407joi@chromium.org
204ccda47032903a6550dac2921f88c51b4da55aa36benrg@chromium.org  return tids
205