about_dialog.cc doesn't always rebuild if supporters.cc changes;
[dcpomatic.git] / wscript
1 #
2 #    Copyright (C) 2012-2017 Carl Hetherington <cth@carlh.net>
3 #
4 #    This file is part of DCP-o-matic.
5 #
6 #    DCP-o-matic is free software; you can redistribute it and/or modify
7 #    it under the terms of the GNU General Public License as published by
8 #    the Free Software Foundation; either version 2 of the License, or
9 #    (at your option) any later version.
10 #
11 #    DCP-o-matic is distributed in the hope that it will be useful,
12 #    but WITHOUT ANY WARRANTY; without even the implied warranty of
13 #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 #    GNU General Public License for more details.
15 #
16 #    You should have received a copy of the GNU General Public License
17 #    along with DCP-o-matic.  If not, see <http://www.gnu.org/licenses/>.
18 #
19
20 import subprocess
21 import os
22 import shlex
23 import sys
24 import glob
25 import distutils
26 import distutils.spawn
27 import urllib
28 from waflib import Logs, Context
29
30 APPNAME = 'dcpomatic'
31
32 this_version = subprocess.Popen(shlex.split('git tag -l --points-at HEAD'), stdout=subprocess.PIPE).communicate()[0]
33 last_version = subprocess.Popen(shlex.split('git describe --tags --abbrev=0'), stdout=subprocess.PIPE).communicate()[0]
34
35 if this_version == '':
36     VERSION = '%sdevel' % last_version[1:].strip()
37 else:
38     VERSION = this_version[1:].strip()
39
40 print('Version: %s' % VERSION)
41
42 def options(opt):
43     opt.load('compiler_cxx')
44     opt.load('winres')
45
46     opt.add_option('--enable-debug',      action='store_true', default=False, help='build with debugging information and without optimisation')
47     opt.add_option('--disable-gui',       action='store_true', default=False, help='disable building of GUI tools')
48     opt.add_option('--disable-tests',     action='store_true', default=False, help='disable building of tests')
49     opt.add_option('--install-prefix',                         default=None,  help='prefix of where DCP-o-matic will be installed')
50     opt.add_option('--target-windows',    action='store_true', default=False, help='set up to do a cross-compile to make a Windows package')
51     opt.add_option('--static-dcpomatic',  action='store_true', default=False, help='link to components of DCP-o-matic statically')
52     opt.add_option('--static-boost',      action='store_true', default=False, help='link statically to Boost')
53     opt.add_option('--static-wxwidgets',  action='store_true', default=False, help='link statically to wxWidgets')
54     opt.add_option('--static-ffmpeg',     action='store_true', default=False, help='link statically to FFmpeg')
55     opt.add_option('--static-xmlpp',      action='store_true', default=False, help='link statically to libxml++')
56     opt.add_option('--static-xmlsec',     action='store_true', default=False, help='link statically to xmlsec')
57     opt.add_option('--static-ssh',        action='store_true', default=False, help='link statically to libssh')
58     opt.add_option('--static-cxml',       action='store_true', default=False, help='link statically to libcxml')
59     opt.add_option('--static-dcp',        action='store_true', default=False, help='link statically to libdcp')
60     opt.add_option('--static-sub',        action='store_true', default=False, help='link statically to libsub')
61     opt.add_option('--static-curl',       action='store_true', default=False, help='link statically to libcurl')
62     opt.add_option('--workaround-gssapi', action='store_true', default=False, help='link to gssapi_krb5')
63     opt.add_option('--force-cpp11',       action='store_true', default=False, help='force use of C++11')
64
65 def configure(conf):
66     conf.load('compiler_cxx')
67     conf.load('clang_compilation_database', tooldir=['waf-tools'])
68     if conf.options.target_windows:
69         conf.load('winres')
70
71     # Save conf.options that we need elsewhere in conf.env
72     conf.env.DISABLE_GUI = conf.options.disable_gui
73     conf.env.DISABLE_TESTS = conf.options.disable_tests
74     conf.env.TARGET_WINDOWS = conf.options.target_windows
75     conf.env.TARGET_OSX = sys.platform == 'darwin'
76     conf.env.TARGET_LINUX = not conf.env.TARGET_WINDOWS and not conf.env.TARGET_OSX
77     conf.env.VERSION = VERSION
78     conf.env.DEBUG = conf.options.enable_debug
79     conf.env.STATIC_DCPOMATIC = conf.options.static_dcpomatic
80     if conf.options.install_prefix is None:
81         conf.env.INSTALL_PREFIX = conf.env.PREFIX
82     else:
83         conf.env.INSTALL_PREFIX = conf.options.install_prefix
84
85     # Common CXXFLAGS
86     conf.env.append_value('CXXFLAGS', ['-D__STDC_CONSTANT_MACROS',
87                                        '-D__STDC_LIMIT_MACROS',
88                                        '-D__STDC_FORMAT_MACROS',
89                                        '-msse',
90                                        '-fno-strict-aliasing',
91                                        '-Wall',
92                                        '-Wcast-align',
93                                        '-Wextra',
94                                        '-Wwrite-strings',
95                                        # Remove auto_ptr warnings from libxml++-2.6
96                                        '-Wno-deprecated-declarations',
97                                        '-D_FILE_OFFSET_BITS=64'])
98
99     if conf.options.force_cpp11:
100         conf.env.append_value('CXXFLAGS', ['-std=c++11', '-DBOOST_NO_CXX11_SCOPED_ENUMS'])
101
102     gcc = conf.env['CC_VERSION']
103     if int(gcc[0]) >= 4 and int(gcc[1]) > 1:
104         conf.env.append_value('CXXFLAGS', ['-Wno-unused-result'])
105     have_c11 = int(gcc[0]) >= 4 and int(gcc[1]) >= 8 and int(gcc[2]) >= 1
106
107     if conf.options.enable_debug:
108         conf.env.append_value('CXXFLAGS', ['-g', '-DDCPOMATIC_DEBUG', '-fno-omit-frame-pointer'])
109     else:
110         conf.env.append_value('CXXFLAGS', '-O2')
111
112     #
113     # Windows/Linux/OS X specific
114     #
115
116     # Windows
117     if conf.env.TARGET_WINDOWS:
118         conf.env.append_value('CXXFLAGS', '-DDCPOMATIC_WINDOWS')
119         conf.env.append_value('CXXFLAGS', '-DWIN32_LEAN_AND_MEAN')
120         conf.env.append_value('CXXFLAGS', '-DBOOST_USE_WINDOWS_H')
121         conf.env.append_value('CXXFLAGS', '-DUNICODE')
122         conf.env.append_value('CXXFLAGS', '-DBOOST_THREAD_PROVIDES_GENERIC_SHARED_MUTEX_ON_WIN')
123         conf.env.append_value('CXXFLAGS', '-mfpmath=sse')
124         conf.env.append_value('CXXFLAGS', '-std=c++11')
125         wxrc = os.popen('wx-config --rescomp').read().split()[1:]
126         conf.env.append_value('WINRCFLAGS', wxrc)
127         if conf.options.enable_debug:
128             conf.env.append_value('CXXFLAGS', ['-mconsole'])
129             conf.env.append_value('LINKFLAGS', ['-mconsole'])
130         conf.check(lib='ws2_32', uselib_store='WINSOCK2', msg="Checking for library winsock2")
131         conf.check(lib='dbghelp', uselib_store='DBGHELP', msg="Checking for library dbghelp")
132         conf.check(lib='shlwapi', uselib_store='SHLWAPI', msg="Checking for library shlwapi")
133         conf.check(lib='mswsock', uselib_store='MSWSOCK', msg="Checking for library mswsock")
134         conf.check(lib='ole32', uselib_store='OLE32', msg="Checking for library ole32")
135         conf.check(lib='dsound', uselib_store='DSOUND', msg="Checking for library dsound")
136         conf.check(lib='winmm', uselib_store='WINMM', msg="Checking for library winmm")
137         conf.check(lib='ksuser', uselib_store='KSUSER', msg="Checking for library ksuser")
138         boost_lib_suffix = '-mt'
139         boost_thread = 'boost_thread_win32-mt'
140         conf.check_cxx(fragment="""
141                                #include <boost/locale.hpp>\n
142                                int main() { std::locale::global (boost::locale::generator().generate ("")); }\n
143                                """,
144                                msg='Checking for boost locale library',
145                                libpath='/usr/local/lib',
146                                lib=['boost_locale%s' % boost_lib_suffix, 'boost_system%s' % boost_lib_suffix],
147                                uselib_store='BOOST_LOCALE')
148
149     # POSIX
150     if conf.env.TARGET_LINUX or conf.env.TARGET_OSX:
151         conf.env.append_value('CXXFLAGS', '-DDCPOMATIC_POSIX')
152         boost_lib_suffix = ''
153         boost_thread = 'boost_thread'
154         conf.env.append_value('LINKFLAGS', '-pthread')
155
156     # Linux
157     if conf.env.TARGET_LINUX:
158         conf.env.append_value('CXXFLAGS', '-mfpmath=sse')
159         conf.env.append_value('CXXFLAGS', '-DLINUX_LOCALE_PREFIX="%s/share/locale"' % conf.env['INSTALL_PREFIX'])
160         conf.env.append_value('CXXFLAGS', '-DLINUX_SHARE_PREFIX="%s/share/dcpomatic2"' % conf.env['INSTALL_PREFIX'])
161         conf.env.append_value('CXXFLAGS', '-DDCPOMATIC_LINUX')
162         conf.env.append_value('CXXFLAGS', ['-Wunsafe-loop-optimizations', '-Wlogical-op'])
163         if not conf.env.DISABLE_GUI:
164             conf.check_cfg(package='gtk+-2.0', args='--cflags --libs', uselib_store='GTK', mandatory=True)
165
166     # OSX
167     if conf.env.TARGET_OSX:
168         conf.env.append_value('CXXFLAGS', ['-DDCPOMATIC_OSX', '-Wno-unused-function', '-Wno-unused-parameter', '-Wno-unused-local-typedef', '-Wno-potentially-evaluated-expression'])
169         conf.env.append_value('LINKFLAGS', '-headerpad_max_install_names')
170
171     #
172     # Dependencies.
173     #
174
175     # It should be possible to use check_cfg for both dynamic and static linking, but
176     # e.g. pkg-config --libs --static foo returns some libraries that should be statically
177     # linked and others that should be dynamic.  This doesn't work too well with waf
178     # as it wants them separate.
179
180     # libcurl
181     if conf.options.static_curl:
182         conf.env.STLIB_CURL = ['curl']
183         conf.env.LIB_CURL = ['ssh2', 'idn']
184     else:
185         conf.check_cfg(package='libcurl', args='--cflags --libs', atleast_version='7.19.1', uselib_store='CURL', mandatory=True)
186
187     # libicu
188     if conf.check_cfg(package='icu-i18n', args='--cflags --libs', uselib_store='ICU', mandatory=False) is None:
189         if conf.check_cfg(package='icu', args='--cflags --libs', uselib_store='ICU', mandatory=False) is None:
190             conf.check_cxx(fragment="""
191                             #include <unicode/ucsdet.h>
192                             int main(void) {
193                                 UErrorCode status = U_ZERO_ERROR;
194                                 UCharsetDetector* detector = ucsdet_open (&status);
195                                 return 0; }\n
196                             """,
197                        mandatory=True,
198                        msg='Checking for libicu',
199                        okmsg='yes',
200                        libpath=['/usr/local/lib', '/usr/lib', '/usr/lib/x86_64-linux-gnu'],
201                        lib=['icuio', 'icui18n', 'icudata', 'icuuc'],
202                        uselib_store='ICU')
203
204     # libsamplerate
205     conf.check_cfg(package='samplerate', args='--cflags --libs', uselib_store='SAMPLERATE', mandatory=True)
206
207     # glib
208     conf.check_cfg(package='glib-2.0', args='--cflags --libs', uselib_store='GLIB', mandatory=True)
209
210     # ImageMagick / GraphicsMagick
211     if distutils.spawn.find_executable('Magick++-config'):
212         conf.check_cfg(package='', path='Magick++-config', args='--cppflags --cxxflags --libs', uselib_store='MAGICK', mandatory=True)
213         conf.env.append_value('CXXFLAGS', '-DDCPOMATIC_IMAGE_MAGICK')
214     else:
215         image = conf.check_cfg(package='ImageMagick++', args='--cflags --libs', uselib_store='MAGICK', mandatory=False)
216         graphics = None
217         if image is None:
218             graphics = conf.check_cfg(package='GraphicsMagick++', args='--cflags --libs', uselib_store='MAGICK', mandatory=False)
219         if image is None and graphics is None:
220             Logs.pprint('RED', 'Neither ImageMagick++ nor GraphicsMagick++ found: one or the other is required')
221         if image is not None:
222             conf.env.append_value('CXXFLAGS', '-DDCPOMATIC_IMAGE_MAGICK')
223         if graphics is not None:
224             conf.env.append_value('CXXFLAGS', '-DDCPOMATIC_GRAPHICS_MAGICK')
225
226     # See if we are using the MagickCore or MagickLib namespaces
227     conf.check_cxx(fragment="""
228                             #include <Magick++/Include.h>\n
229                             using namespace MagickCore;\n
230                             int main () { return 0; }\n
231                             """,
232                    mandatory=False,
233                    msg='Checking for MagickCore namespace',
234                    okmsg='yes',
235                    includes=conf.env['INCLUDES_MAGICK'],
236                    define_name='DCPOMATIC_HAVE_MAGICKCORE_NAMESPACE')
237
238     conf.check_cxx(fragment="""
239                             #include <Magick++/Include.h>\n
240                             using namespace MagickLib;\n
241                             int main () { return 0; }\n
242                             """,
243                    mandatory=False,
244                    msg='Checking for MagickLib namespace',
245                    okmsg='yes',
246                    includes=conf.env['INCLUDES_MAGICK'],
247                    define_name='DCPOMATIC_HAVE_MAGICKLIB_NAMESPACE')
248
249     # See where MagickCore.h is
250     conf.check_cxx(fragment="""
251                             #include <magick/MagickCore.h>\n
252                             int main() { return 0; }\n
253                             """,
254                    mandatory=False,
255                    msg='Checking for MagickCore.h location',
256                    okmsg='magick',
257                    errmsg='not magick',
258                    includes=conf.env['INCLUDES_MAGICK'],
259                    define_name='DCPOMATIC_MAGICKCORE_MAGICK')
260
261     conf.check_cxx(fragment="""
262                             #include <MagickCore/MagickCore.h>\n
263                             int main() { return 0; }\n
264                             """,
265                    mandatory=False,
266                    msg='Checking for MagickCore.h location',
267                    okmsg='MagickCore',
268                    errmsg='not MagickCore',
269                    includes=conf.env['INCLUDES_MAGICK'],
270                    define_name='DCPOMATIC_MAGICKCORE_MAGICKCORE')
271
272     # See if we have advanced compare() methods in Magick
273     conf.check_cxx(fragment="""
274                             #include <Magick++.h>\n
275                             int main() { Magick::Image a; Magick::Image b; a.compare(b, Magick::RootMeanSquaredErrorMetric); }
276                             """,
277                    mandatory=False,
278                    msg='Checking for advanced compare() method in {Image/Graphics}Magick',
279                    uselib='MAGICK',
280                    define_name='DCPOMATIC_ADVANCED_MAGICK_COMPARE'
281                    )
282
283     # libzip
284     conf.check_cfg(package='libzip', args='--cflags --libs', uselib_store='ZIP', mandatory=True)
285     conf.check_cxx(fragment="""
286                             #include <zip.h>
287                             int main() { zip_source_t* foo; }
288                             """,
289                    mandatory=False,
290                    msg="Checking for zip_source_t",
291                    uselib="ZIP",
292                    define_name='DCPOMATIC_HAVE_ZIP_SOURCE_T'
293                    )
294
295     # fontconfig
296     conf.check_cfg(package='fontconfig', args='--cflags --libs', uselib_store='FONTCONFIG', mandatory=True)
297
298     # pangomm
299     conf.check_cfg(package='pangomm-1.4', args='--cflags --libs', uselib_store='PANGOMM', mandatory=True)
300
301     # cairomm
302     conf.check_cfg(package='cairomm-1.0', args='--cflags --libs', uselib_store='CAIROMM', mandatory=True)
303
304     test_cxxflags = ''
305     if have_c11:
306         test_cxxflags = '-std=c++11'
307
308     # See if we have Cairo::ImageSurface::format_stride_for_width; Centos 5 does not
309     conf.check_cxx(fragment="""
310                             #include <cairomm/cairomm.h>
311                             int main(void) {
312                                 Cairo::ImageSurface::format_stride_for_width (Cairo::FORMAT_ARGB32, 1024);\n
313                                 return 0; }\n
314                             """,
315                        mandatory=False,
316                        cxxflags=test_cxxflags,
317                        msg='Checking for format_stride_for_width',
318                        okmsg='yes',
319                        includes=conf.env['INCLUDES_CAIROMM'],
320                        uselib='CAIROMM',
321                        define_name='DCPOMATIC_HAVE_FORMAT_STRIDE_FOR_WIDTH')
322
323     # See if we have Pango::Layout::show_in_cairo_context; Centos 5 does not
324     conf.check_cxx(fragment="""
325                             #include <pangomm.h>
326                             int main(void) {
327                                 Cairo::RefPtr<Cairo::Context> context;
328                                 Glib::RefPtr<Pango::Layout> layout;
329                                 layout->show_in_cairo_context (context);
330                                 return 0; }\n
331                             """,
332                        mandatory=False,
333                        msg='Checking for show_in_cairo_context',
334                        cxxflags=test_cxxflags,
335                        okmsg='yes',
336                        includes=conf.env['INCLUDES_PANGOMM'],
337                        uselib='PANGOMM',
338                        define_name='DCPOMATIC_HAVE_SHOW_IN_CAIRO_CONTEXT')
339
340
341     # libcxml
342     if conf.options.static_cxml:
343         conf.check_cfg(package='libcxml', atleast_version='0.15.5', args='--cflags', uselib_store='CXML', mandatory=True)
344         conf.env.STLIB_CXML = ['cxml']
345     else:
346         conf.check_cfg(package='libcxml', atleast_version='0.15.5', args='--cflags --libs', uselib_store='CXML', mandatory=True)
347
348     # libssh
349     if conf.options.static_ssh:
350         conf.env.STLIB_SSH = ['ssh']
351         if conf.options.workaround_gssapi:
352             conf.env.LIB_SSH = ['gssapi_krb5']
353     else:
354         conf.check_cc(fragment="""
355                                #include <libssh/libssh.h>\n
356                                int main () {\n
357                                ssh_session s = ssh_new ();\n
358                                return 0;\n
359                                }
360                                """,
361                       msg='Checking for library libssh',
362                       mandatory=True,
363                       lib='ssh',
364                       uselib_store='SSH')
365
366     # libdcp
367     if conf.options.static_dcp:
368         conf.check_cfg(package='libdcp-1.0', atleast_version='1.5.1', args='--cflags', uselib_store='DCP', mandatory=True)
369         conf.env.DEFINES_DCP = [f.replace('\\', '') for f in conf.env.DEFINES_DCP]
370         conf.env.STLIB_DCP = ['dcp-1.0', 'asdcp-cth', 'kumu-cth', 'openjp2']
371         conf.env.LIB_DCP = ['glibmm-2.4', 'ssl', 'crypto', 'bz2', 'xslt']
372     else:
373         conf.check_cfg(package='libdcp-1.0', atleast_version='1.5.1', args='--cflags --libs', uselib_store='DCP', mandatory=True)
374         conf.env.DEFINES_DCP = [f.replace('\\', '') for f in conf.env.DEFINES_DCP]
375
376     # libsub
377     if conf.options.static_sub:
378         conf.check_cfg(package='libsub-1.0', atleast_version='1.3.0', args='--cflags', uselib_store='SUB', mandatory=True)
379         conf.env.DEFINES_SUB = [f.replace('\\', '') for f in conf.env.DEFINES_SUB]
380         conf.env.STLIB_SUB = ['sub-1.0']
381     else:
382         conf.check_cfg(package='libsub-1.0', atleast_version='1.3.0', args='--cflags --libs', uselib_store='SUB', mandatory=True)
383         conf.env.DEFINES_SUB = [f.replace('\\', '') for f in conf.env.DEFINES_SUB]
384
385     # libxml++
386     if conf.options.static_xmlpp:
387         conf.env.STLIB_XMLPP = ['xml++-2.6']
388         conf.env.LIB_XMLPP = ['xml2']
389     else:
390         conf.check_cfg(package='libxml++-2.6', args='--cflags --libs', uselib_store='XMLPP', mandatory=True)
391
392     # libxmlsec
393     if conf.options.static_xmlsec:
394         if conf.check_cxx(lib='xmlsec1-openssl', mandatory=False):
395             conf.env.STLIB_XMLSEC = ['xmlsec1-openssl', 'xmlsec1']
396         else:
397             conf.env.STLIB_XMLSEC = ['xmlsec1']
398     else:
399         conf.env.LIB_XMLSEC = ['xmlsec1-openssl', 'xmlsec1']
400
401     # nettle
402     conf.check_cfg(package="nettle", args='--cflags --libs', uselib_store='NETTLE', mandatory=True)
403
404     # FFmpeg
405     if conf.options.static_ffmpeg:
406         names = ['avformat', 'avfilter', 'avcodec', 'avutil', 'swscale', 'postproc', 'swresample']
407         for name in names:
408             static = subprocess.Popen(shlex.split('pkg-config --static --libs lib%s' % name), stdout=subprocess.PIPE).communicate()[0].decode('utf-8')
409             libs = []
410             stlibs = []
411             include = []
412             libpath = []
413             for s in static.split():
414                 if s.startswith('-L'):
415                     libpath.append(s[2:])
416                 elif s.startswith('-I'):
417                     include.append(s[2:])
418                 elif s.startswith('-l'):
419                     if s[2:] not in names:
420                         libs.append(s[2:])
421                     else:
422                         stlibs.append(s[2:])
423
424             conf.env['LIB_%s' % name.upper()] = libs
425             conf.env['STLIB_%s' % name.upper()] = stlibs
426             conf.env['INCLUDES_%s' % name.upper()] = include
427             conf.env['LIBPATH_%s' % name.upper()] = libpath
428     else:
429         conf.check_cfg(package='libavformat', args='--cflags --libs', uselib_store='AVFORMAT', mandatory=True)
430         conf.check_cfg(package='libavfilter', args='--cflags --libs', uselib_store='AVFILTER', mandatory=True)
431         conf.check_cfg(package='libavcodec', args='--cflags --libs', uselib_store='AVCODEC', mandatory=True)
432         conf.check_cfg(package='libavutil', args='--cflags --libs', uselib_store='AVUTIL', mandatory=True)
433         conf.check_cfg(package='libswscale', args='--cflags --libs', uselib_store='SWSCALE', mandatory=True)
434         conf.check_cfg(package='libpostproc', args='--cflags --libs', uselib_store='POSTPROC', mandatory=True)
435         conf.check_cfg(package='libswresample', args='--cflags --libs', uselib_store='SWRESAMPLE', mandatory=True)
436
437     # Check to see if we have our version of FFmpeg that allows us to get at EBUR128 results
438     conf.check_cxx(fragment="""
439                             extern "C" {\n
440                             #include <libavfilter/f_ebur128.h>\n
441                             }\n
442                             int main () { av_ebur128_get_true_peaks (0); }\n
443                             """,
444                    msg='Checking for EBUR128-patched FFmpeg',
445                    libpath=conf.env['LIBPATH_AVFORMAT'],
446                    lib='avfilter avutil swresample',
447                    includes=conf.env['INCLUDES_AVFORMAT'],
448                    define_name='DCPOMATIC_HAVE_EBUR128_PATCHED_FFMPEG',
449                    mandatory=False)
450
451     # Check to see if we have our AVSubtitleRect has a pict member
452     # Older versions (e.g. that shipped with Ubuntu 16.04) do
453     conf.check_cxx(fragment="""
454                             extern "C" {\n
455                             #include <libavcodec/avcodec.h>\n
456                             }\n
457                             int main () { AVSubtitleRect r; r.pict; }\n
458                             """,
459                    msg='Checking for AVSubtitleRect::pict',
460                    cxxflags='-Wno-unused-result -Wno-unused-value -Wdeprecated-declarations -Werror',
461                    libpath=conf.env['LIBPATH_AVCODEC'],
462                    lib='avcodec',
463                    includes=conf.env['INCLUDES_AVCODEC'],
464                    define_name='DCPOMATIC_HAVE_AVSUBTITLERECT_PICT',
465                    mandatory=False)
466
467     # Check to see if we have our AVComponentDescriptor has a depth_minus1 member
468     # Older versions (e.g. that shipped with Ubuntu 16.04) do
469     conf.check_cxx(fragment="""
470                             extern "C" {\n
471                             #include <libavutil/pixdesc.h>\n
472                             }\n
473                             int main () { AVComponentDescriptor d; d.depth_minus1; }\n
474                             """,
475                    msg='Checking for AVComponentDescriptor::depth_minus1',
476                    cxxflags='-Wno-unused-result -Wno-unused-value -Wdeprecated-declarations -Werror',
477                    libpath=conf.env['LIBPATH_AVUTIL'],
478                    lib='avutil',
479                    includes=conf.env['INCLUDES_AVUTIL'],
480                    define_name='DCPOMATIC_HAVE_AVCOMPONENTDESCRIPTOR_DEPTH_MINUS1',
481                    mandatory=False)
482
483     # Hack: the previous two check_cxx calls end up copying their (necessary) cxxflags
484     # to these variables.  We don't want to use these for the actual build, so clearn them out.
485     conf.env['CXXFLAGS_AVCODEC'] = []
486     conf.env['CXXFLAGS_AVUTIL'] = []
487
488     # Boost
489     if conf.options.static_boost:
490         conf.env.STLIB_BOOST_THREAD = ['boost_thread']
491         conf.env.STLIB_BOOST_FILESYSTEM = ['boost_filesystem%s' % boost_lib_suffix]
492         conf.env.STLIB_BOOST_DATETIME = ['boost_date_time%s' % boost_lib_suffix, 'boost_system%s' % boost_lib_suffix]
493         conf.env.STLIB_BOOST_SIGNALS2 = ['boost_signals2']
494         conf.env.STLIB_BOOST_SYSTEM = ['boost_system']
495         conf.env.STLIB_BOOST_REGEX = ['boost_regex']
496     else:
497         conf.check_cxx(fragment="""
498                             #include <boost/version.hpp>\n
499                             #if BOOST_VERSION < 104500\n
500                             #error boost too old\n
501                             #endif\n
502                             int main(void) { return 0; }\n
503                             """,
504                        mandatory=True,
505                        msg='Checking for boost library >= 1.45',
506                        okmsg='yes',
507                        errmsg='too old\nPlease install boost version 1.45 or higher.')
508
509         conf.check_cxx(fragment="""
510                             #include <boost/thread.hpp>\n
511                             int main() { boost::thread t (); }\n
512                             """,
513                        msg='Checking for boost threading library',
514                        libpath='/usr/local/lib',
515                        lib=[boost_thread, 'boost_system%s' % boost_lib_suffix],
516                        uselib_store='BOOST_THREAD')
517
518         conf.check_cxx(fragment="""
519                             #include <boost/filesystem.hpp>\n
520                             int main() { boost::filesystem::copy_file ("a", "b"); }\n
521                             """,
522                        msg='Checking for boost filesystem library',
523                        libpath='/usr/local/lib',
524                        lib=['boost_filesystem%s' % boost_lib_suffix, 'boost_system%s' % boost_lib_suffix],
525                        uselib_store='BOOST_FILESYSTEM')
526
527         conf.check_cxx(fragment="""
528                             #include <boost/date_time.hpp>\n
529                             int main() { boost::gregorian::day_clock::local_day(); }\n
530                             """,
531                        msg='Checking for boost datetime library',
532                        libpath='/usr/local/lib',
533                        lib=['boost_date_time%s' % boost_lib_suffix, 'boost_system%s' % boost_lib_suffix],
534                        uselib_store='BOOST_DATETIME')
535
536         conf.check_cxx(fragment="""
537                             #include <boost/signals2.hpp>\n
538                             int main() { boost::signals2::signal<void (int)> x; }\n
539                             """,
540                        msg='Checking for boost signals2 library',
541                        uselib_store='BOOST_SIGNALS2')
542
543         conf.check_cxx(fragment="""
544                             #include <boost/regex.hpp>\n
545                             int main() { boost::regex re ("foo"); }\n
546                             """,
547                        msg='Checking for boost regex library',
548                        lib=['boost_regex%s' % boost_lib_suffix],
549                        uselib_store='BOOST_REGEX')
550
551     # libxml++ requires glibmm and versions of glibmm 2.45.31 and later
552     # must be built with -std=c++11 as they use c++11
553     # features and c++11 is not (yet) the default in gcc.
554     glibmm_version = conf.cmd_and_log(['pkg-config', '--modversion', 'glibmm-2.4'], output=Context.STDOUT, quiet=Context.BOTH)
555     s = glibmm_version.split('.')
556     v = (int(s[0]) << 16) | (int(s[1]) << 8) | int(s[2])
557     if v >= 0x022D1F:
558         conf.env.append_value('CXXFLAGS', '-std=c++11')
559
560     # Other stuff
561
562     conf.find_program('msgfmt', var='MSGFMT')
563
564     datadir = conf.env.DATADIR
565     if not datadir:
566         datadir = os.path.join(conf.env.PREFIX, 'share')
567
568     conf.define('LOCALEDIR', os.path.join(datadir, 'locale'))
569     conf.define('DATADIR', datadir)
570
571     conf.recurse('src')
572     if not conf.env.DISABLE_TESTS:
573         conf.recurse('test')
574
575     Logs.pprint('YELLOW', '')
576     if conf.env.TARGET_WINDOWS:
577         Logs.pprint('YELLOW', '\t' + 'Target'.ljust(25) + ': Windows')
578     elif conf.env.TARGET_LINUX:
579         Logs.pprint('YELLOW', '\t' + 'Target'.ljust(25) + ': Linux')
580     elif conf.env.TARGET_OSX:
581         Logs.pprint('YELLOW', '\t' + 'Target'.ljust(25) + ': OS X')
582
583     def report(name, variable):
584         linkage = ''
585         if variable:
586             linkage = 'static'
587         else:
588             linkage = 'dynamic'
589         Logs.pprint('YELLOW', '\t%s: %s' % (name.ljust(25), linkage))
590
591     report('DCP-o-matic libraries', conf.options.static_dcpomatic)
592     report('Boost', conf.options.static_boost)
593     report('wxWidgets', conf.options.static_wxwidgets)
594     report('FFmpeg', conf.options.static_ffmpeg)
595     report('libxml++', conf.options.static_xmlpp)
596     report('xmlsec', conf.options.static_xmlsec)
597     report('libssh', conf.options.static_ssh)
598     report('libcxml', conf.options.static_cxml)
599     report('libdcp', conf.options.static_dcp)
600     report('libcurl', conf.options.static_curl)
601
602     Logs.pprint('YELLOW', '')
603
604 def download_supporters():
605     last_date = subprocess.Popen(shlex.split('git log -1 --format=%%ai %s' % last_version), stdout=subprocess.PIPE).communicate()[0]
606     os.system('curl https://dcpomatic.com/supporters.cc?%s > src/wx/supporters.cc' % urllib.urlencode({"until": last_date.strip()}))
607
608 def build(bld):
609     create_version_cc(VERSION, bld.env.CXXFLAGS)
610     download_supporters()
611
612     bld.recurse('src')
613     bld.recurse('graphics')
614
615     if not bld.env.DISABLE_TESTS:
616         bld.recurse('test')
617     if bld.env.TARGET_WINDOWS:
618         bld.recurse('platform/windows')
619     if bld.env.TARGET_LINUX:
620         bld.recurse('platform/linux')
621     if bld.env.TARGET_OSX:
622         bld.recurse('platform/osx')
623
624     if not bld.env.TARGET_WINDOWS:
625         bld.install_files('${PREFIX}/share/dcpomatic2', 'fonts/LiberationSans-Regular.ttf')
626         bld.install_files('${PREFIX}/share/dcpomatic2', 'fonts/LiberationSans-Italic.ttf')
627         bld.install_files('${PREFIX}/share/dcpomatic2', 'fonts/LiberationSans-Bold.ttf')
628
629     bld.add_post_fun(post)
630
631 def git_revision():
632     if not os.path.exists('.git'):
633         return None
634
635     cmd = "LANG= git log --abbrev HEAD^..HEAD ."
636     output = subprocess.Popen(cmd, shell=True, stderr=subprocess.STDOUT, stdout=subprocess.PIPE).communicate()[0].splitlines()
637     if len(output) == 0:
638         return None
639     o = output[0].decode('utf-8')
640     return o.replace("commit ", "")[0:10]
641
642 def dist(ctx):
643     r = git_revision()
644     if r is not None:
645         f = open('.git_revision', 'w')
646         print >>f,r
647     f.close()
648
649     ctx.excl = """
650                TODO core *~ src/wx/*~ src/lib/*~ builds/*~ doc/manual/*~ src/tools/*~ *.pyc .waf* build .git
651                deps alignment hacks sync *.tar.bz2 *.exe .lock* *build-windows doc/manual/pdf doc/manual/html
652                GRSYMS GRTAGS GSYMS GTAGS compile_commands.json
653                """
654
655 def create_version_cc(version, cxx_flags):
656     commit = git_revision()
657     if commit is None and os.path.exists('.git_revision'):
658         f = open('.git_revision', 'r')
659         commit = f.readline().strip()
660
661     if commit is None:
662         commit = 'release'
663
664     try:
665         text =  '#include "version.h"\n'
666         text += 'char const * dcpomatic_git_commit = \"%s\";\n' % commit
667         text += 'char const * dcpomatic_version = \"%s\";\n' % version
668
669         t = ''
670         for f in cxx_flags:
671             f = f.replace('"', '\\"')
672             t += f + ' '
673         text += 'char const * dcpomatic_cxx_flags = \"%s\";\n' % t[:-1]
674
675         print('Writing version information to src/lib/version.cc')
676         o = open('src/lib/version.cc', 'w')
677         o.write(text)
678         o.close()
679     except IOError:
680         print('Could not open src/lib/version.cc for writing\n')
681         sys.exit(-1)
682
683 def post(ctx):
684     if ctx.cmd == 'install':
685         ctx.exec_command('/sbin/ldconfig')
686
687 def pot(bld):
688     bld.recurse('src')
689
690 def pot_merge(bld):
691     bld.recurse('src')
692
693 def tags(bld):
694     os.system('etags src/lib/*.cc src/lib/*.h src/wx/*.cc src/wx/*.h src/tools/*.cc')
695
696 def cppcheck(bld):
697     os.system('cppcheck --enable=all --quiet .')