1#!/usr/bin/env python
2
3import os
4import sys
5
6from waflib import Build, Logs, Options, TaskGen
7from waflib.extras import autowaf
8
9# Library and package version (UNIX style major, minor, micro)
10# major increment <=> incompatible changes
11# minor increment <=> compatible changes (additions)
12# micro increment <=> no interface changes
13PUGL_VERSION       = '0.2.0'
14PUGL_MAJOR_VERSION = '0'
15
16# Mandatory waf variables
17APPNAME = 'pugl'        # Package name for waf dist
18VERSION = PUGL_VERSION  # Package version for waf dist
19top     = '.'           # Source directory
20out     = 'build'       # Build directory
21
22
23def options(ctx):
24    ctx.load('compiler_c')
25    ctx.load('compiler_cxx')
26
27    opts = ctx.configuration_options()
28    opts.add_option('--target', default=None, dest='target',
29                    help='target platform (e.g. "win32" or "darwin")')
30
31    ctx.add_flags(
32        opts,
33        {'all-headers': 'install complete header implementation',
34         'no-vulkan':   'do not build Vulkan support',
35         'no-gl':       'do not build OpenGL support',
36         'no-cxx':      'do not build C++ examples',
37         'no-cairo':    'do not build Cairo support',
38         'no-static':   'do not build static library',
39         'no-shared':   'do not build shared library'})
40
41    ctx.get_option_group('Test options').add_option(
42        '--gui-tests', action='store_true', help='Run GUI tests')
43
44
45def configure(conf):
46    conf.load('compiler_c', cache=True)
47    if not Options.options.no_cxx:
48        try:
49            conf.load('compiler_cxx', cache=True)
50        except Exception:
51            pass
52
53    conf.load('autowaf', cache=True)
54    autowaf.set_c_lang(conf, 'c99')
55    if 'COMPILER_CXX' in conf.env:
56        autowaf.set_cxx_lang(conf, 'c++11')
57
58    conf.env.TARGET_PLATFORM = Options.options.target or sys.platform
59    platform                 = conf.env.TARGET_PLATFORM
60
61    data_dir = '%s/%s' % (conf.env.DATADIR, 'pugl-%s' % PUGL_MAJOR_VERSION)
62
63    if Options.options.strict:
64        # Check for programs used by lint target
65        conf.find_program("flake8", var="FLAKE8", mandatory=False)
66        conf.find_program("clang-tidy", var="CLANG_TIDY", mandatory=False)
67        conf.find_program("iwyu_tool", var="IWYU_TOOL", mandatory=False)
68
69    if Options.options.ultra_strict:
70        # All warnings enabled by autowaf, disable some we trigger
71
72        autowaf.add_compiler_flags(conf.env, '*', {
73            'clang': [
74                '-Wno-padded',
75                '-Wno-reserved-id-macro',
76                '-Wno-switch-enum',
77            ],
78            'gcc': [
79                '-Wno-inline',
80                '-Wno-padded',
81                '-Wno-suggest-attribute=const',
82                '-Wno-suggest-attribute=malloc',
83                '-Wno-suggest-attribute=pure',
84                '-Wno-switch-enum',
85            ],
86            'msvc': [
87                '/wd4061',  # enumerator in switch is not explicitly handled
88                '/wd4514',  # unreferenced inline function has been removed
89                '/wd4710',  # function not inlined
90                '/wd4711',  # function selected for automatic inline expansion
91                '/wd4820',  # padding added after construct
92                '/wd4996',  # POSIX name for this item is deprecated
93                '/wd5045',  # will insert Spectre mitigation for memory load
94            ],
95        })
96
97        autowaf.add_compiler_flags(conf.env, 'c', {
98            'clang': [
99                '-Wno-bad-function-cast',
100                '-Wno-float-equal',
101                '-Wno-implicit-fallthrough',
102            ],
103            'gcc': [
104                '-Wno-bad-function-cast',
105                '-Wno-float-equal',
106                '-Wno-pedantic',
107            ],
108            'msvc': [
109                '/wd4191',  # unsafe conversion from type to type
110                '/wd4706',  # assignment within conditional expression
111                '/wd4710',  # function not inlined
112                '/wd5045',  # will insert Spectre mitigation for memory load
113            ],
114        })
115
116        autowaf.add_compiler_flags(conf.env, 'cxx', {
117            'clang': [
118                '-Wno-cast-align',  # pugl_vulkan_cxx_demo
119                '-Wno-documentation-unknown-command',
120                '-Wno-old-style-cast',
121            ],
122            'gcc': [
123                '-Wno-cast-align',  # pugl_vulkan_cxx_demo
124                '-Wno-effc++',
125                '-Wno-old-style-cast',
126                '-Wno-suggest-final-methods',
127                '-Wno-useless-cast',
128            ],
129            'msvc': [
130                '/wd4191',  # unsafe conversion between function pointers
131                '/wd4355',  # 'this' used in base member initializer list
132                '/wd4571',  # structured exceptions (SEH) are no longer caught
133                '/wd4623',  # default constructor implicitly deleted
134                '/wd4625',  # copy constructor implicitly deleted
135                '/wd4626',  # assignment operator implicitly deleted
136                '/wd4706',  # assignment within conditional expression
137                '/wd4868',  # may not enforce left-to-right evaluation order
138                '/wd5026',  # move constructor implicitly deleted
139                '/wd5027',  # move assignment operator implicitly deleted
140                '/wd5039',  # pointer to throwing function passed to C function
141            ],
142        })
143
144        # Add some platform-specific warning suppressions
145        if conf.env.TARGET_PLATFORM == "win32":
146            autowaf.add_compiler_flags(conf.env, '*', {
147                'gcc': ['-Wno-cast-function-type',
148                        '-Wno-conversion',
149                        '-Wno-format',
150                        '-Wno-suggest-attribute=format'],
151                'clang': ['-D_CRT_SECURE_NO_WARNINGS',
152                          '-Wno-format-nonliteral',
153                          '-Wno-nonportable-system-include-path'],
154            })
155        elif conf.env.TARGET_PLATFORM == 'darwin':
156            autowaf.add_compiler_flags(conf.env, '*', {
157                'clang': ['-DGL_SILENCE_DEPRECATION',
158                          '-Wno-deprecated-declarations',
159                          '-Wno-direct-ivar-access'],
160                'gcc': ['-DGL_SILENCE_DEPRECATION',
161                        '-Wno-deprecated-declarations',
162                        '-Wno-direct-ivar-access'],
163            })
164
165    if Options.options.debug and not Options.options.no_coverage:
166        conf.env.append_unique('CFLAGS', ['--coverage'])
167        conf.env.append_unique('CXXFLAGS', ['--coverage'])
168        conf.env.append_unique('LINKFLAGS', ['--coverage'])
169
170    if conf.env.DOCS:
171        conf.load('python')
172        conf.load('sphinx')
173        conf.find_program('doxygen', var='DOXYGEN')
174
175        if not (conf.env.DOXYGEN and conf.env.SPHINX_BUILD):
176            conf.env.DOCS = False
177
178    sys_header = 'windows.h' if platform == 'win32' else ''
179
180    if not Options.options.no_vulkan:
181        vulkan_sdk = os.environ.get('VULKAN_SDK', None)
182        vulkan_cflags = ''
183        if vulkan_sdk:
184            vk_include_path = os.path.join(vulkan_sdk, 'Include')
185            vulkan_cflags = conf.env.CPPPATH_ST % vk_include_path
186
187        # Check for Vulkan header (needed for backends)
188        conf.check(features='c cxx',
189                   cflags=vulkan_cflags,
190                   cxxflags=vulkan_cflags,
191                   header_name=sys_header + ' vulkan/vulkan.h',
192                   uselib_store='VULKAN',
193                   mandatory=False)
194
195        if conf.env.BUILD_TESTS and conf.env.HAVE_VULKAN:
196            # Check for Vulkan library and shader compiler
197            # The library is needed by pugl_vulkan_demo.c which has no loader
198            vulkan_linkflags = ''
199            if vulkan_sdk:
200                vk_lib_path = os.path.join(vulkan_sdk, 'Lib')
201                vulkan_linkflags = conf.env.LIBPATH_ST % vk_lib_path
202
203            # The Vulkan library has a different name on Windows
204            for l in ['vulkan', 'vulkan-1']:
205                if conf.check(lib=l,
206                              uselib_store='VULKAN_LIB',
207                              cflags=vulkan_cflags,
208                              linkflags=vulkan_linkflags,
209                              mandatory=False):
210                    break
211
212            validator_name = 'glslangValidator'
213            if vulkan_sdk:
214                vk_bin_path = os.path.join(vulkan_sdk, 'bin')
215                validator_name = os.path.join(vk_bin_path, 'glslangValidator')
216
217            conf.find_program(validator_name, var='GLSLANGVALIDATOR')
218
219    # Check for base system libraries needed on some systems
220    conf.check_cc(lib='pthread', uselib_store='PTHREAD', mandatory=False)
221    conf.check_cc(lib='m', uselib_store='M', mandatory=False)
222    conf.check_cc(lib='dl', uselib_store='DL', mandatory=False)
223
224    # Check for "native" platform dependencies
225    conf.env.HAVE_GL = False
226    if platform == 'darwin':
227        conf.check_cc(framework_name='Cocoa', framework='Cocoa',
228                      uselib_store='COCOA')
229        conf.check_cc(framework_name='Corevideo', framework='Corevideo',
230                      uselib_store='COREVIDEO')
231        if not Options.options.no_gl:
232            conf.check_cc(framework_name='OpenGL', uselib_store='GL',
233                          mandatory=False)
234            conf.env.HAVE_GL = conf.env.FRAMEWORK_GL
235
236    elif platform == 'win32':
237        conf.check_cc(lib='gdi32', uselib_store='GDI32')
238        conf.check_cc(lib='user32', uselib_store='USER32')
239        if not Options.options.no_gl:
240            conf.check_cc(lib='opengl32', uselib_store='GL', mandatory=False)
241            conf.env.HAVE_GL = conf.env.LIB_GL
242
243    else:
244        conf.check_cc(lib='X11', uselib_store='X11')
245
246        xsync_fragment = """#include <X11/Xlib.h>
247            #include <X11/extensions/sync.h>
248            int main(void) { XSyncQueryExtension(0, 0, 0); return 0; }"""
249        if conf.check_cc(fragment=xsync_fragment,
250                         uselib_store='XSYNC',
251                         lib='Xext',
252                         mandatory=False,
253                         msg='Checking for function XSyncQueryExtension'):
254            conf.define('HAVE_XSYNC', 1)
255
256        if conf.check_cc(lib='Xcursor',
257                         uselib_store='XCURSOR',
258                         mandatory=False):
259            conf.define('HAVE_XCURSOR', 1)
260
261        if conf.check_cc(lib='Xrandr',
262                         uselib_store='XRANDR',
263                         mandatory=False):
264            conf.define('HAVE_XRANDR', 1)
265
266        if not Options.options.no_gl:
267            glx_fragment = """#include <GL/glx.h>
268                int main(void) { glXSwapBuffers(0, 0); return 0; }"""
269
270            conf.check_cc(lib='GLX', uselib_store='GLX', mandatory=False)
271            conf.check_cc(lib='GL', uselib_store='GL', mandatory=False)
272            conf.check_cc(fragment=glx_fragment,
273                          lib='GLX' if conf.env.LIB_GLX else 'GL',
274                          mandatory=False,
275                          msg='Checking for GLX')
276            conf.env.HAVE_GL = conf.env.LIB_GL or conf.env.LIB_GLX
277
278    # Check for Cairo via pkg-config
279    if not Options.options.no_cairo:
280        autowaf.check_pkg(conf, 'cairo',
281                          uselib_store    = 'CAIRO',
282                          atleast_version = '1.0.0',
283                          system          = True,
284                          mandatory       = False)
285
286    conf.env.update({
287        'BUILD_SHARED': not Options.options.no_shared,
288        'BUILD_STATIC': conf.env.BUILD_TESTS or not Options.options.no_static,
289        'BUILD_STRICT_HEADER_TEST': Options.options.strict,
290        'PUGL_MAJOR_VERSION': PUGL_MAJOR_VERSION,
291    })
292
293    if conf.env.TARGET_PLATFORM == 'win32':
294        conf.env.PUGL_PLATFORM = 'win'
295    elif conf.env.TARGET_PLATFORM == 'darwin':
296        conf.env.PUGL_PLATFORM = 'mac'
297    else:
298        conf.env.PUGL_PLATFORM = 'x11'
299
300    conf.define('PUGL_DATA_DIR', data_dir)
301
302    autowaf.set_lib_env(conf, 'pugl', PUGL_VERSION,
303                        lib='pugl_' + conf.env.PUGL_PLATFORM)
304
305    autowaf.set_lib_env(conf, 'pugl_gl', PUGL_VERSION,
306                        lib='pugl_%s_gl' % conf.env.PUGL_PLATFORM)
307
308    autowaf.display_summary(
309        conf,
310        {"Build static library":   bool(conf.env.BUILD_STATIC),
311         "Build shared library":   bool(conf.env.BUILD_SHARED),
312         "Cairo support":          bool(conf.env.HAVE_CAIRO),
313         "OpenGL support":         bool(conf.env.HAVE_GL),
314         "Vulkan support":         bool(conf.env.HAVE_VULKAN)})
315
316
317def _build_pc_file(bld,
318                   name,
319                   desc,
320                   target,
321                   libname,
322                   deps={},
323                   requires=[],
324                   cflags=[]):
325    "Builds a pkg-config file for a library"
326    env = bld.env
327    prefix = env.PREFIX
328    xprefix = os.path.dirname(env.LIBDIR)
329    if libname is not None:
330        libname += '-%s' % PUGL_MAJOR_VERSION
331
332    uselib   = deps.get('uselib', [])
333    pkg_deps = [l for l in uselib if 'PKG_' + l.lower() in env]
334    lib_deps = [l for l in uselib if 'PKG_' + l.lower() not in env]
335    lib      = deps.get('lib', []) + [libname] if libname is not None else []
336
337    link_flags = [env.LIB_ST % l for l in lib]
338    for l in lib_deps:
339        link_flags += [env.LIB_ST % l for l in env['LIB_' + l]]
340    for f in deps.get('framework', []):
341        link_flags += ['-framework', f]
342
343    bld(features='subst',
344        source='pugl.pc.in',
345        target='%s-%s.pc' % (target, PUGL_MAJOR_VERSION),
346        install_path=os.path.join(env.LIBDIR, 'pkgconfig'),
347
348        PREFIX=prefix,
349        EXEC_PREFIX='${prefix}' if xprefix == prefix else xprefix,
350        LIBDIR='${exec_prefix}/' + os.path.basename(env.LIBDIR),
351        INCLUDEDIR=env.INCLUDEDIR.replace(prefix, '${prefix}', 1),
352
353        NAME=name,
354        DESCRIPTION=desc,
355        PUGL_MAJOR_VERSION=PUGL_MAJOR_VERSION,
356        REQUIRES=' '.join(requires + [p.lower() for p in pkg_deps]),
357        LIBS=' '.join(link_flags),
358        CFLAGS=' '.join(cflags))
359
360
361gl_tests = ['gl_hints']
362
363basic_tests = [
364    'clipboard',
365    'realize',
366    'redisplay',
367    'show_hide',
368    'stub_hints',
369    'timer',
370    'update',
371]
372
373tests = gl_tests + basic_tests
374
375
376def concatenate(task):
377    """Task to concatenate all input files into the output file"""
378    with open(task.outputs[0].abspath(), 'w') as out:
379        for filename in task.inputs:
380            with open(filename.abspath(), 'r') as source:
381                for line in source:
382                    out.write(line)
383
384
385def build(bld):
386    # C Headers
387    includedir = '${INCLUDEDIR}/pugl-%s/pugl' % PUGL_MAJOR_VERSION
388    bld.install_files(includedir, bld.path.ant_glob('include/pugl/*.h'))
389
390    if 'COMPILER_CXX' in bld.env:
391        # C++ Headers
392        includedirxx = '${INCLUDEDIR}/puglxx-%s/pugl' % PUGL_MAJOR_VERSION
393        bld.install_files(includedirxx,
394                          bld.path.ant_glob('bindings/cxx/include/pugl/*.hpp'))
395        bld.install_files(includedirxx,
396                          bld.path.ant_glob('bindings/cxx/include/pugl/*.ipp'))
397
398    # Library dependencies of pugl libraries (for building examples)
399    deps = {}
400
401    data_dir = os.path.join(bld.env.DATADIR, 'pugl-%s' % PUGL_MAJOR_VERSION)
402
403    def build_pugl_lib(name, **kwargs):
404        deps[name] = {}
405        for k in ('lib', 'framework', 'uselib'):
406            deps[name][k] = kwargs.get(k, [])
407
408        args = kwargs.copy()
409        args.update({'includes':        ['include'],
410                     'export_includes': ['include'],
411                     'install_path':    '${LIBDIR}',
412                     'vnum':            PUGL_VERSION})
413
414        flags = []
415        if not bld.env.MSVC_COMPILER:
416            flags = ['-fvisibility=hidden']
417        if bld.env.TARGET_PLATFORM != 'win32':
418            flags = ['-fPIC']
419
420        if bld.env.BUILD_SHARED:
421            bld(features  = 'c cshlib',
422                name      = name,
423                target    = 'pugl_%s-%s' % (name, PUGL_MAJOR_VERSION),
424                defines   = ['PUGL_INTERNAL'],
425                cflags    = flags,
426                linkflags = flags,
427                **args)
428
429        if bld.env.BUILD_STATIC:
430            bld(features  = 'c cstlib',
431                name      = 'pugl_%s_static' % name,
432                target    = 'pugl_%s-%s' % (name, PUGL_MAJOR_VERSION),
433                defines   = ['PUGL_STATIC', 'PUGL_INTERNAL', 'PUGL_DISABLE_DEPRECATED'],
434                cflags    = flags,
435                linkflags = flags,
436                **args)
437
438    def build_platform(platform, **kwargs):
439        build_pugl_lib(platform, **kwargs)
440        _build_pc_file(bld, 'Pugl', 'Pugl GUI library core',
441                       'pugl', 'pugl_%s' % platform,
442                       deps=kwargs,
443                       cflags=['-I${includedir}/pugl-%s' % PUGL_MAJOR_VERSION])
444
445    def build_backend(platform, backend, **kwargs):
446        name  = '%s_%s' % (platform, backend)
447        label = 'OpenGL' if backend == 'gl' else backend.title()
448        build_pugl_lib(name, **kwargs)
449        _build_pc_file(bld, 'Pugl %s' % label,
450                       'Pugl GUI library with %s backend' % label,
451                       'pugl-%s' % backend, 'pugl_' + name,
452                       deps=kwargs,
453                       requires=['pugl-%s' % PUGL_MAJOR_VERSION],
454                       cflags=['-I${includedir}/pugl-%s' % PUGL_MAJOR_VERSION])
455
456    lib_source = ['src/implementation.c']
457    if bld.env.TARGET_PLATFORM == 'win32':
458        platform = 'win'
459        build_platform('win',
460                       uselib=['GDI32', 'USER32'],
461                       source=lib_source + ['src/win.c'])
462
463        build_backend('win', 'stub',
464                      uselib=['GDI32', 'USER32'],
465                      source=['src/win_stub.c'])
466
467        if bld.env.HAVE_GL:
468            build_backend('win', 'gl',
469                          uselib=['GDI32', 'USER32', 'GL'],
470                          source=['src/win_gl.c'])
471
472        if bld.env.HAVE_VULKAN:
473            build_backend('win', 'vulkan',
474                          uselib=['GDI32', 'USER32', 'VULKAN'],
475                          source=['src/win_vulkan.c', 'src/win_stub.c'])
476
477        if bld.env.HAVE_CAIRO:
478            build_backend('win', 'cairo',
479                          uselib=['CAIRO', 'GDI32', 'USER32'],
480                          source=['src/win_cairo.c', 'src/win_stub.c'])
481
482    elif bld.env.TARGET_PLATFORM == 'darwin':
483        platform = 'mac'
484        build_platform('mac',
485                       framework=['Cocoa', 'Corevideo'],
486                       source=lib_source + ['src/mac.m'])
487
488        build_backend('mac', 'stub',
489                      framework=['Cocoa', 'Corevideo'],
490                      source=['src/mac_stub.m'])
491
492        if bld.env.HAVE_GL:
493            build_backend('mac', 'gl',
494                          framework=['Cocoa', 'Corevideo', 'OpenGL'],
495                          source=['src/mac_gl.m'])
496
497        if bld.env.HAVE_VULKAN:
498            build_backend('mac', 'vulkan',
499                          framework=['Cocoa', 'QuartzCore'],
500                          source=['src/mac_vulkan.m'])
501
502        if bld.env.HAVE_CAIRO:
503            build_backend('mac', 'cairo',
504                          framework=['Cocoa', 'Corevideo'],
505                          uselib=['CAIRO'],
506                          source=['src/mac_cairo.m'])
507    else:
508        platform = 'x11'
509        build_platform('x11',
510                       uselib=['M', 'X11', 'XSYNC', 'XCURSOR', 'XRANDR'],
511                       source=lib_source + ['src/x11.c'])
512
513        build_backend('x11', 'stub',
514                      uselib=['X11'],
515                      source=['src/x11_stub.c'])
516
517        if bld.env.HAVE_GL:
518            glx_lib = 'GLX' if bld.env.LIB_GLX else 'GL'
519            build_backend('x11', 'gl',
520                          uselib=[glx_lib, 'X11'],
521                          source=['src/x11_gl.c'])
522
523        if bld.env.HAVE_VULKAN:
524            build_backend('x11', 'vulkan',
525                          uselib=['DL', 'X11'],
526                          source=['src/x11_vulkan.c', 'src/x11_stub.c'])
527
528        if bld.env.HAVE_CAIRO:
529            build_backend('x11', 'cairo',
530                          uselib=['CAIRO', 'X11'],
531                          source=['src/x11_cairo.c', 'src/x11_stub.c'])
532
533    if 'COMPILER_CXX' in bld.env:
534        _build_pc_file(
535            bld, 'Pugl C++',
536            'C++ bindings for the Pugl GUI library',
537            'puglxx',
538            None,
539            requires=['pugl-%s' % PUGL_MAJOR_VERSION],
540            cflags=['-I${includedir}/puglxx-%s' % PUGL_MAJOR_VERSION])
541
542    bld.add_group()
543
544    def build_example(prog, source, platform, backend, **kwargs):
545        lang = 'cxx' if source[0].endswith('.cpp') else 'c'
546
547        includes = ['.'] + (['bindings/cxx/include'] if lang == 'cxx' else [])
548
549        use = ['pugl_%s_static' % platform,
550               'pugl_%s_%s_static' % (platform, backend)]
551
552        target = 'examples/' + prog
553        if bld.env.TARGET_PLATFORM == 'darwin':
554            target = '{0}.app/Contents/MacOS/{0}'.format(prog)
555
556            bld(features     = 'subst',
557                source       = 'resources/Info.plist.in',
558                target       = '{}.app/Contents/Info.plist'.format(prog),
559                includes     = includes,
560                install_path = '',
561                NAME         = prog)
562
563        backend_lib = platform + '_' + backend
564        for k in ('lib', 'framework', 'uselib'):
565            kwargs.update({k: (kwargs.get(k, []) +
566                               deps.get(platform, {}).get(k, []) +
567                               deps.get(backend_lib, {}).get(k, []))})
568
569        kwargs['defines'] = kwargs.get('defines', []) + ['PUGL_STATIC']
570
571        bld(features     = '%s %sprogram' % (lang, lang),
572            source       = source,
573            target       = target,
574            includes     = includes,
575            use          = use,
576            install_path = bld.env.BINDIR,
577            **kwargs)
578
579    if bld.env.BUILD_TESTS:
580        for s in [
581                'header_330.glsl',
582                'header_420.glsl',
583                'rect.frag',
584                'rect.vert',
585        ]:
586            # Copy shaders to build directory for example programs
587            bld(features = 'subst',
588                is_copy  = True,
589                source   = 'examples/shaders/%s' % s,
590                target   = 'examples/shaders/%s' % s,
591                install_path = os.path.join(data_dir, 'shaders'))
592
593        if bld.env.HAVE_GL:
594            glad_cflags = [] if bld.env.MSVC_COMPILER else ['-Wno-pedantic']
595            build_example('pugl_embed_demo', ['examples/pugl_embed_demo.c'],
596                          platform, 'gl', uselib=['GL', 'M'])
597            build_example('pugl_window_demo', ['examples/pugl_window_demo.c'],
598                          platform, 'gl', uselib=['GL', 'M'])
599            build_example('pugl_cursor_demo', ['examples/pugl_cursor_demo.c'],
600                          platform, 'gl', uselib=['GL', 'M'])
601            build_example('pugl_print_events',
602                          ['examples/pugl_print_events.c'],
603                          platform, 'stub')
604            build_example('pugl_shader_demo',
605                          ['examples/pugl_shader_demo.c',
606                           'examples/file_utils.c',
607                           'examples/glad/glad.c'],
608                          platform, 'gl',
609                          cflags=glad_cflags,
610                          uselib=['DL', 'GL', 'M'])
611
612            for test in gl_tests:
613                bld(features     = 'c cprogram',
614                    source       = 'test/test_%s.c' % test,
615                    target       = 'test/test_%s' % test,
616                    install_path = '',
617                    defines      = ['PUGL_STATIC'],
618                    use          = ['pugl_%s_static' % platform,
619                                    'pugl_%s_gl_static' % platform],
620                    uselib       = deps[platform]['uselib'] + ['GL'])
621
622        if bld.env.HAVE_VULKAN and 'GLSLANGVALIDATOR' in bld.env:
623            for s in ['rect.vert', 'rect.frag']:
624                complete = bld.path.get_bld().make_node(
625                    'shaders/%s' % s.replace('.', '.vulkan.'))
626                bld(rule = concatenate,
627                    source = ['examples/shaders/header_420.glsl',
628                              'examples/shaders/%s' % s],
629                    target = complete)
630
631                cmd = bld.env.GLSLANGVALIDATOR[0] + " -V -o ${TGT} ${SRC}"
632                bld(rule = cmd,
633                    source = complete,
634                    target = 'examples/shaders/%s.spv' % s,
635                    install_path = os.path.join(data_dir, 'shaders'))
636
637            build_example('pugl_vulkan_demo',
638                          ['examples/pugl_vulkan_demo.c'],
639                          platform,
640                          'vulkan', uselib=['M', 'VULKAN', 'VULKAN_LIB'])
641
642            if bld.env.CXX:
643                build_example('pugl_vulkan_cxx_demo',
644                              ['examples/pugl_vulkan_cxx_demo.cpp',
645                               'examples/file_utils.c'],
646                              platform, 'vulkan',
647                              defines=['PUGL_STATIC', 'PUGL_DISABLE_DEPRECATED'],
648                              uselib=['DL', 'M', 'PTHREAD', 'VULKAN'])
649
650        if bld.env.HAVE_CAIRO:
651            build_example('pugl_cairo_demo', ['examples/pugl_cairo_demo.c'],
652                          platform, 'cairo',
653                          uselib=['M', 'CAIRO'])
654
655        for test in basic_tests:
656            bld(features     = 'c cprogram',
657                source       = 'test/test_%s.c' % test,
658                target       = 'test/test_%s' % test,
659                install_path = '',
660                defines      = ['PUGL_STATIC'],
661                use          = ['pugl_%s_static' % platform,
662                                'pugl_%s_stub_static' % platform,
663                                'pugl_%s_gl_static' % platform],
664                uselib       = deps[platform]['uselib'] + ['CAIRO'])
665
666        if bld.env.BUILD_STRICT_HEADER_TEST:
667            # Make a hyper strict warning environment for checking API headers
668            strict_env = bld.env.derive()
669            autowaf.remove_all_warning_flags(strict_env)
670            autowaf.enable_all_warnings(strict_env)
671            autowaf.set_warnings_as_errors(strict_env)
672            autowaf.add_compiler_flags(strict_env, '*', {
673                'clang': ['-Wno-padded'],
674                'gcc': ['-Wno-padded', '-Wno-suggest-attribute=const'],
675                'msvc': [
676                    '/wd4514',  # unreferenced inline function has been removed
677                    '/wd4820',  # padding added after construct
678                ],
679            })
680            autowaf.add_compiler_flags(strict_env, 'cxx', {
681                'clang': ['-Wno-documentation-unknown-command'],
682                'msvc': [
683                    '/wd4355',  # 'this' used in base member initializer list
684                    '/wd4571',  # structured exceptions are no longer caught
685                ],
686            })
687
688            # Check that C headers build with (almost) no warnings
689            bld(features     = 'c cprogram',
690                source       = 'test/test_build.c',
691                target       = 'test/test_build_c',
692                install_path = '',
693                env          = strict_env,
694                use          = ['pugl_%s_static' % platform],
695                uselib       = deps[platform]['uselib'] + ['CAIRO'])
696
697            # Check that C++ headers build with (almost) no warnings
698            bld(features     = 'cxx cxxprogram',
699                source       = 'test/test_build.cpp',
700                target       = 'test/test_build_cpp',
701                install_path = '',
702                env          = strict_env,
703                includes     = ['bindings/cxx/include'],
704                use          = ['pugl_%s_static' % platform],
705                uselib       = deps[platform]['uselib'] + ['CAIRO'])
706
707        if bld.env.CXX and bld.env.HAVE_GL:
708            build_example('pugl_cxx_demo', ['examples/pugl_cxx_demo.cpp'],
709                          platform, 'gl',
710                          defines=['PUGL_DISABLE_DEPRECATED'],
711                          uselib=['GL', 'M'])
712
713    if bld.env.DOCS:
714        bld.recurse('doc/c')
715        bld.recurse('doc/cpp')
716
717
718def test(tst):
719    if tst.options.gui_tests:
720        with tst.group('gui') as check:
721            for test in basic_tests:
722                check(['test/test_%s' % test])
723
724            if tst.env.HAVE_GL:
725                for test in gl_tests:
726                    check(['test/test_%s' % test])
727
728
729class LintContext(Build.BuildContext):
730    fun = cmd = 'lint'
731
732
733def lint(ctx):
734    "checks code for style issues"
735    import glob
736    import json
737    import subprocess
738
739    st = 0
740
741    if "FLAKE8" in ctx.env:
742        Logs.info("Running flake8")
743        st = subprocess.call([ctx.env.FLAKE8[0],
744                              "wscript",
745                              "--ignore",
746                              "E221,W504,E251,E241,E741"])
747    else:
748        Logs.warn("Not running flake8")
749
750    if "IWYU_TOOL" in ctx.env:
751        Logs.info("Running include-what-you-use")
752        cmd = [ctx.env.IWYU_TOOL[0], "-o", "clang", "-p", "build", "--",
753               "-Xiwyu", "--check_also=*.hpp",
754               "-Xiwyu", "--check_also=examples/*.hpp",
755               "-Xiwyu", "--check_also=examples/*",
756               "-Xiwyu", "--check_also=pugl/*.h",
757               "-Xiwyu", "--check_also=bindings/cxx/include/pugl/*.*",
758               "-Xiwyu", "--check_also=src/*.c",
759               "-Xiwyu", "--check_also=src/*.h",
760               "-Xiwyu", "--check_also=src/*.m"]
761        output = subprocess.check_output(cmd).decode('utf-8')
762        if 'error: ' in output:
763            sys.stdout.write(output)
764            st += 1
765    else:
766        Logs.warn("Not running include-what-you-use")
767
768    if "CLANG_TIDY" in ctx.env and "clang" in ctx.env.CC[0]:
769        Logs.info("Running clang-tidy")
770        with open('build/compile_commands.json', 'r') as db:
771            commands = json.load(db)
772            files = [c['file'] for c in commands
773                     if os.path.basename(c['file']) != 'glad.c']
774
775            c_files = [os.path.join('build', f)
776                       for f in files if f.endswith('.c')]
777
778            c_files += glob.glob('include/pugl/*.h')
779            c_files += glob.glob('src/implementation.h')
780
781            cpp_files = [os.path.join('build', f)
782                         for f in files if f.endswith('.cpp')]
783
784            cpp_files += glob.glob('bindings/cxx/include/pugl/*')
785
786        c_files = list(map(os.path.abspath, c_files))
787        cpp_files = list(map(os.path.abspath, cpp_files))
788
789        procs = []
790        for c_file in c_files:
791            cmd = [ctx.env.CLANG_TIDY[0], "--quiet", "-p=.", c_file]
792            procs += [subprocess.Popen(cmd, cwd="build")]
793
794        for cpp_file in cpp_files:
795            cmd = [ctx.env.CLANG_TIDY[0],
796                   '--header-filter=".*"',
797                   "--quiet",
798                   "-p=.", cpp_file]
799            procs += [subprocess.Popen(cmd, cwd="build")]
800
801        for proc in procs:
802            stdout, stderr = proc.communicate()
803            st += proc.returncode
804    else:
805        Logs.warn("Not running clang-tidy")
806
807    if st != 0:
808        sys.exit(st)
809
810
811# Alias .m files to be compiled like .c files, gcc will do the right thing.
812@TaskGen.extension('.m')
813def m_hook(self, node):
814    return self.create_compiled_task('c', node)
815