OILS / build / ninja_main.py View on Github | oils.pub

545 lines, 296 significant
1#!/usr/bin/env python2
2"""
3build/ninja_main.py - invoked by ./NINJA-config.sh
4
5See build/README.md for the code and data layout.
6"""
7from __future__ import print_function
8
9import cStringIO
10from glob import glob
11import os
12import re
13import sys
14
15from build import ninja_lib
16from build.ninja_lib import log
17
18from asdl import NINJA_subgraph as asdl_subgraph
19from bin import NINJA_subgraph as bin_subgraph
20from core import NINJA_subgraph as core_subgraph
21from cpp import NINJA_subgraph as cpp_subgraph
22from data_lang import NINJA_subgraph as data_lang_subgraph
23from display import NINJA_subgraph as display_subgraph
24from frontend import NINJA_subgraph as frontend_subgraph
25from osh import NINJA_subgraph as osh_subgraph
26from mycpp import NINJA_subgraph as mycpp_subgraph
27from pea import NINJA_subgraph as pea_subgraph
28from prebuilt import NINJA_subgraph as prebuilt_subgraph
29from yaks import NINJA_subgraph as yaks_subgraph
30from ysh import NINJA_subgraph as ysh_subgraph
31
32from vendor import ninja_syntax
33
34# The file Ninja runs by default.
35BUILD_NINJA = 'build.ninja'
36
37
38def TarballManifest(some_files):
39 names = []
40
41 # Code we know about
42 names.extend(some_files)
43
44 names.extend([
45 # Text
46 'LICENSE.txt',
47 'README-native.txt',
48 'INSTALL.txt',
49 'configure',
50 'install',
51 'doc/osh.1',
52
53 # Build Scripts
54 'build/common.sh',
55 'build/native.sh',
56 'build/static-oils.sh',
57
58 # These 2 are used by build/ninja-rules-cpp.sh
59 'build/py2.sh',
60 'build/dev-shell.sh',
61 'build/ninja-rules-cpp.sh',
62
63 # These are in build/py.sh, not Ninja. Should probably put them in Ninja.
64 #'_gen/frontend/help_meta.h',
65 '_gen/frontend/match.re2c.h',
66 '_gen/frontend/id_kind.asdl_c.h',
67 '_gen/frontend/types.asdl_c.h',
68 ])
69
70 # For configure
71 names.extend(glob('build/detect-*.c'))
72
73 names.extend(glob('cpp/*.h'))
74 names.extend(glob('mycpp/*.h'))
75 names.extend(glob('asdl/*.h'))
76
77 # ONLY the headers
78 names.extend(glob('prebuilt/*/*.h'))
79
80 names.sort() # Pass them to tar sorted
81
82 # Check for dupes here
83 unique = sorted(set(names))
84 if names != unique:
85 dupes = [n for n in names if names.count(n) > 1]
86 raise AssertionError("Tarball manifest shouldn't have duplicates: %s" %
87 dupes)
88
89 for name in names:
90 print(name)
91
92
93def _WinPath(s):
94 return s.replace('/', '\\')
95
96
97def BatchFunctions(app_name, cc_sources, f, argv0):
98 objects = []
99 in_out = []
100 for src in sorted(cc_sources):
101 # e.g. _build/obj/cxx-dbg-sh/posix.o
102 prefix, _ = os.path.splitext(src)
103
104 #obj = '_build/obj/$compiler-$variant-sh/%s.o' % prefix
105 obj = '_build/obj/cxx-dbg/%s.o' % prefix
106 in_out.append((src, obj))
107
108 obj_dirs = sorted(set(os.path.dirname(obj) for _, obj in in_out))
109 for d in obj_dirs:
110 print('mkdir %s' % _WinPath(d), file=f)
111
112 print('', file=f)
113
114 for i, (src, obj) in enumerate(in_out):
115 #obj_quoted = '"%s"' % obj
116 objects.append(_WinPath(obj))
117
118 # TODO: OILS_WIN32 is a hack, should have ./configure for dprintf()
119 print('g++ -I . -D OILS_WIN32 -D MARK_SWEEP -c -o %s %s' %
120 (_WinPath(obj), _WinPath(src)),
121 file=f)
122 print('if %ERRORLEVEL% neq 0 goto :error', file=f)
123
124 print('', file=f)
125
126 # Link
127
128 out_dir = '_bin/cxx-dbg'
129 out = '%s/%s' % (out_dir, app_name)
130 print('mkdir %s' % _WinPath(out_dir), file=f)
131 print('g++ -o %s %s' % (_WinPath(out), ' '.join(objects)), file=f)
132
133 print('', file=f)
134 print(':error', file=f)
135 print(' echo ERROR %ERRORLEVEL%', file=f)
136 print(' exit /b %ERRORLEVEL%', file=f)
137
138
139def ShellFunctions(app_name, cc_sources, f, argv0):
140 """
141 Generate a shell fragment that invokes the same function that build.ninja
142 does
143 """
144 # oils_for_unix -> oils-for-unix
145 app_name_hyphens = app_name.replace('_', '-')
146
147 print('''\
148main() {
149 ### Compile oils-for-unix into _bin/$compiler-$variant-sh/ (not with ninja)
150
151 parse_flags "$@"
152
153 # Copy into locals
154 local compiler=$FLAG_cxx
155 local variant=$FLAG_variant
156 local translator=$FLAG_translator
157 local skip_rebuild=$FLAG_skip_rebuild
158
159 local out_name=%s
160
161 local out_dir
162 case $translator in
163 mycpp)
164 out_dir=_bin/$compiler-$variant-sh
165 ;;
166 *)
167 out_dir=_bin/$compiler-$variant-sh/$translator
168 ;;
169 esac
170 local out_path=$out_dir/$out_name$FLAG_suffix
171
172 echo
173 echo "$0: Building $out_name: $out_path"
174 echo " PWD = $PWD"
175 echo " cxx = $compiler"
176 echo " variant = $variant"
177 echo " translator = $translator"
178 echo " suffix = $FLAG_suffix"
179 if test -n "$FLAG_without_readline"; then
180 echo " without_readline = $FLAG_without_readline"
181 fi
182 if test -n "$skip_rebuild"; then
183 echo " skip_rebuild = $skip_rebuild"
184 fi
185
186 if test -n "$skip_rebuild" && test -f "$out_path"; then
187 echo
188 echo "$0: SKIPPING build because $out_path exists"
189 echo
190 return
191 fi
192
193 echo
194''' % app_name_hyphens,
195 file=f)
196
197 objects = []
198
199 # Filenames that depend on the translator
200 in_out = [
201 ('_gen/bin/%s.$translator.cc' % app_name,
202 '_build/obj/$compiler-$variant-sh/_gen/bin/%s.$translator.o' %
203 app_name),
204 ('_gen/bin/%s.$translator-main.cc' % app_name,
205 '_build/obj/$compiler-$variant-sh/_gen/bin/%s.$translator-main.o' %
206 app_name),
207 ]
208 for src in sorted(cc_sources):
209 # e.g. _build/obj/cxx-dbg-sh/posix.o
210 prefix, _ = os.path.splitext(src)
211
212 # HACK to skip the special ones above
213 if re.match(r'.*\.mycpp.*\.cc$', src):
214 continue
215
216 obj = '_build/obj/$compiler-$variant-sh/%s.o' % prefix
217 in_out.append((src, obj))
218
219 if 0:
220 from pprint import pformat
221 log('cc_sources = %s', pformat(cc_sources))
222 log('in_out = %s', pformat(in_out))
223
224 bin_dir = '_bin/$compiler-$variant-sh/$translator'
225 obj_dirs = sorted(set(os.path.dirname(obj) for _, obj in in_out))
226
227 all_dirs = [bin_dir] + obj_dirs
228 # Double quote
229 all_dirs = ['"%s"' % d for d in all_dirs]
230
231 print(' mkdir -p \\', file=f)
232 print(' %s' % ' \\\n '.join(all_dirs), file=f)
233 print('', file=f)
234
235 do_fork = ''
236
237 for i, (src, obj) in enumerate(in_out):
238 obj_quoted = '"%s"' % obj
239 objects.append(obj_quoted)
240
241 # Only fork one translation unit that we know to be slow
242 if src.endswith('.$translator.cc'):
243 # There should only be one forked translation unit
244 # It can be turned off with OILS_PARALLEL_BUILD= _build/oils
245 assert do_fork == ''
246 do_fork = '_do_fork=$OILS_PARALLEL_BUILD'
247 else:
248 do_fork = ''
249
250 if do_fork:
251 print(' # Potentially fork this translation unit with &', file=f)
252 print(' %s \\' % do_fork, file=f)
253 indent = ' ' if do_fork else ''
254 print(' %s_compile_one "$compiler" "$variant" "" \\' % indent, file=f)
255 print(' %s %s' % (src, obj_quoted), file=f)
256 if do_fork:
257 print(
258 ' _do_fork= # work around bug in some versions of the dash shell',
259 file=f)
260 print('', file=f)
261
262 print(' # wait for the translation unit before linking', file=f)
263 print(' echo WAIT', file=f)
264 # time -p shows any excess parallelism on 2 cores
265 # example: oils_for_unix.mycpp.cc takes ~8 seconds longer to compile than all
266 # other translation units combined!
267
268 # Timing isn't POSIX
269 #print(' time -p wait', file=f)
270 print(' wait', file=f)
271 print('', file=f)
272
273 print(' echo "LINK $out_path"', file=f)
274 # put each object on its own line, and indent by 4
275 # note: can't have spaces in filenames
276 print(' set -- \\', file=f)
277 print(' %s' % (' \\\n '.join(objects)), file=f)
278 print('', file=f)
279
280 # Strip opt binary
281 # TODO: provide a way for the user to get symbols?
282
283 print('''\
284
285 link "$compiler" "$variant" "" "$out_path" "$@"
286
287 if test "$variant" = opt; then
288 strip -o "$out_path.stripped" "$out_path"
289 fi
290
291 # Symlink to unstripped binary for benchmarking
292 cd "$out_dir" # dir may have spaces
293 for symlink in "osh$FLAG_suffix" "ysh$FLAG_suffix"; do
294 # like ln -v, which we can't use portably
295 echo " $symlink -> $out_name$FLAG_suffix"
296 ln -s -f "$out_name$FLAG_suffix" $symlink
297 done
298}
299
300main "$@"
301''',
302 file=f)
303
304
305def WritePreprocessed(n, cc_sources):
306 # See how much input we're feeding to the compiler. Test C++ template
307 # explosion, e.g. <unordered_map>
308 #
309 # Limit to {dbg,opt} so we don't generate useless rules. Invoked by
310 # metrics/source-code.sh
311
312 pre_matrix = [
313 ('cxx', 'dbg'),
314 ('cxx', 'opt'),
315 ('clang', 'dbg'),
316 ('clang', 'opt'),
317 ]
318 for compiler, variant in pre_matrix:
319 preprocessed = []
320 for src in cc_sources:
321 # e.g. mycpp/gc_heap.cc -> _build/preprocessed/cxx-dbg/mycpp/gc_heap.cc
322 pre = '_build/preprocessed/%s-%s/%s' % (compiler, variant, src)
323 preprocessed.append(pre)
324
325 # Summary file
326 n.build('_build/preprocessed/%s-%s.txt' % (compiler, variant),
327 'line_count', preprocessed)
328 n.newline()
329
330
331def InitSteps(n):
332 """Wrappers for build/ninja-rules-*.sh
333
334 Some of these are defined in mycpp/NINJA_subgraph.py. Could move them here.
335 """
336 #
337 # Compiling and linking
338 #
339
340 # Preprocess one translation unit
341 n.rule(
342 'preprocess',
343 # compile_one detects the _build/preprocessed path
344 command=
345 'build/ninja-rules-cpp.sh compile_one $compiler $variant $more_cxx_flags $in $out',
346 description='PP $compiler $variant $more_cxx_flags $in $out')
347 n.newline()
348
349 n.rule('line_count',
350 command='build/ninja-rules-cpp.sh line_count $out $in',
351 description='line_count $out $in')
352 n.newline()
353
354 # Compile one translation unit
355 n.rule(
356 'compile_one',
357 command=
358 'build/ninja-rules-cpp.sh compile_one $compiler $variant $more_cxx_flags $in $out $out.d',
359 depfile='$out.d',
360 # no prefix since the compiler is the first arg
361 description='$compiler $variant $more_cxx_flags $in $out')
362 n.newline()
363
364 # Link objects together
365 n.rule(
366 'link',
367 command=
368 'build/ninja-rules-cpp.sh link $compiler $variant $more_link_flags $out $in',
369 description='LINK $compiler $variant $more_link_flags $out $in')
370 n.newline()
371
372 # 1 input and 2 outputs
373 n.rule('strip',
374 command='build/ninja-rules-cpp.sh strip_ $in $out',
375 description='STRIP $in $out')
376 n.newline()
377
378 # cc_binary can have symliks
379 n.rule('symlink',
380 command='build/ninja-rules-cpp.sh symlink $out $symlink_val',
381 description='SYMLINK $out $symlink_val')
382 n.newline()
383
384 #
385 # Code generators
386 #
387
388 n.rule(
389 'write-shwrap',
390 # $in must start with main program
391 command='build/ninja-rules-py.sh write-shwrap $template $out $in',
392 description='make-shwrap $template $out $in')
393 n.newline()
394
395 n.rule('write-main',
396 command=
397 'build/ninja-rules-py.sh write-main $template $out $main_namespace',
398 description='write-main $out $main_namespace')
399 n.newline()
400
401 # Trivial build rule, for bin/mycpp_main -> _bin/shwrap/mycpp_souffle
402 # while adding implicit deps
403 n.rule('cp', command='cp $in $out', description='cp $in $out')
404 n.newline()
405
406
407def main(argv):
408 try:
409 action = argv[1]
410 except IndexError:
411 action = 'ninja'
412
413 try:
414 app_name = argv[2]
415 except IndexError:
416 app_name = 'oils_for_unix'
417
418 if action == 'ninja':
419 f = open(BUILD_NINJA, 'w')
420 else:
421 f = cStringIO.StringIO() # thrown away
422
423 n = ninja_syntax.Writer(f)
424 ru = ninja_lib.Rules(n)
425
426 ru.comment('InitSteps()')
427 InitSteps(n)
428
429 #
430 # Create the graph.
431 #
432
433 asdl_subgraph.NinjaGraph(ru)
434 ru.comment('')
435
436 # translate-mycpp rule
437 mycpp_subgraph.NinjaGraph(ru)
438 ru.comment('')
439
440 bin_subgraph.NinjaGraph(ru)
441 ru.comment('')
442
443 core_subgraph.NinjaGraph(ru)
444 ru.comment('')
445
446 cpp_subgraph.NinjaGraph(ru)
447 ru.comment('')
448
449 data_lang_subgraph.NinjaGraph(ru)
450 ru.comment('')
451
452 display_subgraph.NinjaGraph(ru)
453 ru.comment('')
454
455 frontend_subgraph.NinjaGraph(ru)
456 ru.comment('')
457
458 ysh_subgraph.NinjaGraph(ru)
459 ru.comment('')
460
461 osh_subgraph.NinjaGraph(ru)
462 ru.comment('')
463
464 pea_subgraph.NinjaGraph(ru)
465 ru.comment('')
466
467 prebuilt_subgraph.NinjaGraph(ru)
468 ru.comment('')
469
470 yaks_subgraph.NinjaGraph(ru)
471 ru.comment('')
472
473 deps = ninja_lib.Deps(ru)
474 # Materialize all the cc_binary() rules
475 deps.WriteRules()
476
477 main_cc = '_gen/bin/%s.mycpp-main.cc' % app_name
478
479 # Collect sources for metrics, tarball, shell script
480 cc_sources = deps.SourcesForBinary(main_cc)
481
482 if 0:
483 from pprint import pprint
484 pprint(cc_sources)
485
486 # TODO: could thin these out, not generate for unit tests, etc.
487 WritePreprocessed(n, cc_sources)
488
489 ru.WritePhony()
490
491 n.default(['_bin/cxx-asan/osh', '_bin/cxx-asan/ysh'])
492
493 if action == 'ninja':
494 log(' (%s) -> %s (%d targets)', argv[0], BUILD_NINJA,
495 n.num_build_targets())
496
497 elif action == 'shell':
498 ShellFunctions(app_name, cc_sources, sys.stdout, argv[0])
499
500 elif action == 'batch':
501 # TODO: write batch file
502 BatchFunctions(app_name, cc_sources, sys.stdout, argv[0])
503
504 elif action == 'tarball-manifest':
505 names = list(cc_sources) # copy
506
507 h = deps.HeadersForBinary(main_cc)
508 names.extend(h)
509
510 if app_name == 'oils_for_unix':
511 # TODO: SourcesForBinary() and HeadersForBinary can take MULTIPLE
512 # -main.cc
513 names.extend([
514 '_gen/bin/%s.mycpp-nosouffle.cc' % app_name,
515 '_gen/bin/%s.mycpp-nosouffle-main.cc' % app_name,
516
517 # TODO: remove
518 '_gen/bin/%s.mycpp-souffle.cc' % app_name,
519 '_gen/bin/%s.mycpp-souffle-main.cc' % app_name,
520 ])
521 names.append('_build/oils.sh')
522 else:
523 names.append('_build/%s.sh' % app_name)
524 names.append('_build/%s.bat' % app_name)
525
526 TarballManifest(names)
527
528 elif action == 'app-deps':
529 for name in cc_sources:
530 print(name)
531 print('---')
532 h_sources = deps.HeadersForBinary(main_cc)
533 for name in h_sources:
534 print(name)
535
536 else:
537 raise RuntimeError('Invalid action %r' % action)
538
539
540if __name__ == '__main__':
541 try:
542 main(sys.argv)
543 except RuntimeError as e:
544 print('FATAL: %s' % e, file=sys.stderr)
545 sys.exit(1)