build (6070B) - raw
1 #!/usr/bin/env python3
2
3 """
4 My own homegrown build script.
5 Because waf, SCons, autotools, and CMake all made me angry.
6 """
7
8 import sys
9 import os
10 import subprocess
11 import errno
12
13 def invoke(command):
14 print(' '.join(command))
15 subprocess.check_call(command)
16
17 def files_under(*paths):
18 result = []
19 for path in paths:
20 for dirpath, dirnames, filenames in os.walk(path):
21 result.extend(os.path.join(dirpath, filename) for filename in filenames)
22 return result
23
24 def ensure_dir(path):
25 if not os.path.isdir(path):
26 os.makedirs(path)
27
28 def replace_ext(path, old, new):
29 if not path.endswith(old):
30 raise ValueError('Path %r does not end with %r' % (path, old))
31 return path[:-len(old)] + new
32
33 class Program(object):
34
35 default_valac = 'valac'
36 default_vala_flags = '-g'
37 default_cc = 'gcc'
38 default_c_flags = '-g -O -Wall'
39 default_cpp_flags = '-g -O -Wall'
40 default_ld_flags = ''
41
42 def __init__(self, target):
43 self.target = target
44 self.work_dir = os.path.join('target', '%s-work' % self.target)
45 ensure_dir(self.work_dir)
46 self.vala_sources = []
47 self.valac = os.environ.get('VALAC', self.default_valac)
48 self.vala_flags = os.environ.get('VALAFLAGS', self.default_vala_flags).split()
49 self.vala_pkgs = []
50 self.vala_vapidirs = []
51 self.c_sources = []
52 self.cc = os.environ.get('CC', self.default_cc)
53 self.c_flags = os.environ.get('CFLAGS', self.default_c_flags).split()
54 self.cpp_sources = []
55 self.cpp_flags = os.environ.get('CXXFLAGS', self.default_cpp_flags).split()
56 self.objects = []
57 self.ld_flags = os.environ.get('LDFLAGS', self.default_ld_flags).split()
58
59 def from_sources(self, vala_sources=[], c_sources=[], cpp_sources=[]):
60 self.vala_sources.extend(vala_sources)
61 self.c_sources.extend(c_sources)
62 self.cpp_sources.extend(cpp_sources)
63 return self
64
65 def from_sources_under(self, *paths):
66 self.vala_sources.extend(f for f in files_under(*paths) if f.endswith('.vala'))
67 self.c_sources.extend(f for f in files_under(*paths) if f.endswith('.c'))
68 self.cpp_sources.extend(f for f in files_under(*paths) if f.endswith('.cpp'))
69 return self
70
71 def with_vapi_dirs(self, *vapidirs):
72 self.vala_flags.extend('--vapidir=%s' % vapidir for vapidir in vapidirs)
73 return self
74
75 def with_c_includes(self, *includes):
76 self.c_flags.extend('-I%s' % inc for inc in includes)
77 return self
78
79 def with_cpp_includes(self, *includes):
80 self.cpp_flags.extend('-I%s' % inc for inc in includes)
81 return self
82
83 def using_pkgs(self, *pkgs):
84 for pkg in pkgs:
85 cflags = subprocess.check_output(['pkg-config', '--cflags', pkg]).decode('ascii').split()
86 self.c_flags.extend(cflags)
87 self.cpp_flags.extend(cflags)
88 self.ld_flags.extend(subprocess.check_output(['pkg-config', '--libs', pkg]).decode('ascii').split())
89 self.vala_pkgs.extend(pkgs)
90 return self
91
92 def with_defines(self, *defs):
93 self.vala_flags.extend('--define=%s' % d for d in defs)
94 return self
95
96 def build(self):
97 self._compile_vala()
98 self._compile_c()
99 self._compile_cpp()
100 self._link()
101
102 def _compile_vala(self):
103 if self.vala_sources:
104 invoke([self.valac, '-C', '-d', self.work_dir] +
105 self.vala_flags +
106 ['--pkg=%s' % pkg for pkg in self.vala_pkgs] +
107 self.vala_sources)
108 self.c_sources.extend(os.path.join(self.work_dir, replace_ext(source, '.vala', '.c'))
109 for source in self.vala_sources)
110
111 def _compile_c(self):
112 for source in self.c_sources:
113 out = os.path.join(self.work_dir, replace_ext(os.path.basename(source), '.c', '.o'))
114 invoke([self.cc] + self.c_flags + ['-c', source, '-o', out])
115 self.objects.append(out)
116
117 def _compile_cpp(self):
118 for source in self.cpp_sources:
119 out = os.path.join(self.work_dir, replace_ext(os.path.basename(source), '.cpp', '.o'))
120 invoke(['g++'] + self.cpp_flags + ['-c', source, '-o', out])
121 self.objects.append(out)
122
123 def _link(self):
124 invoke([self.cc] + self.ld_flags + self.objects +
125 ['-o', os.path.join('target', self.target)])
126
127 def compile():
128 pkgs = ['gtk+-2.0', 'gee-1.0', 'libxml-2.0', 'libsoup-2.4', 'exiv2', 'osmgpsmap']
129 xmpedit = Program('xmpedit') \
130 .from_sources_under('src', 'lib') \
131 .with_vapi_dirs('vapi') \
132 .with_c_includes('src', 'lib/genx') \
133 .using_pkgs(*pkgs) \
134 .with_defines('DEBUG')
135 xmpedit.c_flags.append('-Wno-pointer-sign')
136 xmpedit.vala_pkgs.append('genx')
137 xmpedit.build()
138
139 def test():
140 # unit tests
141 pkgs = ['gtk+-2.0', 'gee-1.0', 'libxml-2.0', 'libsoup-2.4', 'exiv2', 'osmgpsmap']
142 xmpedit_test = Program('xmpedit_test') \
143 .from_sources_under('src', 'lib') \
144 .with_vapi_dirs('vapi') \
145 .with_c_includes('src', 'lib/genx') \
146 .using_pkgs(*pkgs) \
147 .with_defines('DEBUG', 'TEST')
148 xmpedit_test.c_flags.append('-Wno-pointer-sign')
149 xmpedit_test.vala_pkgs.append('genx')
150 xmpedit_test.build()
151 invoke(['gtester', '--verbose', 'target/xmpedit_test'])
152 # gui tests
153 printxmp = Program('printxmp') \
154 .from_sources(cpp_sources=[os.path.join('test', 'printxmp.cpp')]) \
155 .using_pkgs('exiv2') \
156 .build()
157 invoke(['test/guitest.py'])
158
159 if __name__ == '__main__':
160 from optparse import OptionParser
161 parser = OptionParser(usage='usage: %prog [options]')
162 parser.add_option('-t', '--test', action='store_true', default=False,
163 help='build and run tests')
164 options, args = parser.parse_args()
165 compile()
166 if options.test:
167 test()