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.
15 from waflib import Configure, Context, Logs, Node, Options, Task, Utils
16 from waflib.TaskGen import feature, before, after
21 # Only run autowaf hooks once (even if sub projects call several times)
25 # Compute dependencies globally
27 #preproc.go_absolute = True
30 @after('apply_incpaths')
31 def include_config_h(self):
32 self.env.append_value('INCPATHS', self.bld.bldnode.abspath())
34 def set_options(opt, debug_by_default=False):
35 "Add standard autowaf options if they havn't been added yet"
40 # Install directory options
41 dirs_options = opt.add_option_group('Installation directories', '')
43 # Move --prefix and --destdir to directory options group
44 for k in ('--prefix', '--destdir'):
45 option = opt.parser.get_option(k)
47 opt.parser.remove_option(k)
48 dirs_options.add_option(option)
50 # Standard directory options
51 dirs_options.add_option('--bindir', type='string',
52 help="Executable programs [Default: PREFIX/bin]")
53 dirs_options.add_option('--configdir', type='string',
54 help="Configuration data [Default: PREFIX/etc]")
55 dirs_options.add_option('--datadir', type='string',
56 help="Shared data [Default: PREFIX/share]")
57 dirs_options.add_option('--includedir', type='string',
58 help="Header files [Default: PREFIX/include]")
59 dirs_options.add_option('--libdir', type='string',
60 help="Libraries [Default: PREFIX/lib]")
61 dirs_options.add_option('--mandir', type='string',
62 help="Manual pages [Default: DATADIR/man]")
63 dirs_options.add_option('--docdir', type='string',
64 help="HTML documentation [Default: DATADIR/doc]")
68 opt.add_option('--optimize', action='store_false', default=True, dest='debug',
69 help="Build optimized binaries")
71 opt.add_option('--debug', action='store_true', default=False, dest='debug',
72 help="Build debuggable binaries")
74 opt.add_option('--pardebug', action='store_true', default=False, dest='pardebug',
75 help="Build parallel-installable debuggable libraries with D suffix")
77 opt.add_option('--grind', action='store_true', default=False, dest='grind',
78 help="Run tests in valgrind")
79 opt.add_option('--strict', action='store_true', default=False, dest='strict',
80 help="Use strict compiler flags and show all warnings")
81 opt.add_option('--ultra-strict', action='store_true', default=False, dest='ultra_strict',
82 help="Use even stricter compiler flags (likely to trigger many warnings in library headers)")
83 opt.add_option('--docs', action='store_true', default=False, dest='docs',
84 help="Build documentation - requires doxygen")
87 opt.add_option('--lv2-user', action='store_true', default=False, dest='lv2_user',
88 help="Install LV2 bundles to user location")
89 opt.add_option('--lv2-system', action='store_true', default=False, dest='lv2_system',
90 help="Install LV2 bundles to system location")
91 dirs_options.add_option('--lv2dir', type='string',
92 help="LV2 bundles [Default: LIBDIR/lv2]")
96 # a cross-platform utility for copying files as part of tasks
97 src = task.inputs[0].abspath()
98 tgt = task.outputs[0].abspath()
99 shutil.copy2 (src, tgt)
101 def check_header(conf, lang, name, define='', mandatory=True):
103 includes = '' # search default system include paths
104 if sys.platform == "darwin":
105 includes = '/opt/local/include'
108 check_func = conf.check_cc
110 check_func = conf.check_cxx
112 Logs.error("Unknown header language `%s'" % lang)
116 check_func(header_name=name, includes=includes,
117 define_name=define, mandatory=mandatory)
119 check_func(header_name=name, includes=includes, mandatory=mandatory)
122 return name.replace('/', '_').replace('++', 'PP').replace('-', '_').replace('.', '_')
124 def define(conf, var_name, value):
125 conf.define(var_name, value)
126 conf.env[var_name] = value
128 def check_pkg(conf, name, **args):
129 "Check for a package iff it hasn't been checked for yet"
130 if args['uselib_store'].lower() in conf.env['AUTOWAF_LOCAL_LIBS']:
135 var_name = 'CHECKED_' + nameify(args['uselib_store'])
136 check = not var_name in conf.env
137 mandatory = not 'mandatory' in args or args['mandatory']
138 if not check and 'atleast_version' in args:
139 # Re-check if version is newer than previous check
140 checked_version = conf.env['VERSION_' + name]
141 if checked_version and checked_version < args['atleast_version']:
143 if not check and mandatory and conf.env[var_name] == CheckType.OPTIONAL:
144 # Re-check if previous check was optional but this one is mandatory
148 pkg_var_name = 'PKG_' + name.replace('-', '_')
150 if conf.env.PARDEBUG:
151 args['mandatory'] = False # Smash mandatory arg
152 found = conf.check_cfg(package=pkg_name + 'D', args="--cflags --libs", **args)
156 args['mandatory'] = True # Unsmash mandatory arg
158 found = conf.check_cfg(package=pkg_name, args="--cflags --libs", **args)
160 conf.env[pkg_var_name] = pkg_name
161 if 'atleast_version' in args:
162 conf.env['VERSION_' + name] = args['atleast_version']
164 conf.env[var_name] = CheckType.MANDATORY
166 conf.env[var_name] = CheckType.OPTIONAL
170 if sys.platform == 'win32':
171 return os.path.normpath(path).replace('\\', '/')
173 return os.path.normpath(path)
179 def append_cxx_flags(flags):
180 conf.env.append_value('CFLAGS', flags)
181 conf.env.append_value('CXXFLAGS', flags)
183 display_header('Global Configuration')
185 if Options.options.docs:
188 conf.env['DOCS'] = Options.options.docs
189 conf.env['DEBUG'] = Options.options.debug or Options.options.pardebug
190 conf.env['PARDEBUG'] = Options.options.pardebug
191 conf.env['PREFIX'] = normpath(os.path.abspath(os.path.expanduser(conf.env['PREFIX'])))
193 def config_dir(var, opt, default):
195 conf.env[var] = normpath(opt)
197 conf.env[var] = normpath(default)
199 opts = Options.options
200 prefix = conf.env['PREFIX']
202 config_dir('BINDIR', opts.bindir, os.path.join(prefix, 'bin'))
203 config_dir('SYSCONFDIR', opts.configdir, os.path.join(prefix, 'etc'))
204 config_dir('DATADIR', opts.datadir, os.path.join(prefix, 'share'))
205 config_dir('INCLUDEDIR', opts.includedir, os.path.join(prefix, 'include'))
206 config_dir('LIBDIR', opts.libdir, os.path.join(prefix, 'lib'))
207 config_dir('MANDIR', opts.mandir, os.path.join(conf.env['DATADIR'], 'man'))
208 config_dir('DOCDIR', opts.docdir, os.path.join(conf.env['DATADIR'], 'doc'))
210 if Options.options.lv2dir:
211 conf.env['LV2DIR'] = Options.options.lv2dir
212 elif Options.options.lv2_user:
213 if sys.platform == "darwin":
214 conf.env['LV2DIR'] = os.path.join(os.getenv('HOME'), 'Library/Audio/Plug-Ins/LV2')
215 elif sys.platform == "win32":
216 conf.env['LV2DIR'] = os.path.join(os.getenv('APPDATA'), 'LV2')
218 conf.env['LV2DIR'] = os.path.join(os.getenv('HOME'), '.lv2')
219 elif Options.options.lv2_system:
220 if sys.platform == "darwin":
221 conf.env['LV2DIR'] = '/Library/Audio/Plug-Ins/LV2'
222 elif sys.platform == "win32":
223 conf.env['LV2DIR'] = os.path.join(os.getenv('COMMONPROGRAMFILES'), 'LV2')
225 conf.env['LV2DIR'] = os.path.join(conf.env['LIBDIR'], 'lv2')
227 conf.env['LV2DIR'] = os.path.join(conf.env['LIBDIR'], 'lv2')
229 conf.env['LV2DIR'] = normpath(conf.env['LV2DIR'])
231 if Options.options.docs:
232 doxygen = conf.find_program('doxygen')
234 conf.fatal("Doxygen is required to build with --docs")
236 dot = conf.find_program('dot')
238 conf.fatal("Graphviz (dot) is required to build with --docs")
240 if Options.options.debug:
241 if conf.env['MSVC_COMPILER']:
242 conf.env['CFLAGS'] = ['/Od', '/Zi', '/MTd']
243 conf.env['CXXFLAGS'] = ['/Od', '/Zi', '/MTd']
244 conf.env['LINKFLAGS'] = ['/DEBUG']
246 conf.env['CFLAGS'] = ['-O0', '-g']
247 conf.env['CXXFLAGS'] = ['-O0', '-g']
249 if conf.env['MSVC_COMPILER']:
250 conf.env['CFLAGS'] = ['/MD']
251 conf.env['CXXFLAGS'] = ['/MD']
252 append_cxx_flags(['-DNDEBUG'])
254 if Options.options.ultra_strict:
255 Options.options.strict = True
256 conf.env.append_value('CFLAGS', ['-Wredundant-decls',
257 '-Wstrict-prototypes',
258 '-Wmissing-prototypes'])
260 if Options.options.strict:
261 conf.env.append_value('CFLAGS', ['-std=c99', '-pedantic', '-Wshadow'])
262 conf.env.append_value('CXXFLAGS', ['-ansi',
263 '-Wnon-virtual-dtor',
264 '-Woverloaded-virtual'])
265 append_cxx_flags(['-Wall',
269 if sys.platform != "darwin":
270 # this is really only to be avoid on OLD apple gcc, but not sure how to version check
271 append_cxx_flags(['-fstrict-overflow'])
273 if not conf.check_cc(fragment = '''
277 int main() { return 0; }''',
281 msg = 'Checking for clang'):
282 if sys.platform != "darwin":
283 # this is really only to be avoid on OLD apple gcc, but not sure how to version check
284 append_cxx_flags(['-Wunsafe-loop-optimizations'])
285 # this is invalid (still) on Lion apple gcc
286 append_cxx_flags(['-Wlogical-op'])
289 if not conf.env['MSVC_COMPILER']:
290 append_cxx_flags(['-fshow-column'])
292 conf.env.prepend_value('CFLAGS', '-I' + os.path.abspath('.'))
293 conf.env.prepend_value('CXXFLAGS', '-I' + os.path.abspath('.'))
295 display_msg(conf, "Install prefix", conf.env['PREFIX'])
296 display_msg(conf, "Debuggable build", str(conf.env['DEBUG']))
297 display_msg(conf, "Build documentation", str(conf.env['DOCS']))
302 def set_c99_mode(conf):
303 if conf.env.MSVC_COMPILER:
304 # MSVC has no hope or desire to compile C99, just compile as C++
305 conf.env.append_unique('CFLAGS', ['-TP'])
307 conf.env.append_unique('CFLAGS', ['-std=c99'])
309 def set_local_lib(conf, name, has_objects):
310 var_name = 'HAVE_' + nameify(name.upper())
311 define(conf, var_name, 1)
313 if type(conf.env['AUTOWAF_LOCAL_LIBS']) != dict:
314 conf.env['AUTOWAF_LOCAL_LIBS'] = {}
315 conf.env['AUTOWAF_LOCAL_LIBS'][name.lower()] = True
317 if type(conf.env['AUTOWAF_LOCAL_HEADERS']) != dict:
318 conf.env['AUTOWAF_LOCAL_HEADERS'] = {}
319 conf.env['AUTOWAF_LOCAL_HEADERS'][name.lower()] = True
321 def append_property(obj, key, val):
322 if hasattr(obj, key):
323 setattr(obj, key, getattr(obj, key) + val)
325 setattr(obj, key, val)
327 def use_lib(bld, obj, libs):
328 abssrcdir = os.path.abspath('.')
329 libs_list = libs.split()
331 in_headers = l.lower() in bld.env['AUTOWAF_LOCAL_HEADERS']
332 in_libs = l.lower() in bld.env['AUTOWAF_LOCAL_LIBS']
334 append_property(obj, 'use', ' lib%s ' % l.lower())
335 append_property(obj, 'framework', bld.env['FRAMEWORK_' + l])
336 if in_headers or in_libs:
337 inc_flag = '-iquote ' + os.path.join(abssrcdir, l.lower())
338 for f in ['CFLAGS', 'CXXFLAGS']:
339 if not inc_flag in bld.env[f]:
340 bld.env.prepend_value(f, inc_flag)
342 append_property(obj, 'uselib', ' ' + l)
345 @before('apply_link')
346 def version_lib(self):
347 if sys.platform == 'win32':
348 self.vnum = None # Prevent waf from automatically appending -0
349 if self.env['PARDEBUG']:
350 applicable = ['cshlib', 'cxxshlib', 'cstlib', 'cxxstlib']
351 if [x for x in applicable if x in self.features]:
352 self.target = self.target + 'D'
354 def set_lib_env(conf, name, version):
355 'Set up environment for local library as if found via pkg-config.'
357 major_ver = version.split('.')[0]
358 pkg_var_name = 'PKG_' + name.replace('-', '_')
359 lib_name = '%s-%s' % (name, major_ver)
360 if conf.env.PARDEBUG:
362 conf.env[pkg_var_name] = lib_name
363 conf.env['INCLUDES_' + NAME] = ['${INCLUDEDIR}/%s-%s' % (name, major_ver)]
364 conf.env['LIBPATH_' + NAME] = [conf.env.LIBDIR]
365 conf.env['LIB_' + NAME] = [lib_name]
367 def display_header(title):
368 Logs.pprint('BOLD', title)
370 def display_msg(conf, msg, status = None, color = None):
372 if type(status) == bool and status or status == "True":
374 elif type(status) == bool and not status or status == "False":
376 Logs.pprint('BOLD', " *", sep='')
377 Logs.pprint('NORMAL', "%s" % msg.ljust(conf.line_just - 3), sep='')
378 Logs.pprint('BOLD', ":", sep='')
379 Logs.pprint(color, status)
381 def link_flags(env, lib):
382 return ' '.join(map(lambda x: env['LIB_ST'] % x, env['LIB_' + lib]))
384 def compile_flags(env, lib):
385 return ' '.join(map(lambda x: env['CPPPATH_ST'] % x, env['INCLUDES_' + lib]))
396 def build_pc(bld, name, version, version_suffix, libs, subst_dict={}):
397 '''Build a pkg-config file for a library.
398 name -- uppercase variable name (e.g. 'SOMENAME')
399 version -- version string (e.g. '1.2.3')
400 version_suffix -- name version suffix (e.g. '2')
401 libs -- string/list of dependencies (e.g. 'LIBFOO GLIB')
403 pkg_prefix = bld.env['PREFIX']
404 if pkg_prefix[-1] == '/':
405 pkg_prefix = pkg_prefix[:-1]
407 target = name.lower()
408 if version_suffix != '':
409 target += '-' + version_suffix
411 if bld.env['PARDEBUG']:
416 libdir = bld.env['LIBDIR']
417 if libdir.startswith(pkg_prefix):
418 libdir = libdir.replace(pkg_prefix, '${exec_prefix}')
420 includedir = bld.env['INCLUDEDIR']
421 if includedir.startswith(pkg_prefix):
422 includedir = includedir.replace(pkg_prefix, '${prefix}')
424 obj = bld(features = 'subst',
425 source = '%s.pc.in' % name.lower(),
427 install_path = os.path.join(bld.env['LIBDIR'], 'pkgconfig'),
428 exec_prefix = '${prefix}',
430 EXEC_PREFIX = '${prefix}',
432 INCLUDEDIR = includedir)
434 if type(libs) != list:
437 subst_dict[name + '_VERSION'] = version
438 subst_dict[name + '_MAJOR_VERSION'] = version[0:version.find('.')]
440 subst_dict[i + '_LIBS'] = link_flags(bld.env, i)
441 lib_cflags = compile_flags(bld.env, i)
444 subst_dict[i + '_CFLAGS'] = lib_cflags
446 obj.__dict__.update(subst_dict)
448 def build_dir(name, subdir):
450 return os.path.join('build', name, subdir)
452 return os.path.join('build', subdir)
454 # Clean up messy Doxygen documentation after it is built
455 def make_simple_dox(name):
460 os.chdir(build_dir(name, 'doc/html'))
461 page = 'group__%s.html' % name
462 if not os.path.exists(page):
465 ['%s_API ' % NAME, ''],
466 ['%s_DEPRECATED ' % NAME, ''],
467 ['group__%s.html' % name, ''],
469 ['<script.*><\/script>', ''],
470 ['<hr\/><a name="details" id="details"><\/a><h2>.*<\/h2>', ''],
471 ['<link href=\"tabs.css\" rel=\"stylesheet\" type=\"text\/css\"\/>',
473 ['<img class=\"footer\" src=\"doxygen.png\" alt=\"doxygen\"\/>',
475 os.system("sed -i 's/%s/%s/g' %s" % (i[0], i[1], page))
476 os.rename('group__%s.html' % name, 'index.html')
477 for i in (glob.glob('*.png') +
478 glob.glob('*.html') +
481 if i != 'index.html' and i != 'style.css':
484 os.chdir(build_dir(name, 'doc/man/man3'))
485 for i in glob.glob('*.3'):
486 os.system("sed -i 's/%s_API //' %s" % (NAME, i))
487 for i in glob.glob('_*'):
490 except Exception as e:
491 Logs.error("Failed to fix up %s documentation: %s" % (name, e))
493 # Doxygen API documentation
494 def build_dox(bld, name, version, srcdir, blddir, outdir=''):
495 if not bld.env['DOCS']:
499 src_dir = os.path.join(srcdir, name.lower())
500 doc_dir = os.path.join(blddir, name.lower(), 'doc')
503 doc_dir = os.path.join(blddir, 'doc')
505 subst_tg = bld(features = 'subst',
506 source = 'doc/reference.doxygen.in',
507 target = 'doc/reference.doxygen',
512 name + '_VERSION' : version,
513 name + '_SRCDIR' : os.path.abspath(src_dir),
514 name + '_DOC_DIR' : os.path.abspath(doc_dir)
517 subst_tg.__dict__.update(subst_dict)
521 docs = bld(features = 'doxygen',
522 doxyfile = 'doc/reference.doxygen')
526 major = int(version[0:version.find('.')])
528 os.path.join('${DOCDIR}', '%s-%d' % (name.lower(), major), outdir, 'html'),
529 bld.path.get_bld().ant_glob('doc/html/*'))
530 for i in range(1, 8):
531 bld.install_files('${MANDIR}/man%d' % i,
532 bld.path.get_bld().ant_glob('doc/man/man%d/*' % i,
535 # Version code file generation
536 def build_version_files(header_path, source_path, domain, major, minor, micro, exportname, visheader):
537 header_path = os.path.abspath(header_path)
538 source_path = os.path.abspath(source_path)
539 text = "int " + domain + "_major_version = " + str(major) + ";\n"
540 text += "int " + domain + "_minor_version = " + str(minor) + ";\n"
541 text += "int " + domain + "_micro_version = " + str(micro) + ";\n"
543 o = open(source_path, 'w')
547 Logs.error('Failed to open %s for writing\n' % source_path)
550 text = "#ifndef __" + domain + "_version_h__\n"
551 text += "#define __" + domain + "_version_h__\n"
553 text += "#include \"" + visheader + "\"\n"
554 text += exportname + " extern const char* " + domain + "_revision;\n"
555 text += exportname + " extern int " + domain + "_major_version;\n"
556 text += exportname + " extern int " + domain + "_minor_version;\n"
557 text += exportname + " extern int " + domain + "_micro_version;\n"
558 text += "#endif /* __" + domain + "_version_h__ */\n"
560 o = open(header_path, 'w')
564 Logs.warn('Failed to open %s for writing\n' % header_path)
569 def build_i18n_pot(bld, srcdir, dir, name, sources, copyright_holder=None):
570 Logs.info('Generating pot file from %s' % name)
571 pot_file = '%s.pot' % name
582 cmd += ['--copyright-holder="%s"' % copyright_holder]
585 Logs.info('Updating ' + pot_file)
586 subprocess.call(cmd, cwd=os.path.join(srcdir, dir))
588 def build_i18n_po(bld, srcdir, dir, name, sources, copyright_holder=None):
590 os.chdir(os.path.join(srcdir, dir))
591 pot_file = '%s.pot' % name
592 po_files = glob.glob('po/*.po')
593 for po_file in po_files:
596 '--no-fuzzy-matching',
599 Logs.info('Updating ' + po_file)
603 def build_i18n_mo(bld, srcdir, dir, name, sources, copyright_holder=None):
605 os.chdir(os.path.join(srcdir, dir))
606 pot_file = '%s.pot' % name
607 po_files = glob.glob('po/*.po')
608 for po_file in po_files:
609 mo_file = po_file.replace('.po', '.mo')
616 Logs.info('Generating ' + po_file)
620 def build_i18n(bld, srcdir, dir, name, sources, copyright_holder=None):
621 build_i18n_pot(bld, srcdir, dir, name, sources, copyright_holder)
622 build_i18n_po(bld, srcdir, dir, name, sources, copyright_holder)
623 build_i18n_mo(bld, srcdir, dir, name, sources, copyright_holder)
625 def cd_to_build_dir(ctx, appname):
626 orig_dir = os.path.abspath(os.curdir)
627 top_level = (len(ctx.stack_path) > 1)
629 os.chdir(os.path.join('build', appname))
632 Logs.pprint('GREEN', "Waf: Entering directory `%s'" % os.path.abspath(os.getcwd()))
634 def cd_to_orig_dir(ctx, child):
636 os.chdir(os.path.join('..', '..'))
640 def pre_test(ctx, appname, dirs=['src']):
643 diropts += ' -d ' + i
644 cd_to_build_dir(ctx, appname)
645 clear_log = open('lcov-clear.log', 'w')
648 # Clear coverage data
649 subprocess.call(('lcov %s -z' % diropts).split(),
650 stdout=clear_log, stderr=clear_log)
652 Logs.warn('Failed to run lcov, no coverage report will be generated')
656 def post_test(ctx, appname, dirs=['src'], remove=['*boost*', 'c++*']):
659 diropts += ' -d ' + i
660 coverage_log = open('lcov-coverage.log', 'w')
661 coverage_lcov = open('coverage.lcov', 'w')
662 coverage_stripped_lcov = open('coverage-stripped.lcov', 'w')
669 # Generate coverage data
670 subprocess.call(('lcov -c %s -b %s' % (diropts, base)).split(),
671 stdout=coverage_lcov, stderr=coverage_log)
673 # Strip unwanted stuff
675 ['lcov', '--remove', 'coverage.lcov'] + remove,
676 stdout=coverage_stripped_lcov, stderr=coverage_log)
678 # Generate HTML coverage output
679 if not os.path.isdir('coverage'):
680 os.makedirs('coverage')
681 subprocess.call('genhtml -o coverage coverage-stripped.lcov'.split(),
682 stdout=coverage_log, stderr=coverage_log)
685 Logs.warn('Failed to run lcov, no coverage report will be generated')
687 coverage_stripped_lcov.close()
688 coverage_lcov.close()
692 Logs.pprint('GREEN', "Waf: Leaving directory `%s'" % os.path.abspath(os.getcwd()))
693 top_level = (len(ctx.stack_path) > 1)
695 cd_to_orig_dir(ctx, top_level)
698 Logs.pprint('BOLD', 'Coverage:', sep='')
699 print('<file://%s>\n\n' % os.path.abspath('coverage/index.html'))
701 def run_tests(ctx, appname, tests, desired_status=0, dirs=['src'], name='*'):
705 diropts += ' -d ' + i
710 if type(i) == type([]):
713 Logs.pprint('BOLD', '** Test', sep='')
714 Logs.pprint('NORMAL', '%s' % s)
716 if Options.options.grind:
717 cmd = 'valgrind ' + i
718 if subprocess.call(cmd, shell=True) == desired_status:
719 Logs.pprint('GREEN', '** Pass')
722 Logs.pprint('RED', '** FAIL')
726 Logs.pprint('GREEN', '** Pass: All %s.%s tests passed' % (appname, name))
728 Logs.pprint('RED', '** FAIL: %d %s.%s tests failed' % (failures, appname, name))
730 def run_ldconfig(ctx):
731 if (ctx.cmd == 'install'
732 and not ctx.env['RAN_LDCONFIG']
733 and ctx.env['LIBDIR']
734 and not 'DESTDIR' in os.environ
735 and not Options.options.destdir):
737 Logs.info("Waf: Running `/sbin/ldconfig %s'" % ctx.env['LIBDIR'])
738 subprocess.call(['/sbin/ldconfig', ctx.env['LIBDIR']])
739 ctx.env['RAN_LDCONFIG'] = True
743 def write_news(name, in_files, out_file, top_entries=None, extra_entries=None):
746 from time import strftime, strptime
748 doap = rdflib.Namespace('http://usefulinc.com/ns/doap#')
749 dcs = rdflib.Namespace('http://ontologi.es/doap-changeset#')
750 rdfs = rdflib.Namespace('http://www.w3.org/2000/01/rdf-schema#')
751 foaf = rdflib.Namespace('http://xmlns.com/foaf/0.1/')
752 rdf = rdflib.Namespace('http://www.w3.org/1999/02/22-rdf-syntax-ns#')
753 m = rdflib.ConjunctiveGraph()
757 m.parse(i, format='n3')
759 Logs.warn('Error parsing data, unable to generate NEWS')
762 proj = m.value(None, rdf.type, doap.Project)
763 for f in m.triples([proj, rdfs.seeAlso, None]):
764 if f[2].endswith('.ttl'):
765 m.parse(f[2], format='n3')
768 for r in m.triples([proj, doap.release, None]):
770 revision = m.value(release, doap.revision, None)
771 date = m.value(release, doap.created, None)
772 blamee = m.value(release, dcs.blame, None)
773 changeset = m.value(release, dcs.changeset, None)
774 dist = m.value(release, doap['file-release'], None)
776 if revision and date and blamee and changeset:
777 entry = '%s (%s) stable;\n' % (name, revision)
779 for i in m.triples([changeset, dcs.item, None]):
780 item = textwrap.wrap(m.value(i[2], rdfs.label, None), width=79)
781 entry += '\n * ' + '\n '.join(item)
782 if dist and top_entries is not None:
783 if not str(dist) in top_entries:
784 top_entries[str(dist)] = []
785 top_entries[str(dist)] += [
786 '%s: %s' % (name, '\n '.join(item))]
789 for i in extra_entries[str(dist)]:
794 blamee_name = m.value(blamee, foaf.name, None)
795 blamee_mbox = m.value(blamee, foaf.mbox, None)
796 if blamee_name and blamee_mbox:
797 entry += ' %s <%s>' % (blamee_name,
798 blamee_mbox.replace('mailto:', ''))
800 entry += ' %s\n\n' % (
801 strftime('%a, %d %b %Y %H:%M:%S +0000', strptime(date, '%Y-%m-%d')))
803 entries[revision] = entry
805 Logs.warn('Ignored incomplete %s release description' % name)
808 news = open(out_file, 'w')
809 for e in sorted(entries.keys(), reverse=True):
810 news.write(entries[e])