├── AUTHORS ├── COPYING ├── README.md ├── scons2ninja.conf └── scons2ninja.py /AUTHORS: -------------------------------------------------------------------------------- 1 | Remko Tronçon (http://el-tramo.be) 2 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 Remko Tronçon (http://el-tramo.be) 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software is furnished to do so, 8 | subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [scons2ninja: Generate Ninja build files for SCons](https://el-tramo.be/scons2ninja) 2 | 3 | ## About 4 | 5 | This script generates a [Ninja](http://martine.github.io/ninja/) build file for a 6 | [SCons](http://scons.org) project. 7 | 8 | ## Usage 9 | 10 | First of all, you need to do a small change to the `SConstruct` file of your project. 11 | This script assumes that the `SConstruct` has a configuration variable 12 | `dump_trace`, which, when set, executes the following: 13 | 14 | env.SetOption('no_exec', True) 15 | env.Decider(lambda x, y, z: True) 16 | SCons.Node.Python.Value.changed_since_last_build = (lambda x, y, z: True) 17 | 18 | (i.e. a dry run of rebuilding all files). Also make sure the command line 19 | is fully printed. 20 | 21 | To generate the initial `build.ninja` file, run the following from the 22 | toplevel directory: 23 | 24 | path/to/scons2ninja.py 25 | 26 | This will generate the `build.ninja` file for an equivalent SCons build with the given flags. 27 | From then on, you can just type `ninja` to build. 28 | The `build.ninja` file will be automatically regenerated when necessary (i.e. when a 29 | `SCons*` file changes). 30 | 31 | The script also reads in a `.scons2ninja.conf` file in the current directory, which can 32 | be used to hook your own custom steps into the process. See the example configuration file 33 | to see some customizations. 34 | 35 | ## Caveats 36 | 37 | - This script isn't fully generic yet, so there are still pretty big holes in customization and 38 | configuration. 39 | - This script has only been tested with a very limited set of tools (gcc, clang, Visual Studio, 40 | Qt, ...). Since the script requires full 41 | knowledge of all tools run by SCons, it will fail if it finds an unsupported tool. Feel free 42 | to send a patch with support for your favorite tools. 43 | - The script does not take into account environment variables etc. set from SCons, and 44 | the ninja build will use the current environment instead. 45 | - Files that have dynamic content (i.e. content depending on other things than files, such as 46 | `Value`s), will not be regenerated automatically. This is a general problem with generator-based 47 | build systems. Delete the file if you want it to be regenerated. 48 | 49 | ## TODO 50 | 51 | - Use the batching feature for SCons-generated files, when/if it is implemented in Ninja. 52 | This allows to build all SCons-generated files in one SCons invocation, saving SCons startup 53 | time for every file. 54 | 55 | ## Other projects 56 | 57 | NaCl recently [added their own script](https://groups.google.com/forum/#!msg/native-client-dev/Ev06azGi7pI/2bGp2h_kY-UJ) for integrating Ninja with SCons. It plugs in directly into the SCons backend to get dependency information, 58 | which means it does not require full knowledge of all tools used in the build process. I will look into following 59 | a similar approach for scons2ninja, which would make it more generic and work better out of the box. 60 | -------------------------------------------------------------------------------- /scons2ninja.conf: -------------------------------------------------------------------------------- 1 | import glob, re, os.path 2 | 3 | scons_cmd = "python 3rdParty/SCons/scons.py" 4 | scons_dependencies += glob.glob("BuildTools/SCons/**/*.py") + glob.glob("BuildTools/SCons/SCons*") + ["config.py"] 5 | 6 | def ninja_post(ninja) : 7 | # Unit tests 8 | ninja.build('check', 'run', os.path.join('QA', 'UnitTest', 'checker' + EXE_SUFFIX)) 9 | 10 | # Swift binary 11 | if sys.platform == 'win32' : 12 | ninja.build(['Swift', 'swift'], 'phony', re.compile('Swift\\\\QtUI\\\\Swift\\\\(.*)')) 13 | elif sys.platform == 'posix' : 14 | ninja.build(['Swift', 'swift'], 'phony', 'Swift/QtUI/swift-im') 15 | else : 16 | ninja.build(['Swift', 'swift'], 'phony', re.compile('Swift/QtUI/Swift\.app/(.*)')) 17 | 18 | # Sluift 19 | if sys.platform == 'win32' : 20 | ninja.build(['Sluift', 'sluift'], 'phony', ['Sluift\\exe\\sluift.exe', 'Sluift\\dll\\sluift.dll']) 21 | elif sys.platform in ['posix', 'darwin'] : 22 | ninja.build(['Sluift', 'sluift'], 'phony', ['Sluift/exe/sluift', 'Sluift/dll/sluift.so']) 23 | 24 | # Extra rules 25 | if sys.platform == "darwin" : 26 | ninja.rule('zip', 27 | command = 'cd $dir && zip -r $relative_out $relative_in', 28 | description = 'ZIP $out') 29 | 30 | ninja.rule('package', 31 | command = 'Swift/Packaging/MacOSX/package.sh $in $template $out $qtdir', 32 | description = 'PACKAGE $out') 33 | 34 | 35 | def ninja_custom_command(ninja, command) : 36 | if sys.platform == "darwin" : 37 | m = re.match("cd (.*) && zip -r (.*) (.*)", line) 38 | if m : 39 | ninja.build(os.path.relpath(m.group(2)), 'zip', os.path.join(m.group(1), m.group(3)), dir = m.group(1), relative_in = m.group(3), relative_out = os.path.relpath(m.group(2), m.group(1))) 40 | return True 41 | m = re.match(".*/MacOSX/package.sh (.*) (.*) (.*) (.*)", line) 42 | if m : 43 | ninja.build(m.group(3), 'package', m.group(1), deps = [m.group(2)], qtdir = m.group(4), template = m.group(2)) 44 | return True 45 | return False 46 | -------------------------------------------------------------------------------- /scons2ninja.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | ################################################################################ 5 | # 6 | # scons2ninja: A script to create a Ninja build file from SCons. 7 | # 8 | # Copyright (c) 2013 Remko Tronçon 9 | # Licensed under the MIT license. 10 | # See COPYING for details. 11 | # 12 | ################################################################################ 13 | 14 | import re, os, os.path, subprocess, sys, fnmatch, shlex 15 | 16 | ################################################################################ 17 | # Helper methods & variables 18 | ################################################################################ 19 | 20 | SCRIPT = sys.argv[0] 21 | SCONS_ARGS = ' '.join(sys.argv[1:]) 22 | 23 | # TODO: Make this a tool-specific map 24 | BINARY_FLAGS = ["-framework", "-arch", "-x", "--output-format", "-isystem", "-include"] 25 | 26 | if sys.platform == 'win32' : 27 | LIB_PREFIX = "" 28 | LIB_SUFFIX = "" 29 | EXE_SUFFIX = ".exe" 30 | else : 31 | LIB_PREFIX = "lib" 32 | LIB_SUFFIX = ".a" 33 | EXE_SUFFIX = "" 34 | 35 | def is_regexp(x) : 36 | return 'match' in dir(x) 37 | 38 | def is_list(l) : 39 | return type(l) is list 40 | 41 | def escape(s) : 42 | return s.replace(' ', '$ ').replace(':', '$:') 43 | 44 | def quote_spaces(s) : 45 | if ' ' in s : 46 | return '"' + s + '"' 47 | else : 48 | return s 49 | 50 | def to_list(l) : 51 | if not l : 52 | return [] 53 | if is_list(l) : 54 | return l 55 | return [l] 56 | 57 | def partition(l, f) : 58 | x = [] 59 | y = [] 60 | for v in l : 61 | if f(v) : 62 | x.append(v) 63 | else : 64 | y.append(v) 65 | return (x, y) 66 | 67 | def get_unary_flags(prefix, flags) : 68 | return [x[len(prefix):] for x in flags if x.lower().startswith(prefix.lower())] 69 | 70 | def extract_unary_flags(prefix, flags) : 71 | f1, f2 = partition(flags, lambda x : x.lower().startswith(prefix.lower())) 72 | return ([f[len(prefix):] for f in f1], f2) 73 | 74 | def extract_unary_flag(prefix, flags) : 75 | flag, flags = extract_unary_flags(prefix, flags) 76 | return (flag[0], flags) 77 | 78 | def extract_binary_flag(prefix, flags) : 79 | i = flags.index(prefix) 80 | flag = flags[i + 1] 81 | del flags[i] 82 | del flags[i] 83 | return (flag, flags) 84 | 85 | def get_non_flags(flags) : 86 | skip = False 87 | result = [] 88 | for f in flags : 89 | if skip : 90 | skip = False 91 | elif f in BINARY_FLAGS : 92 | skip = True 93 | elif not f.startswith("/") and not f.startswith("-") : 94 | result.append(f) 95 | return result 96 | 97 | def extract_non_flags(flags) : 98 | non_flags = get_non_flags(flags) 99 | return (non_flags, filter(lambda x : x not in non_flags, flags)) 100 | 101 | def get_dependencies(target, build_targets) : 102 | result = [] 103 | queue = list(dependencies.get(target, [])) 104 | while len(queue) > 0 : 105 | n = queue.pop() 106 | # Filter out Value() results 107 | if n in build_targets or os.path.exists(n) : 108 | result.append(n) 109 | queue += list(dependencies.get(n, [])) 110 | return result 111 | 112 | def get_built_libs(libs, libpaths, outputs) : 113 | canonical_outputs = [os.path.abspath(p) for p in outputs] 114 | result = [] 115 | for libpath in libpaths : 116 | for lib in libs : 117 | lib_libpath = os.path.join(libpath, LIB_PREFIX + lib + LIB_SUFFIX) 118 | if os.path.abspath(lib_libpath) in canonical_outputs : 119 | result.append(lib_libpath) 120 | return result 121 | 122 | def parse_tool_command(line) : 123 | command = shlex.split(line) 124 | flags = command[1:] 125 | tool = os.path.splitext(os.path.basename(command[0]))[0] 126 | if tool.startswith('clang++') or tool.startswith('g++') : 127 | tool = "cxx" 128 | elif tool.startswith('clang') or tool.startswith('gcc') : 129 | tool = "cc" 130 | if tool in ["cc", "cxx"] and not "-c" in flags : 131 | tool = "glink" 132 | tool = tool.replace('-qt4', '') 133 | return tool, command, flags 134 | 135 | def rglob(pattern, root = '.') : 136 | return [os.path.join(path, f) for path, dirs, files in os.walk(root) for f in fnmatch.filter(files, pattern)] 137 | 138 | ################################################################################ 139 | # Helper for building Ninja files 140 | ################################################################################ 141 | 142 | class NinjaBuilder : 143 | def __init__(self) : 144 | self._header = "" 145 | self.variables = "" 146 | self.rules = "" 147 | self._build = "" 148 | self.pools = "" 149 | self._flags = {} 150 | self.targets = [] 151 | 152 | def header(self, text) : 153 | self._header += text + "\n" 154 | 155 | def rule(self, name, **kwargs) : 156 | self.rules += "rule " + name + "\n" 157 | for k, v in kwargs.iteritems() : 158 | self.rules += " " + str(k) + " = " + str(v) + "\n" 159 | self.rules += "\n" 160 | 161 | def pool(self, name, **kwargs) : 162 | self.pools += "pool " + name + "\n" 163 | for k, v in kwargs.iteritems() : 164 | self.pools += " " + str(k) + " = " + str(v) + "\n" 165 | self.pools += "\n" 166 | 167 | def variable(self, name, value) : 168 | self.variables += str(name) + " = " + str(value) + "\n" 169 | 170 | def build(self, target, rule, sources = None, **kwargs) : 171 | self._build += "build " + self.to_string(target) + ": " + rule 172 | if sources : 173 | self._build += " " + self.to_string(sources) 174 | if 'deps' in kwargs and kwargs['deps'] : 175 | self._build += " | " + self.to_string(kwargs["deps"]) 176 | if 'order_deps' in kwargs : 177 | self._build += " || " + self.to_string(kwargs['order_deps']) 178 | self._build += "\n" 179 | for var, value in kwargs.iteritems() : 180 | if var in ['deps', 'order_deps'] : 181 | continue 182 | value = self.to_string(value, quote = True) 183 | if var.endswith("flags") : 184 | value = self.get_flags_variable(var, value) 185 | self._build += " " + var + " = " + value + "\n" 186 | self.targets += to_list(target) 187 | 188 | def header_targets(self) : 189 | return [x for x in self.targets if x.endswith('.h') or x.endswith('.hh')] 190 | 191 | def serialize(self) : 192 | result = "" 193 | result += self._header + "\n" 194 | result += self.variables + "\n" 195 | for prefix in self._flags.values() : 196 | for k, v in prefix.iteritems() : 197 | result += v + " = " + k + "\n" 198 | result += "\n" 199 | result += self.pools + "\n" 200 | result += self.rules + "\n" 201 | result += self._build + "\n" 202 | return result 203 | 204 | def to_string(self, lst, quote = False) : 205 | if is_list(lst) : 206 | if quote : 207 | return ' '.join([quote_spaces(x) for x in lst]) 208 | else : 209 | return ' '.join([escape(x) for x in lst]) 210 | if is_regexp(lst) : 211 | return ' '.join([escape(x) for x in self.targets if lst.match(x)]) 212 | return escape(lst) 213 | 214 | def get_flags_variable(self, flags_type, flags) : 215 | if len(flags) == 0 : 216 | return '' 217 | if flags_type not in self._flags : 218 | self._flags[flags_type] = {} 219 | type_flags = self._flags[flags_type] 220 | if flags not in type_flags : 221 | type_flags[flags] = flags_type + "_" + str(len(type_flags)) 222 | return "$" + type_flags[flags] 223 | 224 | 225 | ################################################################################ 226 | # Configuration 227 | ################################################################################ 228 | 229 | ninja_post = [] 230 | scons_cmd = "scons" 231 | scons_dependencies = ['SConstruct'] + rglob('SConscript') 232 | 233 | def ninja_custom_command(ninja, line) : 234 | return False 235 | 236 | CONFIGURATION_FILE = '.scons2ninja.conf' 237 | execfile(CONFIGURATION_FILE) 238 | 239 | scons_dependencies = [os.path.normpath(x) for x in scons_dependencies] 240 | 241 | 242 | ################################################################################ 243 | # Rules 244 | ################################################################################ 245 | 246 | ninja = NinjaBuilder() 247 | 248 | ninja.pool('scons_pool', depth = 1) 249 | 250 | if sys.platform == 'win32' : 251 | ninja.rule('cl', 252 | deps = 'msvc', 253 | command = '$cl /showIncludes $clflags -c $in /Fo$out', 254 | description = 'CXX $out') 255 | 256 | ninja.rule('link', 257 | command = '$link $in $linkflags $libs /out:$out', 258 | description = 'LINK $out') 259 | 260 | ninja.rule('link_mt', 261 | command = '$link $in $linkflags $libs /out:$out ; $mt $mtflags', 262 | description = 'LINK $out') 263 | 264 | ninja.rule('lib', 265 | command = '$lib $libflags /out:$out $in', 266 | description = 'AR $out') 267 | 268 | ninja.rule('rc', 269 | command = '$rc $rcflags /Fo$out $in', 270 | description = 'RC $out') 271 | 272 | # SCons doesn't touch files if they didn't change, which makes 273 | # ninja rebuild the file over and over again. There's no touch on Windows :( 274 | # Could implement it with a script, but for now, delete the file if 275 | # this problem occurs. I'll fix it if it occurs too much. 276 | ninja.rule('scons', 277 | command = scons_cmd + " ${scons_args} $out", 278 | pool = 'scons_pool', 279 | description = 'GEN $out') 280 | 281 | ninja.rule('install', command = 'cmd /c copy $in $out') 282 | ninja.rule('run', command = '$in') 283 | else : 284 | ninja.rule('cxx', 285 | deps = 'gcc', 286 | depfile = '$out.d', 287 | command = '$cxx -MMD -MF $out.d $cxxflags -c $in -o $out', 288 | description = 'CXX $out') 289 | 290 | ninja.rule('cc', 291 | deps = 'gcc', 292 | depfile = '$out.d', 293 | command = '$cc -MMD -MF $out.d $ccflags -c $in -o $out', 294 | description = 'CC $out') 295 | 296 | ninja.rule('link', 297 | command = '$glink -o $out $in $linkflags', 298 | description = 'LINK $out') 299 | 300 | ninja.rule('ar', 301 | command = 'ar $arflags $out $in && ranlib $out', 302 | description = 'AR $out') 303 | 304 | # SCons doesn't touch files if they didn't change, which makes 305 | # ninja rebuild the file over and over again. Touching solves this. 306 | ninja.rule('scons', 307 | command = scons_cmd + " $out && touch $out", 308 | pool = 'scons_pool', 309 | description = 'GEN $out') 310 | 311 | ninja.rule('install', command = 'install $in $out') 312 | ninja.rule('run', command = './$in') 313 | 314 | 315 | ninja.rule('moc', 316 | command = '$moc $mocflags -o $out $in', 317 | description = 'MOC $out') 318 | 319 | ninja.rule('rcc', 320 | command = '$rcc $rccflags -name $name -o $out $in', 321 | description = 'RCC $out') 322 | 323 | ninja.rule('uic', 324 | command = '$uic $uicflags -o $out $in', 325 | description = 'UIC $out') 326 | 327 | ninja.rule('lrelease', 328 | command = '$lrelease $lreleaseflags $in -qm $out', 329 | description = 'LRELEASE $out') 330 | 331 | ninja.rule('ibtool', 332 | command = '$ibtool $ibtoolflags --compile $out $in', 333 | description = 'IBTOOL $out') 334 | 335 | ninja.rule('dsymutil', 336 | command = '$dsymutil $dsymutilflags -o $out $in', 337 | description = 'DSYMUTIL $out') 338 | 339 | ninja.rule('generator', 340 | command = "python " + SCRIPT + " ${scons_args}", 341 | depfile = ".scons2ninja.deps", 342 | pool = 'scons_pool', 343 | generator = '1', 344 | description = 'Regenerating build.ninja') 345 | 346 | 347 | ################################################################################ 348 | # Build Statements 349 | ################################################################################ 350 | 351 | scons_generate_cmd = scons_cmd + " " + SCONS_ARGS + " --tree=all,prune dump_trace=1" 352 | #scons_generate_cmd = 'cmd /c type scons2ninja.in' 353 | #scons_generate_cmd = 'cat scons2ninja.in' 354 | 355 | # Pass 1: Parse dependencies (and prefilter some build rules) 356 | build_lines = [] 357 | dependencies = {} 358 | mtflags = {} 359 | previous_file = None 360 | f = subprocess.Popen(scons_generate_cmd, stdout = subprocess.PIPE, stderr = subprocess.PIPE, shell=True) 361 | stage = 'preamble' 362 | skip_nth_line = -1 363 | stack = ['.'] 364 | for line in f.stdout : 365 | line = line.rstrip() 366 | 367 | # Skip lines if requested from previous command 368 | if skip_nth_line >= 0 : 369 | skip_nth_line -= 1 370 | if skip_nth_line == 0 : 371 | continue 372 | 373 | if line.startswith('scons: done building targets') : 374 | break 375 | 376 | if stage == "preamble" : 377 | # Pass all lines from the SCons configuration step to output 378 | if re.match("^scons: Building targets ...", line) : 379 | stage = "build" 380 | else : 381 | print line 382 | 383 | elif stage == "build" : 384 | if line.startswith('+-') : 385 | stage = "dependencies" 386 | elif re.match("^Using tempfile", line) : 387 | # Ignore response files from MSVS 388 | skip_nth_line = 2 389 | else : 390 | build_lines.append(line) 391 | 392 | # Already detect targets that will need 'mt' 393 | tool, _, flags = parse_tool_command(line) 394 | if tool == 'mt' : 395 | target = get_unary_flags("-outputresource:", flags)[0] 396 | target = target[0:target.index(';')] 397 | mtflags[target] = flags 398 | 399 | elif stage == "dependencies" : 400 | if not re.match('^[\s|]+\+\-', line) : 401 | # Work around bug in SCons that splits output over multiple lines 402 | continue 403 | 404 | level = line.index('+-') / 2 405 | filename = line[level*2+2:] 406 | if filename.startswith('[') : 407 | filename = filename[1:-1] 408 | 409 | # Check if we use the 'fixed' format which escapes filenamenames 410 | if filename.startswith('\'') and filename.endswith('\'') : 411 | filename = eval(filename) 412 | 413 | if level < len(stack) : 414 | stack = stack[0:level] 415 | elif level > len(stack) : 416 | if level != len(stack) + 1 : 417 | raise Exception("Internal Error" ) 418 | stack.append(previous_filename) 419 | 420 | # Skip absolute paths 421 | if not os.path.isabs(filename) : 422 | target = stack[-1] 423 | if target not in dependencies : 424 | dependencies[target] = [] 425 | dependencies[target].append(filename) 426 | previous_filename = filename 427 | 428 | if f.wait() != 0 : 429 | print "Error calling '" + scons_generate_cmd + "'" 430 | print f.stderr.read() 431 | exit(-1) 432 | 433 | # Pass 2: Parse build rules 434 | tools = {} 435 | for line in build_lines : 436 | # Custom python function 437 | m = re.match('^(\w+)\(\[([^\]]*)\]', line) 438 | if m : 439 | out = [x[1:-1] for x in m.group(2).split(',')] 440 | for x in out : 441 | # 'Note' = To be more correct, deps should also include $scons_dependencies, 442 | # but this regenerates a bit too often, so leaving it out for now. 443 | ninja.build(x, 'scons', None, deps = sorted(get_dependencies(x, ninja.targets))) 444 | continue 445 | 446 | 447 | # TextFile 448 | m = re.match("^Creating '([^']+)'", line) 449 | if m : 450 | out = m.group(1) 451 | # Note: To be more correct, deps should also include $scons_dependencies, 452 | # but this regenerates a bit too often, so leaving it out for now. 453 | ninja.build(out, 'scons', None, deps = sorted(get_dependencies(out, ninja.targets))) 454 | continue 455 | 456 | # Install 457 | m = re.match('^Install file: "(.*)" as "(.*)"', line) 458 | if m : 459 | ninja.build(m.group(2), 'install', m.group(1)) 460 | continue 461 | 462 | m = re.match('^Install directory: "(.*)" as "(.*)"', line) 463 | if m : 464 | for source in rglob('*', m.group(1)) : 465 | if os.path.isdir(source) : 466 | continue 467 | target = os.path.join(m.group(2), os.path.relpath(source, m.group(1))) 468 | ninja.build(target, 'install', source) 469 | continue 470 | 471 | # Tools 472 | tool, command, flags = parse_tool_command(line) 473 | tools[tool] = command[0] 474 | 475 | ############################################################ 476 | # clang/gcc tools 477 | ############################################################ 478 | 479 | if tool == 'cc': 480 | out, flags = extract_binary_flag("-o", flags) 481 | files, flags = extract_non_flags(flags) 482 | ninja.build(out, 'cc', files, order_deps = '_generated_headers', ccflags = flags) 483 | 484 | elif tool == 'cxx': 485 | out, flags = extract_binary_flag("-o", flags) 486 | files, flags = extract_non_flags(flags) 487 | ninja.build(out, 'cxx', files, order_deps = '_generated_headers', cxxflags = flags) 488 | 489 | elif tool == 'glink': 490 | out, flags = extract_binary_flag("-o", flags) 491 | files, flags = extract_non_flags(flags) 492 | libs = get_unary_flags('-l', flags) 493 | libpaths = get_unary_flags("-L", flags) 494 | deps = get_built_libs(libs, libpaths, ninja.targets) 495 | ninja.build(out, 'link', files, deps = sorted(deps), linkflags = flags) 496 | 497 | elif tool == 'ar': 498 | objects, flags = partition(flags, lambda x: x.endswith('.o')) 499 | libs, flags = partition(flags, lambda x: x.endswith('.a')) 500 | out = libs[0] 501 | ninja.build(out, 'ar', objects, arflags = flags) 502 | 503 | elif tool == 'ranlib': 504 | pass 505 | 506 | 507 | ############################################################ 508 | # MSVC tools 509 | ############################################################ 510 | 511 | elif tool == 'cl': 512 | out, flags = extract_unary_flag("/Fo", flags) 513 | files, flags = extract_non_flags(flags) 514 | ninja.build(out, 'cl', files, order_deps = '_generated_headers', clflags = flags) 515 | 516 | elif tool == 'lib': 517 | out, flags = extract_unary_flag("/out:", flags) 518 | files, flags = extract_non_flags(flags) 519 | ninja.build(out, 'lib', files, libflags = flags) 520 | 521 | elif tool == 'link': 522 | objects, flags = partition(flags, lambda x: x.endswith('.obj') or x.endswith('.res')) 523 | out, flags = extract_unary_flag("/out:", flags) 524 | libs, flags = partition(flags, lambda x: not x.startswith("/") and x.endswith(".lib")) 525 | libpaths = get_unary_flags("/libpath:", flags) 526 | deps = get_built_libs(libs, libpaths, ninja.targets) 527 | if out in mtflags : 528 | ninja.build(out, 'link_mt', objects, deps = sorted(deps), 529 | libs = libs, linkflags = flags, mtflags = mtflags[out]) 530 | else : 531 | ninja.build(out, 'link', objects, deps = sorted(deps), 532 | libs = libs, linkflags = flags) 533 | 534 | elif tool == 'rc': 535 | out, flags = extract_unary_flag("/fo", flags) 536 | files, flags = extract_non_flags(flags) 537 | ninja.build(out, 'rc', files[0], order_deps = '_generated_headers', rcflags = flags) 538 | 539 | elif tool == 'mt': 540 | # Already handled 541 | pass 542 | 543 | ############################################################ 544 | # Qt tools 545 | ############################################################ 546 | 547 | elif tool == 'moc': 548 | out, flags = extract_binary_flag("-o", flags) 549 | files, flags = extract_non_flags(flags) 550 | ninja.build(out, 'moc', files, mocflags = flags) 551 | 552 | elif tool == 'uic': 553 | out, flags = extract_binary_flag("-o", flags) 554 | files, flags = extract_non_flags(flags) 555 | ninja.build(out, 'uic', files, uicflags = flags) 556 | 557 | elif tool == 'lrelease': 558 | out, flags = extract_binary_flag("-qm", flags) 559 | files, flags = extract_non_flags(flags) 560 | ninja.build(out, 'lrelease', files, lreleaseflags = flags) 561 | 562 | elif tool == 'rcc': 563 | out, flags = extract_binary_flag("-o", flags) 564 | name, flags = extract_binary_flag("-name", flags) 565 | files, flags = extract_non_flags(flags) 566 | deps = list(set(get_dependencies(out, ninja.targets)) - set(files)) 567 | ninja.build(out, 'rcc', files, deps = sorted(deps), name = name, rccflags = flags) 568 | 569 | ############################################################ 570 | # OS X tools 571 | ############################################################ 572 | 573 | elif tool == 'ibtool': 574 | out, flags = extract_binary_flag("--compile", flags) 575 | files, flags = extract_non_flags(flags) 576 | ninja.build(out, 'ibtool', files, ibtoolflags = flags) 577 | 578 | elif tool == 'dsymutil': 579 | out, flags = extract_binary_flag("-o", flags) 580 | files, flags = extract_non_flags(flags) 581 | ninja.build(out, 'dsymutil', files, dsymutilflags = flags) 582 | 583 | elif not ninja_custom_command(ninja, line) : 584 | raise Exception("Unknown tool: '" + line + "'") 585 | 586 | 587 | # Phony target for all generated headers, used as an order-only depency from all C/C++ sources 588 | ninja.build('_generated_headers', 'phony', ninja.header_targets()) 589 | 590 | # Regenerate build.ninja file 591 | ninja.build('build.ninja', 'generator', [], deps = [SCRIPT, CONFIGURATION_FILE]) 592 | 593 | # Header & variables 594 | ninja.header("# This file is generated by " + SCRIPT) 595 | ninja.variable("ninja_required_version", "1.3") 596 | ninja.variable("scons_args", SCONS_ARGS) 597 | for k, v in tools.iteritems() : 598 | ninja.variable(k, v) 599 | 600 | # Extra customizations 601 | if 'ninja_post' in dir() : 602 | ninja_post(ninja) 603 | 604 | 605 | ################################################################################ 606 | # Result 607 | ################################################################################ 608 | 609 | f = open(".scons2ninja.deps", "w") 610 | f.write("build.ninja: " + " ".join([d for d in scons_dependencies if os.path.exists(d)]) + "\n") 611 | f.close() 612 | 613 | f = open("build.ninja", "w") 614 | f.write(ninja.serialize()) 615 | f.close() 616 | --------------------------------------------------------------------------------