blob: 74e7127b5840b57a90aa022737c721f638280b33 [file] [log] [blame]
#!/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.
'''Item formatters for RC headers.
'''
from grit import exception
from grit import util
from grit.extern import FP
def Format(root, lang='en', output_dir='.'):
yield '''\
// This file is automatically generated by GRIT. Do not edit.
#pragma once
'''
# Check for emit nodes under the rc_header. If any emit node
# is present, we assume it means the GRD file wants to override
# the default header, with no includes.
default_includes = ['#include <atlres.h>', '']
emit_lines = []
for output_node in root.GetOutputFiles():
if output_node.GetType() == 'rc_header':
for child in output_node.children:
if child.name == 'emit' and child.attrs['emit_type'] == 'prepend':
emit_lines.append(child.GetCdata())
for line in emit_lines or default_includes:
yield line + '\n'
for line in FormatDefines(root, root.ShouldOutputAllResourceDefines(),
root.GetRcHeaderFormat()):
yield line
def FormatDefines(root, output_all_resource_defines=True,
rc_header_format=None):
'''Yields #define SYMBOL 1234 lines.
Args:
root: A GritNode.
output_all_resource_defines: If False, output only the symbols used in the
current output configuration.
'''
from grit.node import message
tids = GetIds(root)
if output_all_resource_defines:
items = root.Preorder()
else:
items = root.ActiveDescendants()
if not rc_header_format:
rc_header_format = "#define {textual_id} {numeric_id}"
rc_header_format += "\n"
seen = set()
for item in items:
if not isinstance(item, message.MessageNode):
with item:
for tid in item.GetTextualIds():
if tid in tids and tid not in seen:
seen.add(tid)
yield rc_header_format.format(textual_id=tid,numeric_id=tids[tid])
# Temporarily mimic old behavior: MessageNodes were only output if active,
# even with output_all_resource_defines set. TODO(benrg): Remove this after
# fixing problems in the Chrome tree.
for item in root.ActiveDescendants():
if isinstance(item, message.MessageNode):
with item:
for tid in item.GetTextualIds():
if tid in tids and tid not in seen:
seen.add(tid)
yield rc_header_format.format(textual_id=tid,numeric_id=tids[tid])
_cached_ids = {}
def GetIds(root):
'''Return a dictionary mapping textual ids to numeric ids for the given tree.
Args:
root: A GritNode.
'''
# TODO(benrg): Since other formatters use this, it might make sense to move it
# and _ComputeIds to GritNode and store the cached ids as an attribute. On the
# other hand, GritNode has too much random stuff already.
if root not in _cached_ids:
_cached_ids[root] = _ComputeIds(root)
return _cached_ids[root]
def _ComputeIds(root):
from grit.node import empty, include, message, misc, structure
ids = {} # Maps numeric id to textual id
tids = {} # Maps textual id to numeric id
id_reasons = {} # Maps numeric id to text id and a human-readable explanation
group = None
last_id = None
for item in root:
if isinstance(item, empty.GroupingNode):
# Note: this won't work if any GroupingNode can be contained inside
# another.
group = item
last_id = None
continue
assert not item.GetTextualIds() or isinstance(item,
(include.IncludeNode, message.MessageNode,
misc.IdentifierNode, structure.StructureNode))
# Resources that use the RES protocol don't need
# any numerical ids generated, so we skip them altogether.
# This is accomplished by setting the flag 'generateid' to false
# in the GRD file.
if item.attrs.get('generateid', 'true') == 'false':
continue
for tid in item.GetTextualIds():
if util.SYSTEM_IDENTIFIERS.match(tid):
# Don't emit a new ID for predefined IDs
continue
if tid in tids:
continue
# Some identifier nodes can provide their own id,
# and we use that id in the generated header in that case.
if hasattr(item, 'GetId') and item.GetId():
id = long(item.GetId())
reason = 'returned by GetId() method'
elif ('offset' in item.attrs and group and
group.attrs.get('first_id', '') != ''):
offset_text = item.attrs['offset']
parent_text = group.attrs['first_id']
try:
offset_id = long(offset_text)
except ValueError:
offset_id = tids[offset_text]
try:
parent_id = long(parent_text)
except ValueError:
parent_id = tids[parent_text]
id = parent_id + offset_id
reason = 'first_id %d + offset %d' % (parent_id, offset_id)
# We try to allocate IDs sequentially for blocks of items that might
# be related, for instance strings in a stringtable (as their IDs might be
# used e.g. as IDs for some radio buttons, in which case the IDs must
# be sequential).
#
# We do this by having the first item in a section store its computed ID
# (computed from a fingerprint) in its parent object. Subsequent children
# of the same parent will then try to get IDs that sequentially follow
# the currently stored ID (on the parent) and increment it.
elif last_id is None:
# First check if the starting ID is explicitly specified by the parent.
if group and group.attrs.get('first_id', '') != '':
id = long(group.attrs['first_id'])
reason = "from parent's first_id attribute"
else:
# Automatically generate the ID based on the first clique from the
# first child of the first child node of our parent (i.e. when we
# first get to this location in the code).
# According to
# http://msdn.microsoft.com/en-us/library/t2zechd4(VS.71).aspx
# the safe usable range for resource IDs in Windows is from decimal
# 101 to 0x7FFF.
id = FP.UnsignedFingerPrint(tid)
id = id % (0x7FFF - 101) + 101
reason = 'chosen by random fingerprint -- use first_id to override'
last_id = id
else:
id = last_id = last_id + 1
reason = 'sequentially assigned'
reason = "%s (%s)" % (tid, reason)
# Don't fail when 'offset' is specified, as the base and the 0th
# offset will have the same ID.
if id in id_reasons and not 'offset' in item.attrs:
raise exception.IdRangeOverlap('ID %d was assigned to both %s and %s.'
% (id, id_reasons[id], reason))
if id < 101:
print ('WARNING: Numeric resource IDs should be greater than 100 to\n'
'avoid conflicts with system-defined resource IDs.')
ids[id] = tid
tids[tid] = id
id_reasons[id] = reason
return tids