[4754f1e] | 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 | |
---|
[fd4e4fb] | 25 | import copy |
---|
[d7e4900] | 26 | import datetime |
---|
[4754f1e] | 27 | import os |
---|
| 28 | import sys |
---|
| 29 | |
---|
[d7e4900] | 30 | try: |
---|
| 31 | import build |
---|
| 32 | import check |
---|
| 33 | import config |
---|
| 34 | import error |
---|
| 35 | import git |
---|
| 36 | import log |
---|
[cb12e48] | 37 | import options |
---|
[d7e4900] | 38 | import path |
---|
| 39 | import setbuilder |
---|
[efb6688] | 40 | import version |
---|
[d7e4900] | 41 | except KeyboardInterrupt: |
---|
| 42 | print 'user terminated' |
---|
| 43 | sys.exit(1) |
---|
| 44 | except: |
---|
[aabd20d] | 45 | print 'error: unknown application load error' |
---|
[d7e4900] | 46 | sys.exit(1) |
---|
[4754f1e] | 47 | |
---|
| 48 | class report: |
---|
| 49 | """Report the build details about a package given a config file.""" |
---|
| 50 | |
---|
| 51 | line_len = 78 |
---|
| 52 | |
---|
[cb12e48] | 53 | def __init__(self, format, _configs, opts, macros = None): |
---|
[4754f1e] | 54 | self.format = format |
---|
| 55 | self.configs = _configs |
---|
| 56 | self.opts = opts |
---|
[cb12e48] | 57 | if macros is None: |
---|
| 58 | self.macros = opts.defaults |
---|
| 59 | else: |
---|
| 60 | self.macros = macros |
---|
[4754f1e] | 61 | self.bset_nesting = 0 |
---|
| 62 | self.configs_active = False |
---|
[d7e4900] | 63 | self.out = '' |
---|
| 64 | self.asciidoc = None |
---|
[4754f1e] | 65 | |
---|
[6ee25e5] | 66 | def _sbpath(self, *args): |
---|
[cb12e48] | 67 | p = self.macros.expand('%{_sbdir}') |
---|
[6ee25e5] | 68 | for arg in args: |
---|
| 69 | p = path.join(p, arg) |
---|
| 70 | return os.path.abspath(path.host(p)) |
---|
| 71 | |
---|
[d7e4900] | 72 | def output(self, text): |
---|
| 73 | self.out += '%s\n' % (text) |
---|
[4754f1e] | 74 | |
---|
| 75 | def is_text(self): |
---|
| 76 | return self.format == 'text' |
---|
| 77 | |
---|
| 78 | def is_asciidoc(self): |
---|
[d6638aa] | 79 | return self.format == 'asciidoc' or self.format == 'html' |
---|
[4754f1e] | 80 | |
---|
| 81 | def setup(self): |
---|
[d6638aa] | 82 | if self.format == 'html': |
---|
[d7e4900] | 83 | try: |
---|
| 84 | import asciidocapi |
---|
| 85 | except: |
---|
| 86 | raise error.general('installation error: no asciidocapi found') |
---|
[cb12e48] | 87 | asciidoc_py = self._sbpath(options.basepath, 'asciidoc', 'asciidoc.py') |
---|
[d7e4900] | 88 | try: |
---|
[6ee25e5] | 89 | self.asciidoc = asciidocapi.AsciiDocAPI(asciidoc_py) |
---|
[d7e4900] | 90 | except: |
---|
| 91 | raise error.general('application error: asciidocapi failed') |
---|
[4754f1e] | 92 | |
---|
| 93 | def header(self): |
---|
| 94 | pass |
---|
| 95 | |
---|
| 96 | def footer(self): |
---|
| 97 | pass |
---|
| 98 | |
---|
[d7e4900] | 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)) |
---|
[cb12e48] | 109 | repo = git.repo('.', self.opts, self.macros) |
---|
[d7e4900] | 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): |
---|
[4754f1e] | 158 | if self.is_asciidoc(): |
---|
| 159 | h = 'RTEMS Source Builder Report' |
---|
[d7e4900] | 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('') |
---|
[cb12e48] | 172 | image = self._sbpath(options.basepath, 'images', 'rtemswhitebg.jpg') |
---|
[d7e4900] | 173 | self.output('image:%s["RTEMS",width="20%%"]' % (image)) |
---|
| 174 | self.output('') |
---|
| 175 | if intro_text: |
---|
| 176 | self.output('%s' % ('\n'.join(intro_text))) |
---|
[4754f1e] | 177 | else: |
---|
[d7e4900] | 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() |
---|
[4754f1e] | 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(): |
---|
[d7e4900] | 193 | self.output('') |
---|
| 194 | self.output("'''") |
---|
| 195 | self.output('') |
---|
[4754f1e] | 196 | |
---|
| 197 | def buildset_start(self, name): |
---|
| 198 | if self.is_asciidoc(): |
---|
| 199 | h = '%s' % (name) |
---|
[d7e4900] | 200 | self.output('=%s %s' % ('=' * self.bset_nesting, h)) |
---|
[4754f1e] | 201 | else: |
---|
[d7e4900] | 202 | self.output('=-' * (self.line_len / 2)) |
---|
| 203 | self.output('Build Set: %s' % (name)) |
---|
[4754f1e] | 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 | |
---|
[514ad16] | 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 | |
---|
[cb12e48] | 248 | def config(self, configname, opts, macros): |
---|
[514ad16] | 249 | |
---|
[cb12e48] | 250 | _config = config.file(configname, opts, macros) |
---|
[4754f1e] | 251 | packages = _config.packages() |
---|
| 252 | package = packages['main'] |
---|
| 253 | name = package.name() |
---|
[514ad16] | 254 | self.config_start(name) |
---|
[4754f1e] | 255 | if self.is_asciidoc(): |
---|
[514ad16] | 256 | self.output('*Package*: _%s_ +' % (name)) |
---|
| 257 | self.output('*Config*: %s' % (configname)) |
---|
[d7e4900] | 258 | self.output('') |
---|
[4754f1e] | 259 | else: |
---|
[514ad16] | 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('') |
---|
[4754f1e] | 270 | sources = package.sources() |
---|
| 271 | if self.is_asciidoc(): |
---|
[514ad16] | 272 | self.output('*Sources:*::') |
---|
[4754f1e] | 273 | if len(sources) == 0: |
---|
[d7e4900] | 274 | self.output('No sources') |
---|
[4754f1e] | 275 | else: |
---|
[d7e4900] | 276 | self.output(' Sources: %d' % (len(sources))) |
---|
[4754f1e] | 277 | c = 0 |
---|
| 278 | for s in sources: |
---|
| 279 | c += 1 |
---|
| 280 | if self.is_asciidoc(): |
---|
[d7e4900] | 281 | self.output('. %s' % (sources[s][0])) |
---|
[4754f1e] | 282 | else: |
---|
[d7e4900] | 283 | self.output(' %2d: %s' % (c, sources[s][0])) |
---|
[4754f1e] | 284 | patches = package.patches() |
---|
| 285 | if self.is_asciidoc(): |
---|
[d7e4900] | 286 | self.output('') |
---|
[514ad16] | 287 | self.output('*Patches:*::') |
---|
[4754f1e] | 288 | if len(patches) == 0: |
---|
[d7e4900] | 289 | self.output('No patches') |
---|
[4754f1e] | 290 | else: |
---|
[d7e4900] | 291 | self.output(' Patches: %s' % (len(patches))) |
---|
[4754f1e] | 292 | c = 0 |
---|
| 293 | for p in patches: |
---|
| 294 | c += 1 |
---|
| 295 | if self.is_asciidoc(): |
---|
[d7e4900] | 296 | self.output('. %s' % (patches[p][0])) |
---|
[4754f1e] | 297 | else: |
---|
[d7e4900] | 298 | self.output(' %2d: %s' % (c, patches[p][0])) |
---|
[514ad16] | 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()) |
---|
[4754f1e] | 303 | self.config_end(name) |
---|
| 304 | |
---|
[055e490] | 305 | def buildset(self, name, opts = None, macros = None): |
---|
[864e8ff] | 306 | self.bset_nesting += 1 |
---|
| 307 | self.buildset_start(name) |
---|
[055e490] | 308 | if opts is None: |
---|
| 309 | opts = self.opts |
---|
| 310 | if macros is None: |
---|
| 311 | macros = self.macros |
---|
[cb12e48] | 312 | bset = setbuilder.buildset(name, self.configs, opts, macros) |
---|
[864e8ff] | 313 | for c in bset.load(): |
---|
| 314 | if c.endswith('.bset'): |
---|
[055e490] | 315 | self.buildset(c, bset.opts, bset.macros) |
---|
[864e8ff] | 316 | elif c.endswith('.cfg'): |
---|
[055e490] | 317 | self.config(c, bset.opts, bset.macros) |
---|
[4754f1e] | 318 | else: |
---|
[864e8ff] | 319 | raise error.general('invalid config type: %s' % (c)) |
---|
| 320 | self.buildset_end(name) |
---|
| 321 | self.bset_nesting -= 1 |
---|
[4754f1e] | 322 | |
---|
[d7e4900] | 323 | def generate(self, name): |
---|
[d6638aa] | 324 | if self.format == 'html': |
---|
[d7e4900] | 325 | if self.asciidoc is None: |
---|
| 326 | raise error.general('asciidoc not initialised') |
---|
| 327 | import StringIO |
---|
| 328 | infile = StringIO.StringIO(self.out) |
---|
| 329 | outfile = StringIO.StringIO() |
---|
| 330 | self.asciidoc.execute(infile, outfile) |
---|
| 331 | self.out = outfile.getvalue() |
---|
| 332 | infile.close() |
---|
| 333 | outfile.close() |
---|
[055e490] | 334 | if name is not None: |
---|
| 335 | try: |
---|
| 336 | o = open(name, "w") |
---|
| 337 | o.write(self.out) |
---|
| 338 | o.close() |
---|
| 339 | del o |
---|
| 340 | except IOError, err: |
---|
| 341 | raise error.general('writing output file: %s: %s' % (name, err)) |
---|
[d7e4900] | 342 | |
---|
[055e490] | 343 | def make(self, inname, outname = None, intro_text = None): |
---|
[d7e4900] | 344 | self.setup() |
---|
| 345 | self.introduction(inname, intro_text) |
---|
[0759d98] | 346 | config = build.find_config(inname, self.configs) |
---|
[864e8ff] | 347 | if config is None: |
---|
| 348 | raise error.general('config file not found: %s' % (inname)) |
---|
| 349 | if config.endswith('.bset'): |
---|
| 350 | self.buildset(config) |
---|
| 351 | elif config.endswith('.cfg'): |
---|
[cb12e48] | 352 | self.config(config, self.opts, self.macros) |
---|
[864e8ff] | 353 | else: |
---|
| 354 | raise error.general('invalid config type: %s' % (config)) |
---|
[d7e4900] | 355 | self.generate(outname) |
---|
[4754f1e] | 356 | |
---|
| 357 | def run(args): |
---|
| 358 | try: |
---|
| 359 | optargs = { '--list-bsets': 'List available build sets', |
---|
| 360 | '--list-configs': 'List available configurations', |
---|
[d6638aa] | 361 | '--format': 'Output format (text, html, asciidoc)', |
---|
[d7e4900] | 362 | '--output': 'File name to output the report' } |
---|
[cb12e48] | 363 | opts = options.load(args, optargs) |
---|
[d7e4900] | 364 | if opts.get_arg('--output') and len(opts.params()) > 1: |
---|
| 365 | raise error.general('--output can only be used with a single config') |
---|
[efb6688] | 366 | print 'RTEMS Source Builder, Reporter v%s' % (version.str()) |
---|
[cb12e48] | 367 | if not check.host_setup(opts): |
---|
[5142bec] | 368 | log.warning('forcing build with known host setup problems') |
---|
[cb12e48] | 369 | configs = build.get_configs(opts) |
---|
[4754f1e] | 370 | if not setbuilder.list_bset_cfg_files(opts, configs): |
---|
[d7e4900] | 371 | output = opts.get_arg('--output') |
---|
| 372 | if output is not None: |
---|
| 373 | output = output[1] |
---|
[4754f1e] | 374 | format = 'text' |
---|
[d7e4900] | 375 | ext = '.txt' |
---|
| 376 | format_opt = opts.get_arg('--format') |
---|
| 377 | if format_opt: |
---|
| 378 | if len(format_opt) != 2: |
---|
| 379 | raise error.general('invalid format option: %s' % ('='.join(format_opt))) |
---|
| 380 | if format_opt[1] == 'text': |
---|
| 381 | pass |
---|
| 382 | elif format_opt[1] == 'asciidoc': |
---|
| 383 | format = 'asciidoc' |
---|
[d6638aa] | 384 | ext = '.txt' |
---|
| 385 | elif format_opt[1] == 'html': |
---|
| 386 | format = 'html' |
---|
[d7e4900] | 387 | ext = '.html' |
---|
| 388 | else: |
---|
| 389 | raise error.general('invalid format: %s' % (format_opt[1])) |
---|
[cb12e48] | 390 | r = report(format, configs, opts) |
---|
[d7e4900] | 391 | for _config in opts.params(): |
---|
| 392 | if output is None: |
---|
| 393 | outname = path.splitext(_config)[0] + ext |
---|
[cb12e48] | 394 | outname = outname.replace('/', '-') |
---|
[d7e4900] | 395 | else: |
---|
| 396 | outname = output |
---|
| 397 | r.make(_config, outname) |
---|
| 398 | del r |
---|
[4754f1e] | 399 | except error.general, gerr: |
---|
| 400 | print gerr |
---|
| 401 | sys.exit(1) |
---|
| 402 | except error.internal, ierr: |
---|
| 403 | print ierr |
---|
| 404 | sys.exit(1) |
---|
| 405 | except error.exit, eerr: |
---|
| 406 | pass |
---|
| 407 | except KeyboardInterrupt: |
---|
[5142bec] | 408 | log.notice('abort: user terminated') |
---|
[4754f1e] | 409 | sys.exit(1) |
---|
| 410 | sys.exit(0) |
---|
| 411 | |
---|
| 412 | if __name__ == "__main__": |
---|
| 413 | run(sys.argv) |
---|