1#! /usr/bin/python
2# -*- coding: utf-8 -*-
3#
4# Copyright (C) 2011, International Business Machines
5# Corporation and others. All Rights Reserved.
6#
7# file name: dependencies.py
8#
9# created on: 2011may26
10
11"""Reader module for dependency data for the ICU dependency tester.
12
13Reads dependencies.txt and makes the data available.
14
15Attributes:
16  files: Set of "library/filename.o" files mentioned in the dependencies file.
17  items: Map from library or group names to item maps.
18    Each item has a "type" ("library" or "group" or "system_symbols").
19    A library or group item can have an optional set of "files" (as in the files attribute).
20    Each item can have an optional set of "deps" (libraries & groups).
21    A group item also has a "library" name unless it is a group of system symbols.
22    The one "system_symbols" item and its groups have sets of "system_symbols"
23    with standard-library system symbol names.
24  libraries: Set of library names mentioned in the dependencies file.
25"""
26__author__ = "Markus W. Scherer"
27
28# TODO: Support binary items.
29# .txt syntax:   binary: tools/genrb
30# item contents: {"type": "binary"} with optional files & deps
31# A binary must not be used as a dependency for anything else.
32
33import sys
34
35files = set()
36items = {}
37libraries = set()
38
39_line_number = 0
40_groups_to_be_defined = set()
41
42def _CheckLibraryName(name):
43  global _line_number
44  if not name:
45    sys.exit("Error:%d: \"library: \" without name" % _line_number)
46  if name.endswith(".o"):
47    sys.exit("Error:%d: invalid library name %s"  % (_line_number, name))
48
49def _CheckGroupName(name):
50  global _line_number
51  if not name:
52    sys.exit("Error:%d: \"group: \" without name" % _line_number)
53  if "/" in name or name.endswith(".o"):
54    sys.exit("Error:%d: invalid group name %s"  % (_line_number, name))
55
56def _CheckFileName(name):
57  global _line_number
58  if "/" in name or not name.endswith(".o"):
59    sys.exit("Error:%d: invalid file name %s"  % (_line_number, name))
60
61def _RemoveComment(line):
62  global _line_number
63  _line_number = _line_number + 1
64  index = line.find("#")  # Remove trailing comment.
65  if index >= 0: line = line[:index]
66  return line.rstrip()  # Remove trailing newlines etc.
67
68def _ReadLine(f):
69  while True:
70    line = _RemoveComment(f.next())
71    if line: return line
72
73def _ReadFiles(deps_file, item, library_name):
74  global files
75  item_files = item.get("files")
76  while True:
77    line = _ReadLine(deps_file)
78    if not line: continue
79    if not line.startswith("    "): return line
80    if item_files == None: item_files = item["files"] = set()
81    for file_name in line.split():
82      _CheckFileName(file_name)
83      file_name = library_name + "/" + file_name
84      if file_name in files:
85        sys.exit("Error:%d: file %s listed in multiple groups" % (_line_number, file_name))
86      files.add(file_name)
87      item_files.add(file_name)
88
89def _IsLibrary(item): return item and item["type"] == "library"
90
91def _IsLibraryGroup(item): return item and "library" in item
92
93def _ReadDeps(deps_file, item, library_name):
94  global items, _line_number, _groups_to_be_defined
95  item_deps = item.get("deps")
96  while True:
97    line = _ReadLine(deps_file)
98    if not line: continue
99    if not line.startswith("    "): return line
100    if item_deps == None: item_deps = item["deps"] = set()
101    for dep in line.split():
102      _CheckGroupName(dep)
103      dep_item = items.get(dep)
104      if item["type"] == "system_symbols" and (_IsLibraryGroup(dep_item) or _IsLibrary(dep_item)):
105        sys.exit(("Error:%d: system_symbols depend on previously defined " +
106                  "library or library group %s") % (_line_number, dep))
107      if dep_item == None:
108        # Add this dependency as a new group.
109        items[dep] = {"type": "group"}
110        if library_name: items[dep]["library"] = library_name
111        _groups_to_be_defined.add(dep)
112      item_deps.add(dep)
113
114def _AddSystemSymbol(item, symbol):
115  exports = item.get("system_symbols")
116  if exports == None: exports = item["system_symbols"] = set()
117  exports.add(symbol)
118
119def _ReadSystemSymbols(deps_file, item):
120  global _line_number
121  while True:
122    line = _ReadLine(deps_file)
123    if not line: continue
124    if not line.startswith("    "): return line
125    line = line.lstrip()
126    if '"' in line:
127      # One double-quote-enclosed symbol on the line, allows spaces in a symbol name.
128      symbol = line[1:-1]
129      if line.startswith('"') and line.endswith('"') and '"' not in symbol:
130        _AddSystemSymbol(item, symbol)
131      else:
132        sys.exit("Error:%d: invalid quoted symbol name %s" % (_line_number, line))
133    else:
134      # One or more space-separate symbols.
135      for symbol in line.split(): _AddSystemSymbol(item, symbol)
136
137def Load():
138  """Reads "dependencies.txt" and populates the module attributes."""
139  global items, libraries, _line_number, _groups_to_be_defined
140  deps_file = open("dependencies.txt")
141  try:
142    line = None
143    current_type = None
144    while True:
145      while not line: line = _RemoveComment(deps_file.next())
146
147      if line.startswith("library: "):
148        current_type = "library"
149        name = line[9:].lstrip()
150        _CheckLibraryName(name)
151        if name in items:
152          sys.exit("Error:%d: library definition using duplicate name %s" % (_line_number, name))
153        libraries.add(name)
154        item = items[name] = {"type": "library"}
155        line = _ReadFiles(deps_file, item, name)
156      elif line.startswith("group: "):
157        current_type = "group"
158        name = line[7:].lstrip()
159        _CheckGroupName(name)
160        if name not in items:
161          sys.exit("Error:%d: group %s defined before mentioned as a dependency" %
162                   (_line_number, name))
163        if name not in _groups_to_be_defined:
164          sys.exit("Error:%d: group definition using duplicate name %s" % (_line_number, name))
165        _groups_to_be_defined.remove(name)
166        item = items[name]
167        library_name = item.get("library")
168        if library_name:
169          line = _ReadFiles(deps_file, item, library_name)
170        else:
171          line = _ReadSystemSymbols(deps_file, item)
172      elif line == "  deps":
173        if current_type == "library":
174          line = _ReadDeps(deps_file, items[name], name)
175        elif current_type == "group":
176          item = items[name]
177          line = _ReadDeps(deps_file, item, item.get("library"))
178        elif current_type == "system_symbols":
179          item = items[current_type]
180          line = _ReadDeps(deps_file, item, None)
181        else:
182          sys.exit("Error:%d: deps before any library or group" % _line_number)
183      elif line == "system_symbols:":
184        current_type = "system_symbols"
185        if current_type in items:
186          sys.exit("Error:%d: duplicate entry for system_symbols" % _line_number)
187        item = items[current_type] = {"type": current_type}
188        line = _ReadSystemSymbols(deps_file, item)
189      else:
190        sys.exit("Syntax error:%d: %s" % (_line_number, line))
191  except StopIteration:
192    pass
193  if _groups_to_be_defined:
194    sys.exit("Error: some groups mentioned in dependencies are undefined: %s" % _groups_to_be_defined)
195