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):
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"
552 text += " extern const char* " + domain + "_revision;\n"
553 text += " extern int " + domain + "_major_version;\n"
554 text += " extern int " + domain + "_minor_version;\n"
555 text += " extern int " + domain + "_micro_version;\n"
556 text += "#endif /* __" + domain + "_version_h__ */\n"
558 o = open(header_path, 'w')
562 Logs.warn('Failed to open %s for writing\n' % header_path)
567 def build_i18n_pot(bld, srcdir, dir, name, sources, copyright_holder=None):
568 Logs.info('Generating pot file from %s' % name)
569 pot_file = '%s.pot' % name
580 cmd += ['--copyright-holder="%s"' % copyright_holder]
583 Logs.info('Updating ' + pot_file)
584 subprocess.call(cmd, cwd=os.path.join(srcdir, dir))
586 def build_i18n_po(bld, srcdir, dir, name, sources, copyright_holder=None):
588 os.chdir(os.path.join(srcdir, dir))
589 pot_file = '%s.pot' % name
590 po_files = glob.glob('po/*.po')
591 for po_file in po_files:
594 '--no-fuzzy-matching',
597 Logs.info('Updating ' + po_file)
601 def build_i18n_mo(bld, srcdir, dir, name, sources, copyright_holder=None):
603 os.chdir(os.path.join(srcdir, dir))
604 pot_file = '%s.pot' % name
605 po_files = glob.glob('po/*.po')
606 for po_file in po_files:
607 mo_file = po_file.replace('.po', '.mo')
614 Logs.info('Generating ' + po_file)
618 def build_i18n(bld, srcdir, dir, name, sources, copyright_holder=None):
619 build_i18n_pot(bld, srcdir, dir, name, sources, copyright_holder)
620 build_i18n_po(bld, srcdir, dir, name, sources, copyright_holder)
621 build_i18n_mo(bld, srcdir, dir, name, sources, copyright_holder)
623 def cd_to_build_dir(ctx, appname):
624 orig_dir = os.path.abspath(os.curdir)
625 top_level = (len(ctx.stack_path) > 1)
627 os.chdir(os.path.join('build', appname))
630 Logs.pprint('GREEN', "Waf: Entering directory `%s'" % os.path.abspath(os.getcwd()))
632 def cd_to_orig_dir(ctx, child):
634 os.chdir(os.path.join('..', '..'))
638 def pre_test(ctx, appname, dirs=['src']):
641 diropts += ' -d ' + i
642 cd_to_build_dir(ctx, appname)
643 clear_log = open('lcov-clear.log', 'w')
646 # Clear coverage data
647 subprocess.call(('lcov %s -z' % diropts).split(),
648 stdout=clear_log, stderr=clear_log)
650 Logs.warn('Failed to run lcov, no coverage report will be generated')
654 def post_test(ctx, appname, dirs=['src'], remove=['*boost*', 'c++*']):
657 diropts += ' -d ' + i
658 coverage_log = open('lcov-coverage.log', 'w')
659 coverage_lcov = open('coverage.lcov', 'w')
660 coverage_stripped_lcov = open('coverage-stripped.lcov', 'w')
667 # Generate coverage data
668 subprocess.call(('lcov -c %s -b %s' % (diropts, base)).split(),
669 stdout=coverage_lcov, stderr=coverage_log)
671 # Strip unwanted stuff
673 ['lcov', '--remove', 'coverage.lcov'] + remove,
674 stdout=coverage_stripped_lcov, stderr=coverage_log)
676 # Generate HTML coverage output
677 if not os.path.isdir('coverage'):
678 os.makedirs('coverage')
679 subprocess.call('genhtml -o coverage coverage-stripped.lcov'.split(),
680 stdout=coverage_log, stderr=coverage_log)
683 Logs.warn('Failed to run lcov, no coverage report will be generated')
685 coverage_stripped_lcov.close()
686 coverage_lcov.close()
690 Logs.pprint('GREEN', "Waf: Leaving directory `%s'" % os.path.abspath(os.getcwd()))
691 top_level = (len(ctx.stack_path) > 1)
693 cd_to_orig_dir(ctx, top_level)
696 Logs.pprint('BOLD', 'Coverage:', sep='')
697 print('<file://%s>\n\n' % os.path.abspath('coverage/index.html'))
699 def run_tests(ctx, appname, tests, desired_status=0, dirs=['src'], name='*'):
703 diropts += ' -d ' + i
708 if type(i) == type([]):
711 Logs.pprint('BOLD', '** Test', sep='')
712 Logs.pprint('NORMAL', '%s' % s)
714 if Options.options.grind:
715 cmd = 'valgrind ' + i
716 if subprocess.call(cmd, shell=True) == desired_status:
717 Logs.pprint('GREEN', '** Pass')
720 Logs.pprint('RED', '** FAIL')
724 Logs.pprint('GREEN', '** Pass: All %s.%s tests passed' % (appname, name))
726 Logs.pprint('RED', '** FAIL: %d %s.%s tests failed' % (failures, appname, name))
728 def run_ldconfig(ctx):
729 if (ctx.cmd == 'install'
730 and not ctx.env['RAN_LDCONFIG']
731 and ctx.env['LIBDIR']
732 and not 'DESTDIR' in os.environ
733 and not Options.options.destdir):
735 Logs.info("Waf: Running `/sbin/ldconfig %s'" % ctx.env['LIBDIR'])
736 subprocess.call(['/sbin/ldconfig', ctx.env['LIBDIR']])
737 ctx.env['RAN_LDCONFIG'] = True
741 def write_news(name, in_files, out_file, top_entries=None, extra_entries=None):
744 from time import strftime, strptime
746 doap = rdflib.Namespace('http://usefulinc.com/ns/doap#')
747 dcs = rdflib.Namespace('http://ontologi.es/doap-changeset#')
748 rdfs = rdflib.Namespace('http://www.w3.org/2000/01/rdf-schema#')
749 foaf = rdflib.Namespace('http://xmlns.com/foaf/0.1/')
750 rdf = rdflib.Namespace('http://www.w3.org/1999/02/22-rdf-syntax-ns#')
751 m = rdflib.ConjunctiveGraph()
755 m.parse(i, format='n3')
757 Logs.warn('Error parsing data, unable to generate NEWS')
760 proj = m.value(None, rdf.type, doap.Project)
761 for f in m.triples([proj, rdfs.seeAlso, None]):
762 if f[2].endswith('.ttl'):
763 m.parse(f[2], format='n3')
766 for r in m.triples([proj, doap.release, None]):
768 revision = m.value(release, doap.revision, None)
769 date = m.value(release, doap.created, None)
770 blamee = m.value(release, dcs.blame, None)
771 changeset = m.value(release, dcs.changeset, None)
772 dist = m.value(release, doap['file-release'], None)
774 if revision and date and blamee and changeset:
775 entry = '%s (%s) stable;\n' % (name, revision)
777 for i in m.triples([changeset, dcs.item, None]):
778 item = textwrap.wrap(m.value(i[2], rdfs.label, None), width=79)
779 entry += '\n * ' + '\n '.join(item)
780 if dist and top_entries is not None:
781 if not str(dist) in top_entries:
782 top_entries[str(dist)] = []
783 top_entries[str(dist)] += [
784 '%s: %s' % (name, '\n '.join(item))]
787 for i in extra_entries[str(dist)]:
792 blamee_name = m.value(blamee, foaf.name, None)
793 blamee_mbox = m.value(blamee, foaf.mbox, None)
794 if blamee_name and blamee_mbox:
795 entry += ' %s <%s>' % (blamee_name,
796 blamee_mbox.replace('mailto:', ''))
798 entry += ' %s\n\n' % (
799 strftime('%a, %d %b %Y %H:%M:%S +0000', strptime(date, '%Y-%m-%d')))
801 entries[revision] = entry
803 Logs.warn('Ignored incomplete %s release description' % name)
806 news = open(out_file, 'w')
807 for e in sorted(entries.keys(), reverse=True):
808 news.write(entries[e])