|
| 1 | +import collections |
| 2 | +import functools |
| 3 | +import itertools |
| 4 | +import subprocess |
| 5 | + |
| 6 | +import drake |
| 7 | +import drake.cxx |
| 8 | +import drake.git |
| 9 | + |
| 10 | +@functools.lru_cache(1) |
| 11 | +def _default_make_binary(): |
| 12 | + from drake.which import which |
| 13 | + to_try = [ |
| 14 | + 'make', |
| 15 | + 'gmake', |
| 16 | + 'mingw32-make', |
| 17 | + 'mingw64-make', |
| 18 | + ] |
| 19 | + for binary in to_try: |
| 20 | + path = which(binary) |
| 21 | + if path is not None: |
| 22 | + return path |
| 23 | + |
| 24 | +class GNUBuilder(drake.Builder): |
| 25 | + |
| 26 | + def __init__( |
| 27 | + self, |
| 28 | + cxx_toolkit, |
| 29 | + targets = [], |
| 30 | + configure: """Configure script path (or None if no configure |
| 31 | + step is needed)""" = None, |
| 32 | + working_directory: "Deduced from configure" = None, |
| 33 | + configure_args: "Arguments of the configure script" = [], |
| 34 | + sources = [], |
| 35 | + make_binary: "Make binary" = None, |
| 36 | + makefile: "Makefile filename, used if not None" = None, |
| 37 | + build_args: "Additional arguments for the make command" = ['install'], |
| 38 | + additional_env: "Additional environment variables" = {}, |
| 39 | + configure_interpreter = None, |
| 40 | + patch = None, |
| 41 | + configure_stdout: 'Show configure standard output' = False, |
| 42 | + build_stdout: 'Show build standard output' = False): |
| 43 | + self.__toolkit = cxx_toolkit |
| 44 | + self.__build_stdout = build_stdout |
| 45 | + self.__configure = configure |
| 46 | + self.__configure_args = configure_args |
| 47 | + self.__configure_interpreter = configure_interpreter |
| 48 | + self.__configure_stdout = configure_stdout |
| 49 | + self.__targets = list(targets) |
| 50 | + if make_binary is None: |
| 51 | + self.__make_binary = _default_make_binary() |
| 52 | + else: |
| 53 | + self.__make_binary = make_binary |
| 54 | + self.__makefile = makefile |
| 55 | + self.__build_args = build_args |
| 56 | + self.__env = {} |
| 57 | + self.__env.update(additional_env) |
| 58 | + self.__patch = patch |
| 59 | + if make_binary is not None: |
| 60 | + self.__env.setdefault('MAKE', make_binary.replace('\\', '/')) |
| 61 | + if working_directory is not None: |
| 62 | + self.__working_directory = working_directory |
| 63 | + if not self.__working_directory.exists(): |
| 64 | + self.__working_directory.mkpath() |
| 65 | + else: |
| 66 | + if self.__configure is None: |
| 67 | + raise Exception( |
| 68 | + "Cannot deduce the working directory (no configure script)" |
| 69 | + ) |
| 70 | + self.__working_directory = self.__configure.path().dirname() |
| 71 | + drake.Builder.__init__( |
| 72 | + self, |
| 73 | + (configure is not None and [configure] or []) + sources, |
| 74 | + self.__targets) |
| 75 | + if isinstance(cxx_toolkit.patchelf, drake.BaseNode): |
| 76 | + self.add_src(cxx_toolkit.patchelf) |
| 77 | + |
| 78 | + def execute(self): |
| 79 | + env = dict(self.__env) |
| 80 | + import os |
| 81 | + env.update(os.environ) |
| 82 | + # Patch |
| 83 | + if self.__patch is not None: |
| 84 | + patch_path = str(drake.path_root() / self.__patch.path()) |
| 85 | + patch_cmd = ['patch', '-N', '-p', '1', '-i', patch_path], |
| 86 | + if not self.cmd('Patch %s' % self.work_directory, |
| 87 | + patch_cmd, |
| 88 | + cwd = self.work_directory): |
| 89 | + return False |
| 90 | + # Configure step |
| 91 | + if self.__configure is not None: |
| 92 | + if not self.cmd('Configure %s' % self.work_directory, |
| 93 | + self.command_configure, |
| 94 | + cwd = self.work_directory, |
| 95 | + env = env, |
| 96 | + leave_stdout = self.__configure_stdout): |
| 97 | + return False |
| 98 | + # Build step |
| 99 | + if not self.cmd('Build %s' % self.work_directory, |
| 100 | + self.command_build, |
| 101 | + cwd = self.work_directory, |
| 102 | + env = env, |
| 103 | + leave_stdout = self.__build_stdout): |
| 104 | + return False |
| 105 | + for target in self.__targets: |
| 106 | + path = target.path().without_prefix(self.work_directory) |
| 107 | + if isinstance(target, drake.cxx.DynLib): |
| 108 | + rpath = '.' |
| 109 | + elif isinstance(target, drake.cxx.Executable): |
| 110 | + rpath = '../lib' |
| 111 | + else: |
| 112 | + continue |
| 113 | + with drake.WritePermissions(target): |
| 114 | + cmd = self.__toolkit.rpath_set_command(target.path(), rpath) |
| 115 | + if self.__toolkit.os is not drake.os.windows: |
| 116 | + if not self.cmd('Fix rpath for %s' % target.path(), cmd): |
| 117 | + return False |
| 118 | + if self.__toolkit.os is drake.os.macos: |
| 119 | + cmd = ['install_name_tool', |
| 120 | + '-id', '@rpath/%s' % target.name().basename(), |
| 121 | + str(target.path())] |
| 122 | + if not self.cmd('Fix rpath for %s' % target.path(), cmd): |
| 123 | + return False |
| 124 | + lib_dependecies = self.parse_otool_libraries(target.path()) |
| 125 | + for dep in lib_dependecies: |
| 126 | + if dep.basename() in (t.path().basename() for t in self.__targets): |
| 127 | + cmd = [ |
| 128 | + 'install_name_tool', |
| 129 | + '-change', |
| 130 | + str(dep), |
| 131 | + '@rpath/%s' % dep.basename(), |
| 132 | + str(target.path()), |
| 133 | + ] |
| 134 | + if not self.cmd('Fix dependency name for %s' % target.path(), cmd): |
| 135 | + return False |
| 136 | + return True |
| 137 | + |
| 138 | + def parse_otool_libraries(self, path): |
| 139 | + command = ['otool', '-L', str(path)] |
| 140 | + return [drake.Path(line[1:].split(' ')[0]) |
| 141 | + for line |
| 142 | + in subprocess.check_output(command).decode().split('\n') |
| 143 | + if line.startswith('\t')] |
| 144 | + |
| 145 | + @property |
| 146 | + def command_configure(self): |
| 147 | + if self.__configure is None: |
| 148 | + return None |
| 149 | + config = [str(drake.path_build(absolute = True) / self.__configure.path())] |
| 150 | + if self.__configure_interpreter is not None: |
| 151 | + config.insert(0, self.__configure_interpreter) |
| 152 | + return config + self.__configure_args |
| 153 | + |
| 154 | + @property |
| 155 | + def command_build(self): |
| 156 | + if self.__makefile is not None: |
| 157 | + return [self.__make_binary, '-f', self.__makefile, 'install'] + self.__build_args |
| 158 | + return [self.__make_binary] + self.__build_args |
| 159 | + |
| 160 | + @property |
| 161 | + def work_directory(self): |
| 162 | + return str(self.__working_directory) |
| 163 | + |
| 164 | + |
| 165 | + def hash(self): |
| 166 | + env = {} |
| 167 | + env.update(self.__env) |
| 168 | + env.pop('DRAKE_RAW', '1') |
| 169 | + return ''.join([ |
| 170 | + str(self.command_configure), |
| 171 | + str(self.command_build), |
| 172 | + str(tuple(sorted(env))), |
| 173 | + ]) |
| 174 | + |
| 175 | + def __str__(self): |
| 176 | + return '%s(%s)' % (self.__class__.__name__, self.__working_directory) |
| 177 | + |
| 178 | +class FatLibraryGenerator(drake.Builder): |
| 179 | + |
| 180 | + def __init__(self, |
| 181 | + input_libs, |
| 182 | + output_lib, |
| 183 | + headers = [], |
| 184 | + input_headers = None, |
| 185 | + output_headers = None): |
| 186 | + drake.Builder.__init__(self, |
| 187 | + input_libs, |
| 188 | + itertools.chain([output_lib], (drake.node(output_headers / p) |
| 189 | + for p in headers))) |
| 190 | + self.__input_libs = input_libs |
| 191 | + self.__output_lib = output_lib |
| 192 | + self.__headers = headers |
| 193 | + if input_headers: |
| 194 | + self.__input_headers = drake.path_build(input_headers) |
| 195 | + else: |
| 196 | + self.__input_headers = None |
| 197 | + if output_headers: |
| 198 | + self.__output_headers = drake.path_build(output_headers) |
| 199 | + else: |
| 200 | + self.__output_headers = None |
| 201 | + |
| 202 | + def execute(self): |
| 203 | + res = self.cmd('Lipo %s' % self.input_paths, |
| 204 | + self.lipo_command, |
| 205 | + leave_stdout = False) |
| 206 | + if not res: |
| 207 | + return False |
| 208 | + if self.__headers and self.__input_headers and self.__output_headers: |
| 209 | + res = self.cmd('cp %s' % self.__input_headers, |
| 210 | + self.copy_headers_command, |
| 211 | + leave_stdout = False) |
| 212 | + return res |
| 213 | + |
| 214 | + @property |
| 215 | + def lipo_command(self): |
| 216 | + if len(self.__input_libs) == 1: |
| 217 | + res = ['cp'] |
| 218 | + res.extend(self.input_paths) |
| 219 | + res.append(self.__output_lib.path()) |
| 220 | + else: |
| 221 | + res = ['lipo'] |
| 222 | + res.extend(self.input_paths) |
| 223 | + res.extend(['-create', '-output']) |
| 224 | + res.append(self.__output_lib.path()) |
| 225 | + return res |
| 226 | + |
| 227 | + @property |
| 228 | + def input_paths(self): |
| 229 | + res = [] |
| 230 | + for input in self.__input_libs: |
| 231 | + res.append(input.path()) |
| 232 | + return res |
| 233 | + |
| 234 | + @property |
| 235 | + def copy_headers_command(self): |
| 236 | + return ['cp', '-r', |
| 237 | + self.__input_headers, self.__output_headers] |
| 238 | + |
| 239 | + |
| 240 | +class VersionGenerator(drake.Builder): |
| 241 | + |
| 242 | + def __init__(self, output, git = None, production_build = True): |
| 243 | + git = git or drake.git.Git() |
| 244 | + drake.Builder.__init__(self, [git], [output]) |
| 245 | + self.__git = git |
| 246 | + self.__output = output |
| 247 | + self.__production_build = production_build |
| 248 | + |
| 249 | + def execute(self): |
| 250 | + self.output('Generate %s' % self.__output.path()) |
| 251 | + chunks = collections.OrderedDict() |
| 252 | + if self.__production_build: |
| 253 | + version = self.__git.description() |
| 254 | + else: |
| 255 | + version = '%s-dev' % self.__git.version().split('-')[0] |
| 256 | + chunks['version'] = version |
| 257 | + chunks['major'], chunks['minor'], chunks['subminor'] = \ |
| 258 | + map(int, version.split('-')[0].split('.')) |
| 259 | + with open(str(self.__output.path()), 'w') as f: |
| 260 | + variables = (self._variable(*item) for item in chunks.items()) |
| 261 | + for line in itertools.chain( |
| 262 | + self._prologue(), variables, self._epilogue()): |
| 263 | + print(line, file = f) |
| 264 | + return True |
| 265 | + |
| 266 | + def _prologue(self): |
| 267 | + return iter(()) |
| 268 | + |
| 269 | + def _epilogue(self): |
| 270 | + return iter(()) |
| 271 | + |
| 272 | + def _variable(self, name, value): |
| 273 | + raise NotImplementedError() |
| 274 | + |
| 275 | + def hash(self): |
| 276 | + return self.__production_build |
| 277 | + |
| 278 | + |
| 279 | +class PythonVersionGenerator(VersionGenerator): |
| 280 | + |
| 281 | + def _variable(self, name, value): |
| 282 | + return '%s = %s' % (name, repr(value)) |
| 283 | + |
| 284 | +class CxxVersionGenerator(VersionGenerator): |
| 285 | + |
| 286 | + def __init__(self, prefix, *args, **kwargs): |
| 287 | + super().__init__(*args, **kwargs) |
| 288 | + self.__prefix = prefix |
| 289 | + |
| 290 | + def _variable(self, name, value): |
| 291 | + try: |
| 292 | + return '#define %s_%s %s' % \ |
| 293 | + (self.__prefix.upper(), name.upper(), int(value)) |
| 294 | + except: |
| 295 | + return '#define %s_%s "%s"' % \ |
| 296 | + (self.__prefix.upper(), name.upper(), value) |
| 297 | + |
| 298 | + def _prologue(self): |
| 299 | + yield '#ifndef %s_GIT_VERSION_HH' % self.__prefix |
| 300 | + yield '# define %s_GIT_VERSION_HH' % self.__prefix |
| 301 | + yield '' |
| 302 | + |
| 303 | + def _epilogue(self): |
| 304 | + yield '' |
| 305 | + yield '#endif' |
0 commit comments