1#!/usr/bin/env python
2
3import ninja_syntax
4import os, sys, glob, re
5import itertools
6import argparse
7
8# utilities
9def flags(*args):
10    return ' '.join(itertools.chain(*args))
11
12def includes(l):
13    return ['-I"{}"'.format(x) for x in l]
14
15def library_includes(l):
16    return ['-L"{}"'.format(x) for x in l]
17
18def libraries(l):
19    return ['-l{}'.format(x) for x in l]
20
21def dependencies(l):
22    return ['-isystem"{}"'.format(x) for x in l]
23
24def object_file(f):
25    (root, ext) = os.path.splitext(f)
26    return os.path.join(objdir, root + '.o')
27
28def replace_extension(f, e):
29    (root, ext) = os.path.splitext(f)
30    return root + e
31
32# Default install dir
33install_dir = os.path.join('/usr', 'include') if 'linux' in sys.platform else 'include'
34
35# Compiler: Read from environment or defaulted
36cxx = os.environ.get('CXX', "g++")
37
38# command line stuff
39parser = argparse.ArgumentParser()
40parser.add_argument('--debug', action='store_true', help='compile with debug flags')
41parser.add_argument('--cxx', metavar='<compiler>', help='compiler name to use (default: env.CXX=%s)' % cxx, default=cxx)
42parser.add_argument('--cxx-flags', help='additional flags passed to the compiler', default='')
43parser.add_argument('--ci', action='store_true', help=argparse.SUPPRESS)
44parser.add_argument('--testing', action='store_true', help=argparse.SUPPRESS)
45parser.add_argument('--lua-version', help='Lua version, e.g. lua53', default='lua53')
46parser.add_argument('--lua-lib', help='lua library name (without the lib on *nix).', default='lua')
47parser.add_argument('--lua-dir', metavar='<dir>', help='directory lua is in with include and lib subdirectories')
48parser.add_argument('--install-dir', metavar='<dir>', help='directory to install the headers to', default=install_dir);
49parser.epilog = """In order to install sol, administrative privileges might be required.
50Note that installation is done through the 'ninja install' command. To uninstall, the
51command used is 'ninja uninstall'. The default installation directory for this
52system is {}""".format(install_dir)
53
54args = parser.parse_args()
55
56# general variables
57include = [ '.', './include' ]
58depends = [os.path.join('Catch', 'include')]
59cxxflags = [ '-Wall', '-Wextra', '-Wpedantic', '-pedantic', '-pedantic-errors', '-std=c++14', '-ftemplate-depth=1024' ]
60cxxflags.extend([p for p in re.split("( |\\\".*?\\\"|'.*?')", args.cxx_flags) if p.strip()])
61example_cxxflags = [ '-Wall', '-Wextra', '-Wpedantic', '-pedantic', '-pedantic-errors', '-std=c++14', '-ftemplate-depth=1024' ]
62example_cxxflags.extend([p for p in re.split("( |\\\".*?\\\"|'.*?')", args.cxx_flags) if p.strip()])
63ldflags = []
64script_dir = os.path.dirname(os.path.realpath(sys.argv[0]))
65sol_dir = os.path.join(script_dir, 'sol')
66sol_file = os.path.join(script_dir, 'sol.hpp')
67
68copy_command = 'cp -rf {} $in && cp -f {} $in'.format(sol_dir, sol_file)
69remove_command = 'rm -rf {} && rm -f {}'.format(os.path.join(args.install_dir, 'sol'), os.path.join(args.install_dir, 'sol.hpp'))
70if sys.platform == 'win32':
71    copy_command = 'robocopy /COPYALL /E {} $in && robocopy /COPYALL {} $in'.format(sol_dir, sol_file)
72    remove_command = 'rmdir /S /Q {} && erase /F /S /Q /A {}'.format(os.path.join(args.install_dir, 'sol'),
73                                                                     os.path.join(args.install_dir, 'sol.hpp'))
74
75if not args.lua_lib:
76     args.lua_lib = 'lua'
77
78if args.debug:
79    cxxflags.extend(['-g', '-O0'])
80else:
81    cxxflags.extend(['-DNDEBUG', '-O3'])
82example_cxxflags.extend(['-g', '-O0'])
83
84if args.lua_dir:
85    include.extend([os.path.join(args.lua_dir, 'include')])
86    ldflags.extend(library_includes([os.path.join(args.lua_dir, 'lib')]))
87
88if 'linux' in sys.platform:
89    lua_version = os.environ.get('LUA_VERSION', args.lua_version)
90    if re.match(r'lua5[1-3]', lua_version):
91        # Using normal lua
92        lua_lib = lua_version[:-1] + '.' + lua_version[-1]
93        lua_incl = lua_lib
94    elif re.match(r'luajit5[1-3]', lua_version):
95        # luajit
96        lua_incl = 'luajit-2.0' # I don't get this..
97        lua_lib = lua_version[:-2] + '-' + lua_version[-2] + '.' + lua_version[-1]
98        include.extend(['/usr/include/luajit-2.0/', '/usr/local/include/luajit-2.0/'])
99    else:
100        raise Exception('Unknown lua_version={}' % lua_version)
101
102
103    include.extend(['/usr/include/' + lua_incl, '/usr/local/include/' + lua_incl])
104    ldflags.extend(library_includes(['/usr/local/lib']))
105    ldflags.extend(libraries([lua_lib]))
106elif 'darwin' in sys.platform:
107    # OSX
108    lua_version = os.environ.get('LUA_VERSION', args.lua_version)
109    if re.match(r'lua5[1-3]', lua_version):
110        # Using normal lua
111        lua_incl = lua_version[:-1] + '.' + lua_version[-1]
112        lua_lib = lua_version[:-2] + '.' +  lua_version[-2] + '.' + lua_version[-1]
113    elif re.match(r'luajit', lua_version):
114        # luajit
115        lua_incl = 'luajit-2.0'
116        lua_lib = 'luajit'
117        ldflags.extend(['-pagezero_size 10000', '-image_base 100000000'])
118    elif re.match(r'luajit5[1-3]', lua_version):
119        # luajit
120        lua_incl = 'luajit-2.0'
121        lua_lib = lua_version[:-2] + '-' + lua_version[-2] + '.' + lua_version[-1]
122        ldflags.extend(['-pagezero_size 10000', '-image_base 100000000'])
123    else:
124        raise Exception('Unknown lua_version={}' % lua_version)
125
126    depends.extend(['/usr/include/' + lua_incl, '/usr/local/include/' + lua_incl])
127    ldflags.extend(library_includes(['/usr/local/lib']))
128    ldflags.extend(libraries([lua_lib]))
129else:
130    ldflags.extend(libraries([args.lua_lib]))
131
132if args.testing:
133    cxxflags.append('-Wmissing-declarations')
134
135if 'linux' in sys.platform:
136    ldflags.extend(libraries(['dl']))
137
138builddir = 'bin'
139objdir = 'obj'
140if 'win32' in sys.platform:
141     tests = os.path.join(builddir, 'tests.exe')
142else:
143     tests = os.path.join(builddir, 'tests')
144
145tests_inputs = []
146tests_object_files = []
147for f in glob.glob('test*.cpp'):
148    obj = object_file(f)
149    tests_inputs.append(f)
150    tests_object_files.append(obj)
151
152examples = []
153examples_input = []
154for f in glob.glob('examples/*.cpp'):
155    if 'win32' in sys.platform:
156        example = os.path.join(builddir, replace_extension(f, '.exe'))
157    else:
158        example = os.path.join(builddir, replace_extension(f, ''))
159    examples_input.append(f)
160    examples.append(example)
161
162
163# ninja file
164ninja = ninja_syntax.Writer(open('build.ninja', 'w'))
165
166# variables
167ninja.variable('ninja_required_version', '1.3')
168ninja.variable('builddir', 'bin')
169ninja.variable('cxx', args.cxx)
170ninja.variable('cxxflags', flags(cxxflags + includes(include) + dependencies(depends)))
171ninja.variable('example_cxxflags', flags(example_cxxflags + includes(include) + dependencies(depends)))
172ninja.variable('ldflags', flags(ldflags))
173ninja.newline()
174
175# rules
176ninja.rule('bootstrap', command = ' '.join(['python'] + sys.argv), generator = True)
177ninja.rule('compile', command = '$cxx -MMD -MF $out.d -c $cxxflags -Werror $in -o $out',
178                      deps = 'gcc', depfile = '$out.d',
179                      description = 'compiling $in to $out')
180ninja.rule('link', command = '$cxx $cxxflags $in -o $out $ldflags', description = 'creating $out')
181ninja.rule('tests_runner', command = tests)
182ninja.rule('examples_runner', command = 'cmd /c ' + (' && '.join(examples)) if 'win32' in sys.platform else ' && '.join(examples) )
183ninja.rule('example', command = '$cxx $example_cxxflags -MMD -MF $out.d $in -o $out $ldflags',
184                      deps = 'gcc', depfile = '$out.d',
185                      description = 'compiling example $in to $out')
186ninja.rule('installer', command = copy_command)
187ninja.rule('uninstaller', command = remove_command)
188ninja.newline()
189
190# builds
191ninja.build('build.ninja', 'bootstrap', implicit = sys.argv[0])
192
193for obj, f in zip(tests_object_files, tests_inputs):
194    ninja.build(obj, 'compile', inputs = f)
195
196for example, f in zip(examples, examples_input):
197    ninja.build(example, 'example', inputs = f)
198
199ninja.build(tests, 'link', inputs = tests_object_files)
200ninja.build('tests', 'phony', inputs = tests)
201ninja.build('examples', 'phony', inputs = examples)
202ninja.build('install', 'installer', inputs = args.install_dir)
203ninja.build('uninstall', 'uninstaller')
204ninja.build('run', 'tests_runner', implicit = 'tests')
205ninja.build('run_examples', 'examples_runner', implicit = 'examples')
206ninja.default('run run_examples')
207