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()