2 # -*- coding: utf-8 -*-
4 # Autowaf, useful waf utilities with support for recursive projects
5 # Copyright 2008-2011 David Robillard
7 # Licensed under the GNU GPL v2 or later, see COPYING file for details.
14 from waflib import Configure, Context, Logs, Node, Options, Task, Utils
15 from waflib.TaskGen import feature, before, after
20 # Only run autowaf hooks once (even if sub projects call several times)
24 # Compute dependencies globally
26 #preproc.go_absolute = True
29 @after('apply_incpaths')
30 def include_config_h(self):
31 self.env.append_value('INCPATHS', self.bld.bldnode.abspath())
33 def set_options(opt, debug_by_default=False):
34 "Add standard autowaf options if they havn't been added yet"
39 # Install directory options
40 dirs_options = opt.add_option_group('Installation directories', '')
42 # Move --prefix and --destdir to directory options group
43 for k in ('--prefix', '--destdir'):
44 option = opt.parser.get_option(k)
46 opt.parser.remove_option(k)
47 dirs_options.add_option(option)
49 # Standard directory options
50 dirs_options.add_option('--bindir', type='string',
51 help="Executable programs [Default: PREFIX/bin]")
52 dirs_options.add_option('--configdir', type='string',
53 help="Configuration data [Default: PREFIX/etc]")
54 dirs_options.add_option('--datadir', type='string',
55 help="Shared data [Default: PREFIX/share]")
56 dirs_options.add_option('--includedir', type='string',
57 help="Header files [Default: PREFIX/include]")
58 dirs_options.add_option('--libdir', type='string',
59 help="Libraries [Default: PREFIX/lib]")
60 dirs_options.add_option('--mandir', type='string',
61 help="Manual pages [Default: DATADIR/man]")
62 dirs_options.add_option('--docdir', type='string',
63 help="HTML documentation [Default: DATADIR/doc]")
67 opt.add_option('--optimize', action='store_false', default=True, dest='debug',
68 help="Build optimized binaries")
70 opt.add_option('--debug', action='store_true', default=False, dest='debug',
71 help="Build debuggable binaries")
73 opt.add_option('--pardebug', action='store_true', default=False, dest='pardebug',
74 help="Build parallel-installable debuggable libraries with D suffix")
76 opt.add_option('--grind', action='store_true', default=False, dest='grind',
77 help="Run tests in valgrind")
78 opt.add_option('--strict', action='store_true', default=False, dest='strict',
79 help="Use strict compiler flags and show all warnings")
80 opt.add_option('--ultra-strict', action='store_true', default=False, dest='ultra_strict',
81 help="Use even stricter compiler flags (likely to trigger many warnings in library headers)")
82 opt.add_option('--docs', action='store_true', default=False, dest='docs',
83 help="Build documentation - requires doxygen")
86 opt.add_option('--lv2-user', action='store_true', default=False, dest='lv2_user',
87 help="Install LV2 bundles to user location")
88 opt.add_option('--lv2-system', action='store_true', default=False, dest='lv2_system',
89 help="Install LV2 bundles to system location")
90 dirs_options.add_option('--lv2dir', type='string',
91 help="LV2 bundles [Default: LIBDIR/lv2]")
94 def check_header(conf, lang, name, define='', mandatory=True):
96 includes = '' # search default system include paths
97 if sys.platform == "darwin":
98 includes = '/opt/local/include'
101 check_func = conf.check_cc
103 check_func = conf.check_cxx
105 Logs.error("Unknown header language `%s'" % lang)
109 check_func(header_name=name, includes=includes,
110 define_name=define, mandatory=mandatory)
112 check_func(header_name=name, includes=includes, mandatory=mandatory)
115 return name.replace('/', '_').replace('++', 'PP').replace('-', '_').replace('.', '_')
117 def define(conf, var_name, value):
118 conf.define(var_name, value)
119 conf.env[var_name] = value
121 def check_pkg(conf, name, **args):
122 "Check for a package iff it hasn't been checked for yet"
123 if args['uselib_store'].lower() in conf.env['AUTOWAF_LOCAL_LIBS']:
128 var_name = 'CHECKED_' + nameify(args['uselib_store'])
129 check = not var_name in conf.env
130 mandatory = not 'mandatory' in args or args['mandatory']
131 if not check and 'atleast_version' in args:
132 # Re-check if version is newer than previous check
133 checked_version = conf.env['VERSION_' + name]
134 if checked_version and checked_version < args['atleast_version']:
136 if not check and mandatory and conf.env[var_name] == CheckType.OPTIONAL:
137 # Re-check if previous check was optional but this one is mandatory
141 pkg_var_name = 'PKG_' + name.replace('-', '_')
143 if conf.env.PARDEBUG:
144 args['mandatory'] = False # Smash mandatory arg
145 found = conf.check_cfg(package=pkg_name + 'D', args="--cflags --libs", **args)
149 args['mandatory'] = True # Unsmash mandatory arg
151 found = conf.check_cfg(package=pkg_name, args="--cflags --libs", **args)
153 conf.env[pkg_var_name] = pkg_name
154 if 'atleast_version' in args:
155 conf.env['VERSION_' + name] = args['atleast_version']
157 conf.env[var_name] = CheckType.MANDATORY
159 conf.env[var_name] = CheckType.OPTIONAL
163 if sys.platform == 'win32':
164 return os.path.normpath(path).replace('\\', '/')
166 return os.path.normpath(path)
172 def append_cxx_flags(flags):
173 conf.env.append_value('CFLAGS', flags)
174 conf.env.append_value('CXXFLAGS', flags)
176 display_header('Global Configuration')
178 if Options.options.docs:
181 conf.env['DOCS'] = Options.options.docs
182 conf.env['DEBUG'] = Options.options.debug or Options.options.pardebug
183 conf.env['PARDEBUG'] = Options.options.pardebug
184 conf.env['PREFIX'] = normpath(os.path.abspath(os.path.expanduser(conf.env['PREFIX'])))
186 def config_dir(var, opt, default):
188 conf.env[var] = normpath(opt)
190 conf.env[var] = normpath(default)
192 opts = Options.options
193 prefix = conf.env['PREFIX']
195 config_dir('BINDIR', opts.bindir, os.path.join(prefix, 'bin'))
196 config_dir('SYSCONFDIR', opts.configdir, os.path.join(prefix, 'etc'))
197 config_dir('DATADIR', opts.datadir, os.path.join(prefix, 'share'))
198 config_dir('INCLUDEDIR', opts.includedir, os.path.join(prefix, 'include'))
199 config_dir('LIBDIR', opts.libdir, os.path.join(prefix, 'lib'))
200 config_dir('MANDIR', opts.mandir, os.path.join(conf.env['DATADIR'], 'man'))
201 config_dir('DOCDIR', opts.docdir, os.path.join(conf.env['DATADIR'], 'doc'))
203 if Options.options.lv2dir:
204 conf.env['LV2DIR'] = Options.options.lv2dir
205 elif Options.options.lv2_user:
206 if sys.platform == "darwin":
207 conf.env['LV2DIR'] = os.path.join(os.getenv('HOME'), 'Library/Audio/Plug-Ins/LV2')
208 elif sys.platform == "win32":
209 conf.env['LV2DIR'] = os.path.join(os.getenv('APPDATA'), 'LV2')
211 conf.env['LV2DIR'] = os.path.join(os.getenv('HOME'), '.lv2')
212 elif Options.options.lv2_system:
213 if sys.platform == "darwin":
214 conf.env['LV2DIR'] = '/Library/Audio/Plug-Ins/LV2'
215 elif sys.platform == "win32":
216 conf.env['LV2DIR'] = os.path.join(os.getenv('COMMONPROGRAMFILES'), 'LV2')
218 conf.env['LV2DIR'] = os.path.join(conf.env['LIBDIR'], 'lv2')
220 conf.env['LV2DIR'] = os.path.join(conf.env['LIBDIR'], 'lv2')
222 conf.env['LV2DIR'] = normpath(conf.env['LV2DIR'])
224 if Options.options.docs:
225 doxygen = conf.find_program('doxygen')
227 conf.fatal("Doxygen is required to build with --docs")
229 dot = conf.find_program('dot')
231 conf.fatal("Graphviz (dot) is required to build with --docs")
233 if Options.options.debug:
234 if conf.env['MSVC_COMPILER']:
235 conf.env['CFLAGS'] = ['/Od', '/Zi', '/MTd']
236 conf.env['CXXFLAGS'] = ['/Od', '/Zi', '/MTd']
237 conf.env['LINKFLAGS'] = ['/DEBUG']
239 conf.env['CFLAGS'] = ['-O0', '-g']
240 conf.env['CXXFLAGS'] = ['-O0', '-g']
242 if conf.env['MSVC_COMPILER']:
243 conf.env['CFLAGS'] = ['/MD']
244 conf.env['CXXFLAGS'] = ['/MD']
245 append_cxx_flags(['-DNDEBUG'])
247 if Options.options.ultra_strict:
248 Options.options.strict = True
249 conf.env.append_value('CFLAGS', ['-Wredundant-decls',
250 '-Wstrict-prototypes',
251 '-Wmissing-prototypes'])
253 if Options.options.strict:
254 conf.env.append_value('CFLAGS', ['-std=c99', '-pedantic', '-Wshadow'])
255 conf.env.append_value('CXXFLAGS', ['-ansi',
256 '-Wnon-virtual-dtor',
257 '-Woverloaded-virtual'])
258 append_cxx_flags(['-Wall',
262 if sys.platform != "darwin":
263 # this is really only to be avoid on OLD apple gcc, but not sure how to version check
264 append_cxx_flags(['-fstrict-overflow'])
266 if not conf.check_cc(fragment = '''
270 int main() { return 0; }''',
274 msg = 'Checking for clang'):
275 if sys.platform != "darwin":
276 # this is really only to be avoid on OLD apple gcc, but not sure how to version check
277 append_cxx_flags(['-Wunsafe-loop-optimizations'])
278 # this is invalid (still) on Lion apple gcc
279 append_cxx_flags(['-Wlogical-op'])
282 if not conf.env['MSVC_COMPILER']:
283 append_cxx_flags(['-fshow-column'])
285 conf.env.prepend_value('CFLAGS', '-I' + os.path.abspath('.'))
286 conf.env.prepend_value('CXXFLAGS', '-I' + os.path.abspath('.'))
288 display_msg(conf, "Install prefix", conf.env['PREFIX'])
289 display_msg(conf, "Debuggable build", str(conf.env['DEBUG']))
290 display_msg(conf, "Build documentation", str(conf.env['DOCS']))
295 def set_c99_mode(conf):
296 if conf.env.MSVC_COMPILER:
297 # MSVC has no hope or desire to compile C99, just compile as C++
298 conf.env.append_unique('CFLAGS', ['-TP'])
300 conf.env.append_unique('CFLAGS', ['-std=c99'])
302 def set_local_lib(conf, name, has_objects):
303 var_name = 'HAVE_' + nameify(name.upper())
304 define(conf, var_name, 1)
306 if type(conf.env['AUTOWAF_LOCAL_LIBS']) != dict:
307 conf.env['AUTOWAF_LOCAL_LIBS'] = {}
308 conf.env['AUTOWAF_LOCAL_LIBS'][name.lower()] = True
310 if type(conf.env['AUTOWAF_LOCAL_HEADERS']) != dict:
311 conf.env['AUTOWAF_LOCAL_HEADERS'] = {}
312 conf.env['AUTOWAF_LOCAL_HEADERS'][name.lower()] = True
314 def append_property(obj, key, val):
315 if hasattr(obj, key):
316 setattr(obj, key, getattr(obj, key) + val)
318 setattr(obj, key, val)
320 def use_lib(bld, obj, libs):
321 abssrcdir = os.path.abspath('.')
322 libs_list = libs.split()
324 in_headers = l.lower() in bld.env['AUTOWAF_LOCAL_HEADERS']
325 in_libs = l.lower() in bld.env['AUTOWAF_LOCAL_LIBS']
327 append_property(obj, 'use', ' lib%s ' % l.lower())
328 append_property(obj, 'framework', bld.env['FRAMEWORK_' + l])
329 if in_headers or in_libs:
330 inc_flag = '-iquote ' + os.path.join(abssrcdir, l.lower())
331 for f in ['CFLAGS', 'CXXFLAGS']:
332 if not inc_flag in bld.env[f]:
333 bld.env.prepend_value(f, inc_flag)
335 append_property(obj, 'uselib', ' ' + l)
338 @before('apply_link')
339 def version_lib(self):
340 if sys.platform == 'win32':
341 self.vnum = None # Prevent waf from automatically appending -0
342 if self.env['PARDEBUG']:
343 applicable = ['cshlib', 'cxxshlib', 'cstlib', 'cxxstlib']
344 if [x for x in applicable if x in self.features]:
345 self.target = self.target + 'D'
347 def set_lib_env(conf, name, version):
348 'Set up environment for local library as if found via pkg-config.'
350 major_ver = version.split('.')[0]
351 pkg_var_name = 'PKG_' + name.replace('-', '_')
352 lib_name = '%s-%s' % (name, major_ver)
353 if conf.env.PARDEBUG:
355 conf.env[pkg_var_name] = lib_name
356 conf.env['INCLUDES_' + NAME] = ['${INCLUDEDIR}/%s-%s' % (name, major_ver)]
357 conf.env['LIBPATH_' + NAME] = [conf.env.LIBDIR]
358 conf.env['LIB_' + NAME] = [lib_name]
360 def display_header(title):
361 Logs.pprint('BOLD', title)
363 def display_msg(conf, msg, status = None, color = None):
365 if type(status) == bool and status or status == "True":
367 elif type(status) == bool and not status or status == "False":
369 Logs.pprint('BOLD', " *", sep='')
370 Logs.pprint('NORMAL', "%s" % msg.ljust(conf.line_just - 3), sep='')
371 Logs.pprint('BOLD', ":", sep='')
372 Logs.pprint(color, status)
374 def link_flags(env, lib):
375 return ' '.join(map(lambda x: env['LIB_ST'] % x, env['LIB_' + lib]))
377 def compile_flags(env, lib):
378 return ' '.join(map(lambda x: env['CPPPATH_ST'] % x, env['INCLUDES_' + lib]))
389 def build_pc(bld, name, version, version_suffix, libs, subst_dict={}):
390 '''Build a pkg-config file for a library.
391 name -- uppercase variable name (e.g. 'SOMENAME')
392 version -- version string (e.g. '1.2.3')
393 version_suffix -- name version suffix (e.g. '2')
394 libs -- string/list of dependencies (e.g. 'LIBFOO GLIB')
396 pkg_prefix = bld.env['PREFIX']
397 if pkg_prefix[-1] == '/':
398 pkg_prefix = pkg_prefix[:-1]
400 target = name.lower()
401 if version_suffix != '':
402 target += '-' + version_suffix
404 if bld.env['PARDEBUG']:
409 libdir = bld.env['LIBDIR']
410 if libdir.startswith(pkg_prefix):
411 libdir = libdir.replace(pkg_prefix, '${exec_prefix}')
413 includedir = bld.env['INCLUDEDIR']
414 if includedir.startswith(pkg_prefix):
415 includedir = includedir.replace(pkg_prefix, '${prefix}')
417 obj = bld(features = 'subst',
418 source = '%s.pc.in' % name.lower(),
420 install_path = os.path.join(bld.env['LIBDIR'], 'pkgconfig'),
421 exec_prefix = '${prefix}',
423 EXEC_PREFIX = '${prefix}',
425 INCLUDEDIR = includedir)
427 if type(libs) != list:
430 subst_dict[name + '_VERSION'] = version
431 subst_dict[name + '_MAJOR_VERSION'] = version[0:version.find('.')]
433 subst_dict[i + '_LIBS'] = link_flags(bld.env, i)
434 lib_cflags = compile_flags(bld.env, i)
437 subst_dict[i + '_CFLAGS'] = lib_cflags
439 obj.__dict__.update(subst_dict)
441 def build_dir(name, subdir):
443 return os.path.join('build', name, subdir)
445 return os.path.join('build', subdir)
447 # Clean up messy Doxygen documentation after it is built
448 def make_simple_dox(name):
453 os.chdir(build_dir(name, 'doc/html'))
454 page = 'group__%s.html' % name
455 if not os.path.exists(page):
458 ['%s_API ' % NAME, ''],
459 ['%s_DEPRECATED ' % NAME, ''],
460 ['group__%s.html' % name, ''],
462 ['<script.*><\/script>', ''],
463 ['<hr\/><a name="details" id="details"><\/a><h2>.*<\/h2>', ''],
464 ['<link href=\"tabs.css\" rel=\"stylesheet\" type=\"text\/css\"\/>',
466 ['<img class=\"footer\" src=\"doxygen.png\" alt=\"doxygen\"\/>',
468 os.system("sed -i 's/%s/%s/g' %s" % (i[0], i[1], page))
469 os.rename('group__%s.html' % name, 'index.html')
470 for i in (glob.glob('*.png') +
471 glob.glob('*.html') +
474 if i != 'index.html' and i != 'style.css':
477 os.chdir(build_dir(name, 'doc/man/man3'))
478 for i in glob.glob('*.3'):
479 os.system("sed -i 's/%s_API //' %s" % (NAME, i))
480 for i in glob.glob('_*'):
483 except Exception as e:
484 Logs.error("Failed to fix up %s documentation: %s" % (name, e))
486 # Doxygen API documentation
487 def build_dox(bld, name, version, srcdir, blddir, outdir=''):
488 if not bld.env['DOCS']:
492 src_dir = os.path.join(srcdir, name.lower())
493 doc_dir = os.path.join(blddir, name.lower(), 'doc')
496 doc_dir = os.path.join(blddir, 'doc')
498 subst_tg = bld(features = 'subst',
499 source = 'doc/reference.doxygen.in',
500 target = 'doc/reference.doxygen',
505 name + '_VERSION' : version,
506 name + '_SRCDIR' : os.path.abspath(src_dir),
507 name + '_DOC_DIR' : os.path.abspath(doc_dir)
510 subst_tg.__dict__.update(subst_dict)
514 docs = bld(features = 'doxygen',
515 doxyfile = 'doc/reference.doxygen')
519 major = int(version[0:version.find('.')])
521 os.path.join('${DOCDIR}', '%s-%d' % (name.lower(), major), outdir, 'html'),
522 bld.path.get_bld().ant_glob('doc/html/*'))
523 for i in range(1, 8):
524 bld.install_files('${MANDIR}/man%d' % i,
525 bld.path.get_bld().ant_glob('doc/man/man%d/*' % i,
528 # Version code file generation
529 def build_version_files(header_path, source_path, domain, major, minor, micro, exportname, visheader):
530 header_path = os.path.abspath(header_path)
531 source_path = os.path.abspath(source_path)
532 text = "int " + domain + "_major_version = " + str(major) + ";\n"
533 text += "int " + domain + "_minor_version = " + str(minor) + ";\n"
534 text += "int " + domain + "_micro_version = " + str(micro) + ";\n"
536 o = open(source_path, 'w')
540 Logs.error('Failed to open %s for writing\n' % source_path)
543 text = "#ifndef __" + domain + "_version_h__\n"
544 text += "#define __" + domain + "_version_h__\n"
546 text += "#include \"" + visheader + "\"\n"
547 text += exportname + " extern const char* " + domain + "_revision;\n"
548 text += exportname + " extern int " + domain + "_major_version;\n"
549 text += exportname + " extern int " + domain + "_minor_version;\n"
550 text += exportname + " extern int " + domain + "_micro_version;\n"
551 text += "#endif /* __" + domain + "_version_h__ */\n"
553 o = open(header_path, 'w')
557 Logs.warn('Failed to open %s for writing\n' % header_path)
562 def build_i18n_pot(bld, srcdir, dir, name, sources, copyright_holder=None):
563 Logs.info('Generating pot file from %s' % name)
564 pot_file = '%s.pot' % name
575 cmd += ['--copyright-holder="%s"' % copyright_holder]
578 Logs.info('Updating ' + pot_file)
579 subprocess.call(cmd, cwd=os.path.join(srcdir, dir))
581 def build_i18n_po(bld, srcdir, dir, name, sources, copyright_holder=None):
583 os.chdir(os.path.join(srcdir, dir))
584 pot_file = '%s.pot' % name
585 po_files = glob.glob('po/*.po')
586 for po_file in po_files:
589 '--no-fuzzy-matching',
592 Logs.info('Updating ' + po_file)
596 def build_i18n_mo(bld, srcdir, dir, name, sources, copyright_holder=None):
598 os.chdir(os.path.join(srcdir, dir))
599 pot_file = '%s.pot' % name
600 po_files = glob.glob('po/*.po')
601 for po_file in po_files:
602 mo_file = po_file.replace('.po', '.mo')
609 Logs.info('Generating ' + po_file)
613 def build_i18n(bld, srcdir, dir, name, sources, copyright_holder=None):
614 build_i18n_pot(bld, srcdir, dir, name, sources, copyright_holder)
615 build_i18n_po(bld, srcdir, dir, name, sources, copyright_holder)
616 build_i18n_mo(bld, srcdir, dir, name, sources, copyright_holder)
618 def cd_to_build_dir(ctx, appname):
619 orig_dir = os.path.abspath(os.curdir)
620 top_level = (len(ctx.stack_path) > 1)
622 os.chdir(os.path.join('build', appname))
625 Logs.pprint('GREEN', "Waf: Entering directory `%s'" % os.path.abspath(os.getcwd()))
627 def cd_to_orig_dir(ctx, child):
629 os.chdir(os.path.join('..', '..'))
633 def pre_test(ctx, appname, dirs=['src']):
636 diropts += ' -d ' + i
637 cd_to_build_dir(ctx, appname)
638 clear_log = open('lcov-clear.log', 'w')
641 # Clear coverage data
642 subprocess.call(('lcov %s -z' % diropts).split(),
643 stdout=clear_log, stderr=clear_log)
645 Logs.warn('Failed to run lcov, no coverage report will be generated')
649 def post_test(ctx, appname, dirs=['src'], remove=['*boost*', 'c++*']):
652 diropts += ' -d ' + i
653 coverage_log = open('lcov-coverage.log', 'w')
654 coverage_lcov = open('coverage.lcov', 'w')
655 coverage_stripped_lcov = open('coverage-stripped.lcov', 'w')
662 # Generate coverage data
663 subprocess.call(('lcov -c %s -b %s' % (diropts, base)).split(),
664 stdout=coverage_lcov, stderr=coverage_log)
666 # Strip unwanted stuff
668 ['lcov', '--remove', 'coverage.lcov'] + remove,
669 stdout=coverage_stripped_lcov, stderr=coverage_log)
671 # Generate HTML coverage output
672 if not os.path.isdir('coverage'):
673 os.makedirs('coverage')
674 subprocess.call('genhtml -o coverage coverage-stripped.lcov'.split(),
675 stdout=coverage_log, stderr=coverage_log)
678 Logs.warn('Failed to run lcov, no coverage report will be generated')
680 coverage_stripped_lcov.close()
681 coverage_lcov.close()
685 Logs.pprint('GREEN', "Waf: Leaving directory `%s'" % os.path.abspath(os.getcwd()))
686 top_level = (len(ctx.stack_path) > 1)
688 cd_to_orig_dir(ctx, top_level)
691 Logs.pprint('BOLD', 'Coverage:', sep='')
692 print('<file://%s>\n\n' % os.path.abspath('coverage/index.html'))
694 def run_tests(ctx, appname, tests, desired_status=0, dirs=['src'], name='*'):
698 diropts += ' -d ' + i
703 if type(i) == type([]):
706 Logs.pprint('BOLD', '** Test', sep='')
707 Logs.pprint('NORMAL', '%s' % s)
709 if Options.options.grind:
710 cmd = 'valgrind ' + i
711 if subprocess.call(cmd, shell=True) == desired_status:
712 Logs.pprint('GREEN', '** Pass')
715 Logs.pprint('RED', '** FAIL')
719 Logs.pprint('GREEN', '** Pass: All %s.%s tests passed' % (appname, name))
721 Logs.pprint('RED', '** FAIL: %d %s.%s tests failed' % (failures, appname, name))
723 def run_ldconfig(ctx):
724 if (ctx.cmd == 'install'
725 and not ctx.env['RAN_LDCONFIG']
726 and ctx.env['LIBDIR']
727 and not 'DESTDIR' in os.environ
728 and not Options.options.destdir):
730 Logs.info("Waf: Running `/sbin/ldconfig %s'" % ctx.env['LIBDIR'])
731 subprocess.call(['/sbin/ldconfig', ctx.env['LIBDIR']])
732 ctx.env['RAN_LDCONFIG'] = True
736 def write_news(name, in_files, out_file, top_entries=None, extra_entries=None):
739 from time import strftime, strptime
741 doap = rdflib.Namespace('http://usefulinc.com/ns/doap#')
742 dcs = rdflib.Namespace('http://ontologi.es/doap-changeset#')
743 rdfs = rdflib.Namespace('http://www.w3.org/2000/01/rdf-schema#')
744 foaf = rdflib.Namespace('http://xmlns.com/foaf/0.1/')
745 rdf = rdflib.Namespace('http://www.w3.org/1999/02/22-rdf-syntax-ns#')
746 m = rdflib.ConjunctiveGraph()
750 m.parse(i, format='n3')
752 Logs.warn('Error parsing data, unable to generate NEWS')
755 proj = m.value(None, rdf.type, doap.Project)
756 for f in m.triples([proj, rdfs.seeAlso, None]):
757 if f[2].endswith('.ttl'):
758 m.parse(f[2], format='n3')
761 for r in m.triples([proj, doap.release, None]):
763 revision = m.value(release, doap.revision, None)
764 date = m.value(release, doap.created, None)
765 blamee = m.value(release, dcs.blame, None)
766 changeset = m.value(release, dcs.changeset, None)
767 dist = m.value(release, doap['file-release'], None)
769 if revision and date and blamee and changeset:
770 entry = '%s (%s) stable;\n' % (name, revision)
772 for i in m.triples([changeset, dcs.item, None]):
773 item = textwrap.wrap(m.value(i[2], rdfs.label, None), width=79)
774 entry += '\n * ' + '\n '.join(item)
775 if dist and top_entries is not None:
776 if not str(dist) in top_entries:
777 top_entries[str(dist)] = []
778 top_entries[str(dist)] += [
779 '%s: %s' % (name, '\n '.join(item))]
782 for i in extra_entries[str(dist)]:
787 blamee_name = m.value(blamee, foaf.name, None)
788 blamee_mbox = m.value(blamee, foaf.mbox, None)
789 if blamee_name and blamee_mbox:
790 entry += ' %s <%s>' % (blamee_name,
791 blamee_mbox.replace('mailto:', ''))
793 entry += ' %s\n\n' % (
794 strftime('%a, %d %b %Y %H:%M:%S +0000', strptime(date, '%Y-%m-%d')))
796 entries[revision] = entry
798 Logs.warn('Ignored incomplete %s release description' % name)
801 news = open(out_file, 'w')
802 for e in sorted(entries.keys(), reverse=True):
803 news.write(entries[e])