]> rtime.felk.cvut.cz Git - omk.git/blob - omkbuild.py
Added support for .idl compilation
[omk.git] / omkbuild.py
1 #!/usr/bin/env python
2 """
3 This script has two main functions:
4
5   1) Combine several snippets to form Makefile.rules file
6   2) Split Makefile.rules file to the originnal snippets
7
8 Snippet syntax:
9
10   snippet ::= legal? documentation rules
11   
12   legal ::= comment* empty-comment empty-comment
13   documentation := comment*
14   rules ::= text
15   
16   comment ::= '#' text '\n'
17   empty-comment ::= '#' '\n'
18   text ::= [^#] ... '\n'
19
20  Makefile.rules policies:
21
22    * All the parts of the Makefile.rules are ordered in the same order
23      as they are in snippets i.e. copyrights, documentations and rules.
24
25    * On the first line of each part of the Makefile.rules, there is
26      special mark of the form:
27
28        #OMK:<snippet file name>[@<included from snippet>]<EOL>
29
30      This mark is used for splitting modified Makefile.rules back to
31      the original snippets. If <snippet file name> starts with __, it
32      is ignored during splitting.
33
34    * Toplevel snippet has name in the forb Makefile.rules.* and no
35      other (included) snippet has such name.
36 """
37
38 from optparse import OptionParser
39 import os
40 import os.path
41 import sys
42 import string
43 import re
44
45 rulesDir = "rules"
46 snippetsDir = "snippets"
47
48 class LineList(list):
49     """List of text lines"""
50     def getDiff(self, other):
51         s = ''
52         for i in range(len(self)):
53             if i >= len(other):
54                 s += ("  Line %d differs!\n" % i)
55                 s += "  -"+self[i].rstrip() + "\n"
56                 s += "  +\n"
57                 break
58             if self[i] != other[i]:
59                 s += ("  Line %d differs!\n" % i)
60                 s += "  -"+self[i].rstrip() + "\n"
61                 s += "  +"+other[i].rstrip() + "\n"
62                 break
63         return s
64     
65     def __str__(self):
66         s = ''
67         for l in self: s += l
68         return s
69
70     def loadFromFile(self, fname):
71         """Loads itself from file."""
72         try:
73             f = open(fname, "r")
74         except IOError:
75             sys.stderr.write("Cannot open %s\n" % fname)
76             sys.exit(1)
77             
78         self.extend(f.readlines())
79         f.close
80
81 class Snippet:
82     def __init__(self, fname = None, name = ""):
83         """Initializes the snippet and if fname is given, reads it
84         from file"""
85         self.name = name
86         self.legal = LineList()
87         self.doc = LineList()
88         self.code = LineList()
89         if fname: self.loadFromFile(fname)
90         
91     def loadFromFile(self, fname):
92         """Loads snippet from file."""
93         self.name = fname
94         f = open(fname, "r")
95         
96         self.readLines(f.readlines())
97          
98         f.close
99
100     def addCodeLine(self, line):
101         self.code.append(line)
102
103     def readLines(self, lines):
104         """Parses the snippet given as a list and stores it in itself."""
105         currentPart = self.legal
106         counter = 0
107
108         for line in lines:
109             if currentPart == self.legal:
110                 if line.strip() == "#": counter += 1
111                 else: counter = 0
112             if line[0] != "#": currentPart = self.code
113
114             currentPart.append(line)
115
116             if counter == 2:
117                 currentPart = self.doc
118                 counter = 0
119
120
121         if not self.doc:
122             self.doc = self.legal
123             self.legal = LineList()
124
125     def asLinesList(self):
126         lines = LineList()
127         for type in ['legal', 'doc', 'code']:
128             for line in self.__dict__[type]:
129                 lines.append(line)
130         return lines
131
132     def __str__(self):
133         return str(self.asLinesList())
134
135     def __repr__(self):
136         s = "<Snippet: %s>" % self.name
137         return s
138
139     def __cmp__(self, other):
140         ret = cmp(self.name, other.name)
141         if ret != 0: return ret
142         ret = cmp(self.legal, other.legal)
143         if ret != 0: return ret
144         ret = cmp(self.doc, other.doc)
145         if ret != 0: return ret
146         ret = cmp(self.code, other.code)
147         return ret
148
149     def __getitem__(self, key):
150         return {
151             'legal': self.legal,
152             'doc'  : self.doc,
153             'code' : self.code
154             }[key]
155
156     def getDiff(self, other):
157         return self.asLinesList().getDiff(other.asLinesList())
158
159 class Snippets:
160     """Collection of snippets, where snippets can be accessed
161     individually by name (like dictionary) or sequentionaly in the
162     order they were added."""
163     def __init__(self):
164         self._snippets = dict()
165         self._order = list()
166
167     def __iadd__(self, snippet):
168         assert isinstance(snippet, Snippet)
169         self._snippets[snippet.name] = snippet
170         self._order += [snippet]
171         return self
172
173     def __getitem__(self, key):
174         return self._snippets[key]
175
176     def __contains__(self, item):
177         return item in self._snippets
178
179     def __iter__(self):
180         return iter(self._order)
181
182     def __cmp__(self, other):
183         return cmp(self._snippets, other._snippets)
184
185     def loadFromFiles(self, fnames):
186         """Reads the snippets from several files and adds them to itself."""
187         for fn in fnames:
188             self += Snippet(fn)
189         
190     def loadFromDict(self, snipDict):
191         """Adds snippets to itself from dictionary of LineLists."""
192         for s in snipDict:
193             snip = Snippet()
194             snip.name = s
195             snip.readLines(snipDict[s])
196             self += snip
197
198     def getDiff(self, other):
199         assert isinstance(other, Snippets)
200         s = ''
201         for snip in self:
202             if (snip.name[0:2] == '__'): continue
203             if (snip != other[snip.name]):
204                 s += "Snippet %s:\n" % snip.name
205                 s += snip.getDiff(other[snip.name])
206         return s
207
208 # Include directoves matching this r.e. will be replaced by this script
209 reInclude = re.compile("^include ([^ ]*) #omkbuild")
210
211 class MakefileRules(LineList):
212     def __init__(self):
213         self.snippets = Snippets()
214         self.rules = LineList()
215
216     def _includeSnippets(self, filename, baseFileName="", onlyLoadSnippets=False):
217         """Recursively traverses snippets according to include
218         directives. If onlyLoadSnippets is True, self.rules is not
219         modified ..."""
220
221         if onlyLoadSnippets:
222             if filename in self.snippets:
223                 sys.stderr.write("Error: Snippet included more than once\n")
224                 # This is not allowed becouse it would cause problems
225                 # during spliting
226                 sys.exit(1)
227             self.snippets += Snippet(filename)
228
229         lines = self.snippets[filename]['code']
230
231         addMarker = 1 # The first line of the snippet should be marked
232         for line in lines:
233             match = reInclude.match(line)
234             if match:
235                 # Include other snippet
236                 self._includeSnippets(match.group(1).strip(),\
237                                       filename,
238                                       onlyLoadSnippets)
239                 addMarker = 2 # The next line after include should be marked
240             else:
241                 # Add this line to rules
242                 if addMarker:
243                     if addMarker==1:
244                         line = string.rstrip(line).ljust(80)+" #OMK:%s@%s\n"%(filename,baseFileName)
245                     elif addMarker==2:
246                         line = string.rstrip(line).ljust(80)+" #OMK:%s\n"%(filename)
247                     addMarker = 0
248                 if not onlyLoadSnippets:
249                     self.rules += [line]
250
251     def combineFrom(self, topLevelSnippet, onlyCheck=False):
252         """Produces self.rules from the topLevelSnippet and all
253         snippets included directly or indirectly from it."""
254         self.rules = LineList()
255
256         if not onlyCheck:
257             self._includeSnippets(topLevelSnippet, onlyLoadSnippets=True)
258
259         # Append legal and doc parts
260         for type in ['legal', 'doc']:
261             for snip in self.snippets:
262                 lines = snip[type]
263                 if len(lines) == 0: continue
264                 firstLine = string.rstrip(lines[0])
265                 self.rules += [firstLine.ljust(80)+" #OMK:%s\n"%snip.name]
266                 self.rules += lines[1:]
267                 #self.rules += ['a'] # test error
268
269         # Append code parts
270         self._includeSnippets(topLevelSnippet)
271
272
273     def split(self):
274         """Split self.rules to the original snippets in self.snippets."""
275         self.snippets = Snippets()
276         snipDict = self._getSnipDicts()
277         self.snippets.loadFromDict(snipDict)
278
279     def _getSnipDicts(self):
280         """Split self.rules to the original snippets, which are
281         returened as dictionary of LineLists."""
282         snipBegin = re.compile("^(.*)#OMK:([^@]*)(?:@(.*))?\n$")
283         snipDict = dict()
284         currentLinesList = None
285
286         for line in self.rules:
287             match = snipBegin.match(line)
288             if match:
289                 line = match.group(1).rstrip() + "\n"
290                 snipName = match.group(2)
291                 includedFrom = match.group(3)
292                 if includedFrom:
293                     if not includedFrom in snipDict: snipDict[includedFrom] = LineList()
294                     snipDict[includedFrom].append("include %s #omkbuild\n" % snipName);
295
296                 if not snipName in snipDict:
297                     snipDict[snipName] = LineList()
298                 currentLinesList = snipDict[snipName]
299             
300             if currentLinesList != None:
301                 currentLinesList.append(line);
302                 
303         return snipDict
304
305
306 def parseCommandLine():
307     parser = OptionParser(usage = """
308     %prog [-o FILE] top-level-snippet      build Makefile.rules from the top-level-snippet and included ones
309     %prog [-o - ] -s Makfile.rules
310     """)
311     parser.add_option("-s", "--split",
312                       action="store", dest="split", default=False, metavar="RULES",
313                       help="Split given Makefile.rules to the original snippets")
314     parser.add_option("-o", "--output",
315                       action="store", dest="output", default=False, metavar="RULES",
316                       help="Write Makefile.rules to file RULES")
317     (options, args) = parser.parse_args()
318     if len(args) > 1:
319         parser.print_help()
320         sys.exit(1)
321     return options, args
322
323 def buildRules(topLevelSnippet, output):
324     rules = MakefileRules()
325     rules.combineFrom(topLevelSnippet)
326
327     rulesCheck = MakefileRules()
328     rulesCheck.rules = rules.rules
329     rulesCheck.split()
330
331     if rules.snippets != rulesCheck.snippets:
332         sys.stderr.write("Consistency error:\n")
333         diff = rules.snippets.getDiff(rulesCheck.snippets)
334         sys.stderr.write(diff)
335         sys.exit(1)
336         
337     if output:
338         try: os.makedirs(os.path.dirname(output))
339         except: pass
340         f = open(output,"w+")
341     else: f = sys.stdout
342     f.writelines(rules.rules)
343     f.close()
344
345 def splitRules(rulesFN, output):
346     rules = MakefileRules()
347     rules.rules.loadFromFile(rulesFN)
348     rules.split()
349
350     rulesCheck = MakefileRules()
351     rulesCheck.snippets = rules.snippets
352
353     topLevelSnippet = None
354     for snip in rules.snippets:
355         if snip.name.startswith("Makefile.rules."):
356             topLevelSnippet = snip.name
357     if not topLevelSnippet:
358         sys.stderr.write("No toplevel snippet (Makefile.rules.*) found\n")
359         sys.exit(1)
360         
361     rulesCheck.combineFrom(topLevelSnippet, onlyCheck=True)
362
363     # The comparsion is not that simple. The order of rules might be
364     # different. FIXME: Is this still true?
365 #     if rules.rules != rulesCheck.rules:
366 #         sys.stderr.write("Consistency error:\n")
367 #         diff = rules.rules.getDiff(rulesCheck.rules)
368 #         sys.stderr.write(diff)
369 #         sys.exit(1)
370
371     for snip in rules.snippets:
372         if snip.name[0:2] == "__":
373             continue
374         print snip.name
375         f = None
376         if output == "-": f = sys.stdout
377         else: f = open(snip.name, "w+")
378         f.writelines(snip.asLinesList())
379         f.close()
380
381 def main():
382     (options, args) = parseCommandLine()
383     if options.split:
384         splitRules(options.split, options.output)
385     else:
386         buildRules(args[0], options.output)
387
388
389 if __name__ == "__main__": main()