1 | # |
---|
2 | # RTEMS Tools Project (http://www.rtems.org/) |
---|
3 | # Copyright 2010-2013 Chris Johns (chrisj@rtems.org) |
---|
4 | # All rights reserved. |
---|
5 | # |
---|
6 | # This file is part of the RTEMS Tools package in 'rtems-tools'. |
---|
7 | # |
---|
8 | # Permission to use, copy, modify, and/or distribute this software for any |
---|
9 | # purpose with or without fee is hereby granted, provided that the above |
---|
10 | # copyright notice and this permission notice appear in all copies. |
---|
11 | # |
---|
12 | # THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
---|
13 | # WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
---|
14 | # MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR |
---|
15 | # ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
---|
16 | # WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN |
---|
17 | # ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF |
---|
18 | # OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
---|
19 | |
---|
20 | # |
---|
21 | # This code builds a package given a config file. It only builds to be |
---|
22 | # installed not to be package unless you run a packager around this. |
---|
23 | # |
---|
24 | |
---|
25 | import copy |
---|
26 | import datetime |
---|
27 | import os |
---|
28 | import sys |
---|
29 | |
---|
30 | try: |
---|
31 | import build |
---|
32 | import check |
---|
33 | import config |
---|
34 | import error |
---|
35 | import git |
---|
36 | import log |
---|
37 | import options |
---|
38 | import path |
---|
39 | import setbuilder |
---|
40 | import version |
---|
41 | except KeyboardInterrupt: |
---|
42 | print 'user terminated' |
---|
43 | sys.exit(1) |
---|
44 | except: |
---|
45 | print 'error: unknown application load error' |
---|
46 | sys.exit(1) |
---|
47 | |
---|
48 | class report: |
---|
49 | """Report the build details about a package given a config file.""" |
---|
50 | |
---|
51 | line_len = 78 |
---|
52 | |
---|
53 | def __init__(self, format, _configs, opts, macros = None): |
---|
54 | self.format = format |
---|
55 | self.configs = _configs |
---|
56 | self.opts = opts |
---|
57 | if macros is None: |
---|
58 | self.macros = opts.defaults |
---|
59 | else: |
---|
60 | self.macros = macros |
---|
61 | self.bset_nesting = 0 |
---|
62 | self.configs_active = False |
---|
63 | self.out = '' |
---|
64 | self.asciidoc = None |
---|
65 | |
---|
66 | def _sbpath(self, *args): |
---|
67 | p = self.macros.expand('%{_sbdir}') |
---|
68 | for arg in args: |
---|
69 | p = path.join(p, arg) |
---|
70 | return os.path.abspath(path.host(p)) |
---|
71 | |
---|
72 | def output(self, text): |
---|
73 | self.out += '%s\n' % (text) |
---|
74 | |
---|
75 | def is_text(self): |
---|
76 | return self.format == 'text' |
---|
77 | |
---|
78 | def is_asciidoc(self): |
---|
79 | return self.format == 'asciidoc' or self.format == 'html' |
---|
80 | |
---|
81 | def setup(self): |
---|
82 | if self.format == 'html': |
---|
83 | try: |
---|
84 | import asciidocapi |
---|
85 | except: |
---|
86 | raise error.general('installation error: no asciidocapi found') |
---|
87 | asciidoc_py = self._sbpath(options.basepath, 'asciidoc', 'asciidoc.py') |
---|
88 | try: |
---|
89 | self.asciidoc = asciidocapi.AsciiDocAPI(asciidoc_py) |
---|
90 | except: |
---|
91 | raise error.general('application error: asciidocapi failed') |
---|
92 | |
---|
93 | def header(self): |
---|
94 | pass |
---|
95 | |
---|
96 | def footer(self): |
---|
97 | pass |
---|
98 | |
---|
99 | def git_status(self): |
---|
100 | text = 'RTEMS Source Builder Repository Status' |
---|
101 | if self.is_asciidoc(): |
---|
102 | self.output('') |
---|
103 | self.output("'''") |
---|
104 | self.output('') |
---|
105 | self.output('.%s' % (text)) |
---|
106 | else: |
---|
107 | self.output('-' * self.line_len) |
---|
108 | self.output('%s' % (text)) |
---|
109 | repo = git.repo('.', self.opts, self.macros) |
---|
110 | repo_valid = repo.valid() |
---|
111 | if repo_valid: |
---|
112 | if self.is_asciidoc(): |
---|
113 | self.output('*Remotes*:;;') |
---|
114 | else: |
---|
115 | self.output(' Remotes:') |
---|
116 | repo_remotes = repo.remotes() |
---|
117 | rc = 0 |
---|
118 | for r in repo_remotes: |
---|
119 | rc += 1 |
---|
120 | if 'url' in repo_remotes[r]: |
---|
121 | text = repo_remotes[r]['url'] |
---|
122 | else: |
---|
123 | text = 'no URL found' |
---|
124 | text = '%s: %s' % (r, text) |
---|
125 | if self.is_asciidoc(): |
---|
126 | self.output('. %s' % (text)) |
---|
127 | else: |
---|
128 | self.output(' %2d: %s' % (rc, text)) |
---|
129 | if self.is_asciidoc(): |
---|
130 | self.output('*Status*:;;') |
---|
131 | else: |
---|
132 | self.output(' Status:') |
---|
133 | if repo.clean(): |
---|
134 | if self.is_asciidoc(): |
---|
135 | self.output('Clean') |
---|
136 | else: |
---|
137 | self.output(' Clean') |
---|
138 | else: |
---|
139 | if self.is_asciidoc(): |
---|
140 | self.output('_Repository is dirty_') |
---|
141 | else: |
---|
142 | self.output(' Repository is dirty') |
---|
143 | repo_head = repo.head() |
---|
144 | if self.is_asciidoc(): |
---|
145 | self.output('*Head*:;;') |
---|
146 | self.output('Commit: %s' % (repo_head)) |
---|
147 | else: |
---|
148 | self.output(' Head:') |
---|
149 | self.output(' Commit: %s' % (repo_head)) |
---|
150 | else: |
---|
151 | self.output('_Not a valid GIT repository_') |
---|
152 | if self.is_asciidoc(): |
---|
153 | self.output('') |
---|
154 | self.output("'''") |
---|
155 | self.output('') |
---|
156 | |
---|
157 | def introduction(self, name, intro_text): |
---|
158 | if self.is_asciidoc(): |
---|
159 | h = 'RTEMS Source Builder Report' |
---|
160 | self.output(h) |
---|
161 | self.output('=' * len(h)) |
---|
162 | self.output(':doctype: book') |
---|
163 | self.output(':toc2:') |
---|
164 | self.output(':toclevels: 5') |
---|
165 | self.output(':icons:') |
---|
166 | self.output(':numbered:') |
---|
167 | self.output(':data-uri:') |
---|
168 | self.output('') |
---|
169 | self.output('RTEMS Tools Project <rtems-users@rtems.org>') |
---|
170 | self.output(datetime.datetime.now().ctime()) |
---|
171 | self.output('') |
---|
172 | image = self._sbpath(options.basepath, 'images', 'rtemswhitebg.jpg') |
---|
173 | self.output('image:%s["RTEMS",width="20%%"]' % (image)) |
---|
174 | self.output('') |
---|
175 | if intro_text: |
---|
176 | self.output('%s' % ('\n'.join(intro_text))) |
---|
177 | else: |
---|
178 | self.output('=' * self.line_len) |
---|
179 | self.output('RTEMS Tools Project <rtems-users@rtems.org> %s' % datetime.datetime.now().ctime()) |
---|
180 | if intro_text: |
---|
181 | self.output('') |
---|
182 | self.output('%s' % ('\n'.join(intro_text))) |
---|
183 | self.output('=' * self.line_len) |
---|
184 | self.output('Report: %s' % (name)) |
---|
185 | self.git_status() |
---|
186 | |
---|
187 | def config_start(self, name): |
---|
188 | first = not self.configs_active |
---|
189 | self.configs_active = True |
---|
190 | |
---|
191 | def config_end(self, name): |
---|
192 | if self.is_asciidoc(): |
---|
193 | self.output('') |
---|
194 | self.output("'''") |
---|
195 | self.output('') |
---|
196 | |
---|
197 | def buildset_start(self, name): |
---|
198 | if self.is_asciidoc(): |
---|
199 | h = '%s' % (name) |
---|
200 | self.output('=%s %s' % ('=' * self.bset_nesting, h)) |
---|
201 | else: |
---|
202 | self.output('=-' * (self.line_len / 2)) |
---|
203 | self.output('Build Set: %s' % (name)) |
---|
204 | |
---|
205 | def buildset_end(self, name): |
---|
206 | self.configs_active = False |
---|
207 | |
---|
208 | def source(self, package, source_tag): |
---|
209 | return package.sources() |
---|
210 | |
---|
211 | def patch(self, package, args): |
---|
212 | return package.patches() |
---|
213 | |
---|
214 | def output_info(self, name, info, separated = False): |
---|
215 | if info is not None: |
---|
216 | end = '' |
---|
217 | if self.is_asciidoc(): |
---|
218 | if separated: |
---|
219 | self.output('*%s:*::' % (name)) |
---|
220 | self.output('') |
---|
221 | else: |
---|
222 | self.output('*%s:* ' % (name)) |
---|
223 | end = ' +' |
---|
224 | spaces = '' |
---|
225 | else: |
---|
226 | self.output(' %s:' % (name)) |
---|
227 | spaces = ' ' |
---|
228 | for l in info: |
---|
229 | self.output('%s%s%s' % (spaces, l, end)) |
---|
230 | if self.is_asciidoc() and separated: |
---|
231 | self.output('') |
---|
232 | |
---|
233 | def output_directive(self, name, directive): |
---|
234 | if directive is not None: |
---|
235 | if self.is_asciidoc(): |
---|
236 | self.output('') |
---|
237 | self.output('*%s*:' % (name)) |
---|
238 | self.output('--------------------------------------------') |
---|
239 | spaces = '' |
---|
240 | else: |
---|
241 | self.output(' %s:' % (name)) |
---|
242 | spaces = ' ' |
---|
243 | for l in directive: |
---|
244 | self.output('%s%s' % (spaces, l)) |
---|
245 | if self.is_asciidoc(): |
---|
246 | self.output('--------------------------------------------') |
---|
247 | |
---|
248 | def config(self, configname, opts, macros): |
---|
249 | |
---|
250 | _config = config.file(configname, opts, macros) |
---|
251 | packages = _config.packages() |
---|
252 | package = packages['main'] |
---|
253 | name = package.name() |
---|
254 | self.config_start(name) |
---|
255 | if self.is_asciidoc(): |
---|
256 | self.output('*Package*: _%s_ +' % (name)) |
---|
257 | self.output('*Config*: %s' % (configname)) |
---|
258 | self.output('') |
---|
259 | else: |
---|
260 | self.output('-' * self.line_len) |
---|
261 | self.output('Package: %s' % (name)) |
---|
262 | self.output(' Config: %s' % (configname)) |
---|
263 | self.output_info('Summary', package.get_info('summary'), True) |
---|
264 | self.output_info('URL', package.get_info('url')) |
---|
265 | self.output_info('Version', package.get_info('version')) |
---|
266 | self.output_info('Release', package.get_info('release')) |
---|
267 | self.output_info('Build Arch', package.get_info('buildarch')) |
---|
268 | if self.is_asciidoc(): |
---|
269 | self.output('') |
---|
270 | sources = package.sources() |
---|
271 | if self.is_asciidoc(): |
---|
272 | self.output('*Sources:*::') |
---|
273 | if len(sources) == 0: |
---|
274 | self.output('No sources') |
---|
275 | else: |
---|
276 | self.output(' Sources: %d' % (len(sources))) |
---|
277 | c = 0 |
---|
278 | for s in sources: |
---|
279 | c += 1 |
---|
280 | if self.is_asciidoc(): |
---|
281 | self.output('. %s' % (sources[s][0])) |
---|
282 | else: |
---|
283 | self.output(' %2d: %s' % (c, sources[s][0])) |
---|
284 | patches = package.patches() |
---|
285 | if self.is_asciidoc(): |
---|
286 | self.output('') |
---|
287 | self.output('*Patches:*::') |
---|
288 | if len(patches) == 0: |
---|
289 | self.output('No patches') |
---|
290 | else: |
---|
291 | self.output(' Patches: %s' % (len(patches))) |
---|
292 | c = 0 |
---|
293 | for p in patches: |
---|
294 | c += 1 |
---|
295 | if self.is_asciidoc(): |
---|
296 | self.output('. %s' % (patches[p][0])) |
---|
297 | else: |
---|
298 | self.output(' %2d: %s' % (c, patches[p][0])) |
---|
299 | self.output_directive('Preparation', package.prep()) |
---|
300 | self.output_directive('Build', package.build()) |
---|
301 | self.output_directive('Install', package.install()) |
---|
302 | self.output_directive('Clean', package.clean()) |
---|
303 | self.config_end(name) |
---|
304 | |
---|
305 | def buildset(self, name): |
---|
306 | self.bset_nesting += 1 |
---|
307 | self.buildset_start(name) |
---|
308 | opts = copy.copy(self.opts) |
---|
309 | macros = copy.copy(self.macros) |
---|
310 | bset = setbuilder.buildset(name, self.configs, opts, macros) |
---|
311 | for c in bset.load(): |
---|
312 | if c.endswith('.bset'): |
---|
313 | self.buildset(c) |
---|
314 | elif c.endswith('.cfg'): |
---|
315 | self.config(c, opts, macros) |
---|
316 | else: |
---|
317 | raise error.general('invalid config type: %s' % (c)) |
---|
318 | self.buildset_end(name) |
---|
319 | self.bset_nesting -= 1 |
---|
320 | |
---|
321 | def generate(self, name): |
---|
322 | if self.format == 'html': |
---|
323 | if self.asciidoc is None: |
---|
324 | raise error.general('asciidoc not initialised') |
---|
325 | import StringIO |
---|
326 | infile = StringIO.StringIO(self.out) |
---|
327 | outfile = StringIO.StringIO() |
---|
328 | self.asciidoc.execute(infile, outfile) |
---|
329 | self.out = outfile.getvalue() |
---|
330 | infile.close() |
---|
331 | outfile.close() |
---|
332 | try: |
---|
333 | o = open(name, "w") |
---|
334 | o.write(self.out) |
---|
335 | o.close() |
---|
336 | del o |
---|
337 | except IOError, err: |
---|
338 | raise error.general('writing output file: %s: %s' % (name, err)) |
---|
339 | |
---|
340 | def make(self, inname, outname, intro_text = None): |
---|
341 | self.setup() |
---|
342 | self.introduction(inname, intro_text) |
---|
343 | config = build.find_config(inname, self.configs) |
---|
344 | if config is None: |
---|
345 | raise error.general('config file not found: %s' % (inname)) |
---|
346 | if config.endswith('.bset'): |
---|
347 | self.buildset(config) |
---|
348 | elif config.endswith('.cfg'): |
---|
349 | self.config(config, self.opts, self.macros) |
---|
350 | else: |
---|
351 | raise error.general('invalid config type: %s' % (config)) |
---|
352 | self.generate(outname) |
---|
353 | |
---|
354 | def run(args): |
---|
355 | try: |
---|
356 | optargs = { '--list-bsets': 'List available build sets', |
---|
357 | '--list-configs': 'List available configurations', |
---|
358 | '--format': 'Output format (text, html, asciidoc)', |
---|
359 | '--output': 'File name to output the report' } |
---|
360 | opts = options.load(args, optargs) |
---|
361 | if opts.get_arg('--output') and len(opts.params()) > 1: |
---|
362 | raise error.general('--output can only be used with a single config') |
---|
363 | print 'RTEMS Source Builder, Reporter v%s' % (version.str()) |
---|
364 | if not check.host_setup(opts): |
---|
365 | log.warning('forcing build with known host setup problems') |
---|
366 | configs = build.get_configs(opts) |
---|
367 | if not setbuilder.list_bset_cfg_files(opts, configs): |
---|
368 | output = opts.get_arg('--output') |
---|
369 | if output is not None: |
---|
370 | output = output[1] |
---|
371 | format = 'text' |
---|
372 | ext = '.txt' |
---|
373 | format_opt = opts.get_arg('--format') |
---|
374 | if format_opt: |
---|
375 | if len(format_opt) != 2: |
---|
376 | raise error.general('invalid format option: %s' % ('='.join(format_opt))) |
---|
377 | if format_opt[1] == 'text': |
---|
378 | pass |
---|
379 | elif format_opt[1] == 'asciidoc': |
---|
380 | format = 'asciidoc' |
---|
381 | ext = '.txt' |
---|
382 | elif format_opt[1] == 'html': |
---|
383 | format = 'html' |
---|
384 | ext = '.html' |
---|
385 | else: |
---|
386 | raise error.general('invalid format: %s' % (format_opt[1])) |
---|
387 | r = report(format, configs, opts) |
---|
388 | for _config in opts.params(): |
---|
389 | if output is None: |
---|
390 | outname = path.splitext(_config)[0] + ext |
---|
391 | outname = outname.replace('/', '-') |
---|
392 | else: |
---|
393 | outname = output |
---|
394 | r.make(_config, outname) |
---|
395 | del r |
---|
396 | except error.general, gerr: |
---|
397 | print gerr |
---|
398 | sys.exit(1) |
---|
399 | except error.internal, ierr: |
---|
400 | print ierr |
---|
401 | sys.exit(1) |
---|
402 | except error.exit, eerr: |
---|
403 | pass |
---|
404 | except KeyboardInterrupt: |
---|
405 | log.notice('abort: user terminated') |
---|
406 | sys.exit(1) |
---|
407 | sys.exit(0) |
---|
408 | |
---|
409 | if __name__ == "__main__": |
---|
410 | run(sys.argv) |
---|