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

453 lines, 236 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(cc_h_files):
39 names = []
40
41 # Code we know about
42 names.extend(cc_h_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
57 # These 2 are used by build/ninja-rules-cpp.sh
58 'build/py2.sh',
59 'build/dev-shell.sh',
60 'build/ninja-rules-cpp.sh',
61
62 # Generated
63 '_build/oils.sh',
64
65 # These are in build/py.sh, not Ninja. Should probably put them in Ninja.
66 #'_gen/frontend/help_meta.h',
67 '_gen/frontend/match.re2c.h',
68 '_gen/frontend/id_kind.asdl_c.h',
69 '_gen/frontend/types.asdl_c.h',
70 ])
71
72 # For configure
73 names.extend(glob('build/detect-*.c'))
74
75 # TODO: crawl headers
76 # We can now use the headers=[] attribute
77 names.extend(glob('bin/oils_for_unix_preamble.h'))
78 names.extend(glob('cpp/*.h'))
79 names.extend(glob('mycpp/*.h'))
80 names.extend(glob('asdl/*.h'))
81
82 # ONLY the headers
83 names.extend(glob('prebuilt/*/*.h'))
84
85 names.sort() # Pass them to tar sorted
86
87 # Check for dupes here
88 unique = sorted(set(names))
89 if names != unique:
90 dupes = [n for n in names if names.count(n) > 1]
91 raise AssertionError("Tarball manifest shouldn't have duplicates: %s" %
92 dupes)
93
94 for name in names:
95 print(name)
96
97
98def ShellFunctions(app_name, cc_sources, f, argv0):
99 """
100 Generate a shell fragment that invokes the same function that build.ninja
101 does
102 """
103 # oils_for_unix -> oils-for-unix
104 app_name_hyphens = app_name.replace('_', '-')
105
106 print('''\
107main() {
108 ### Compile oils-for-unix into _bin/$compiler-$variant-sh/ (not with ninja)
109
110 parse_flags "$@"
111
112 # Copy into locals
113 local compiler=$FLAG_cxx
114 local variant=$FLAG_variant
115 local translator=$FLAG_translator
116 local skip_rebuild=$FLAG_skip_rebuild
117
118 local out_name=%s
119
120 local out_dir
121 case $translator in
122 mycpp)
123 out_dir=_bin/$compiler-$variant-sh
124 ;;
125 *)
126 out_dir=_bin/$compiler-$variant-sh/$translator
127 ;;
128 esac
129 local out=$out_dir/$out_name
130
131 echo
132 echo "$0: Building $out_name: $out"
133 echo " PWD = $PWD"
134 echo " cxx = $compiler"
135 echo " variant = $variant"
136 echo " translator = $translator"
137 if test -n "$skip_rebuild"; then
138 echo " skip_rebuild = $skip_rebuild"
139 fi
140
141 if test -n "$skip_rebuild" && test -f "$out"; then
142 echo
143 echo "$0: SKIPPING build because $out exists"
144 echo
145 return
146 fi
147
148 echo
149''' % app_name_hyphens,
150 file=f)
151
152 objects = []
153
154 # Special case for souffle
155 in_out = [
156 ('_gen/bin/%s.$translator.cc' % app_name,
157 '_build/obj/$compiler-$variant-sh/_gen/bin/%s.$translator.o' %
158 app_name),
159 ]
160 for src in sorted(cc_sources):
161 # e.g. _build/obj/cxx-dbg-sh/posix.o
162 prefix, _ = os.path.splitext(src)
163
164 # Skip the one we started with
165 if prefix.startswith('_gen/bin/%s' % app_name):
166 continue
167
168 obj = '_build/obj/$compiler-$variant-sh/%s.o' % prefix
169 in_out.append((src, obj))
170
171 bin_dir = '_bin/$compiler-$variant-sh/$translator'
172 obj_dirs = sorted(set(os.path.dirname(obj) for _, obj in in_out))
173
174 all_dirs = [bin_dir] + obj_dirs
175 # Double quote
176 all_dirs = ['"%s"' % d for d in all_dirs]
177
178 print(' mkdir -p \\', file=f)
179 print(' %s' % ' \\\n '.join(all_dirs), file=f)
180 print('', file=f)
181
182 do_fork = ''
183
184 for i, (src, obj) in enumerate(in_out):
185 obj_quoted = '"%s"' % obj
186 objects.append(obj_quoted)
187
188 # Only fork one translation unit that we know to be slow
189 if re.match('.*oils_for_unix\..*\.cc', src):
190 # There should only be one forked translation unit
191 # It can be turned off with OILS_PARALLEL_BUILD= _build/oils
192 assert do_fork == ''
193 do_fork = '_do_fork=$OILS_PARALLEL_BUILD'
194 else:
195 do_fork = ''
196
197 if do_fork:
198 print(' # Potentially fork this translation unit with &', file=f)
199 print(' %s \\' % do_fork, file=f)
200 indent = ' ' if do_fork else ''
201 print(' %s_compile_one "$compiler" "$variant" "" \\' % indent, file=f)
202 print(' %s %s' % (src, obj_quoted), file=f)
203 if do_fork:
204 print(
205 ' _do_fork= # work around bug in some versions of the dash shell',
206 file=f)
207 print('', file=f)
208
209 print(' # wait for the translation unit before linking', file=f)
210 print(' echo WAIT', file=f)
211 # time -p shows any excess parallelism on 2 cores
212 # example: oils_for_unix.mycpp.cc takes ~8 seconds longer to compile than all
213 # other translation units combined!
214
215 # Timing isn't POSIX
216 #print(' time -p wait', file=f)
217 print(' wait', file=f)
218 print('', file=f)
219
220 print(' echo "LINK $out"', file=f)
221 # note: can't have spaces in filenames
222 print(' link "$compiler" "$variant" "" "$out" \\', file=f)
223 # put each object on its own line, and indent by 4
224 print(' %s' % (' \\\n '.join(objects)), file=f)
225 print('', file=f)
226
227 # Strip opt binary
228 # TODO: provide a way for the user to get symbols?
229
230 print('''\
231 if test "$variant" = opt; then
232 strip -o "$out.stripped" "$out"
233
234 # Symlink to unstripped binary for benchmarking
235 # out_name=$out_name.stripped
236 fi
237
238 cd "$out_dir" # dir may have spaces
239 for symlink in osh ysh; do
240 # like ln -v, which we can't use portably
241 echo " $symlink -> $out_name"
242 ln -s -f $out_name $symlink
243 done
244}
245
246main "$@"
247''',
248 file=f)
249
250
251def Preprocessed(n, cc_sources):
252 # See how much input we're feeding to the compiler. Test C++ template
253 # explosion, e.g. <unordered_map>
254 #
255 # Limit to {dbg,opt} so we don't generate useless rules. Invoked by
256 # metrics/source-code.sh
257
258 pre_matrix = [
259 ('cxx', 'dbg'),
260 ('cxx', 'opt'),
261 ('clang', 'dbg'),
262 ('clang', 'opt'),
263 ]
264 for compiler, variant in pre_matrix:
265 preprocessed = []
266 for src in cc_sources:
267 # e.g. mycpp/gc_heap.cc -> _build/preprocessed/cxx-dbg/mycpp/gc_heap.cc
268 pre = '_build/preprocessed/%s-%s/%s' % (compiler, variant, src)
269 preprocessed.append(pre)
270
271 # Summary file
272 n.build('_build/preprocessed/%s-%s.txt' % (compiler, variant),
273 'line_count', preprocessed)
274 n.newline()
275
276
277def InitSteps(n):
278 """Wrappers for build/ninja-rules-*.sh
279
280 Some of these are defined in mycpp/NINJA_subgraph.py. Could move them here.
281 """
282 #
283 # Compiling and linking
284 #
285
286 # Preprocess one translation unit
287 n.rule(
288 'preprocess',
289 # compile_one detects the _build/preprocessed path
290 command=
291 'build/ninja-rules-cpp.sh compile_one $compiler $variant $more_cxx_flags $in $out',
292 description='PP $compiler $variant $more_cxx_flags $in $out')
293 n.newline()
294
295 n.rule('line_count',
296 command='build/ninja-rules-cpp.sh line_count $out $in',
297 description='line_count $out $in')
298 n.newline()
299
300 # Compile one translation unit
301 n.rule(
302 'compile_one',
303 command=
304 'build/ninja-rules-cpp.sh compile_one $compiler $variant $more_cxx_flags $in $out $out.d',
305 depfile='$out.d',
306 # no prefix since the compiler is the first arg
307 description='$compiler $variant $more_cxx_flags $in $out')
308 n.newline()
309
310 # Link objects together
311 n.rule(
312 'link',
313 command=
314 'build/ninja-rules-cpp.sh link $compiler $variant $more_link_flags $out $in',
315 description='LINK $compiler $variant $more_link_flags $out $in')
316 n.newline()
317
318 # 1 input and 2 outputs
319 n.rule('strip',
320 command='build/ninja-rules-cpp.sh strip_ $in $out',
321 description='STRIP $in $out')
322 n.newline()
323
324 # cc_binary can have symliks
325 n.rule('symlink',
326 command='build/ninja-rules-cpp.sh symlink $dir $target $new',
327 description='SYMLINK $dir $target $new')
328 n.newline()
329
330 #
331 # Code generators
332 #
333
334 n.rule(
335 'write-shwrap',
336 # $in must start with main program
337 command='build/ninja-rules-py.sh write-shwrap $template $out $in',
338 description='make-pystub $out $in')
339 n.newline()
340
341 # Trivial build rule, for bin/mycpp_main -> _bin/shwrap/mycpp_souffle
342 # while adding implicit deps
343 n.rule('cp', command='cp $in $out', description='cp $in $out')
344 n.newline()
345
346
347def main(argv):
348 try:
349 action = argv[1]
350 except IndexError:
351 action = 'ninja'
352
353 try:
354 app_name = argv[2]
355 except IndexError:
356 app_name = 'oils_for_unix'
357
358 if action == 'ninja':
359 f = open(BUILD_NINJA, 'w')
360 else:
361 f = cStringIO.StringIO() # thrown away
362
363 n = ninja_syntax.Writer(f)
364 ru = ninja_lib.Rules(n)
365
366 ru.comment('InitSteps()')
367 InitSteps(n)
368
369 #
370 # Create the graph.
371 #
372
373 asdl_subgraph.NinjaGraph(ru)
374 ru.comment('')
375
376 # translate-mycpp rule
377 mycpp_subgraph.NinjaGraph(ru)
378 ru.comment('')
379
380 bin_subgraph.NinjaGraph(ru)
381 ru.comment('')
382
383 core_subgraph.NinjaGraph(ru)
384 ru.comment('')
385
386 cpp_subgraph.NinjaGraph(ru)
387 ru.comment('')
388
389 data_lang_subgraph.NinjaGraph(ru)
390 ru.comment('')
391
392 display_subgraph.NinjaGraph(ru)
393 ru.comment('')
394
395 frontend_subgraph.NinjaGraph(ru)
396 ru.comment('')
397
398 ysh_subgraph.NinjaGraph(ru)
399 ru.comment('')
400
401 osh_subgraph.NinjaGraph(ru)
402 ru.comment('')
403
404 pea_subgraph.NinjaGraph(ru)
405 ru.comment('')
406
407 prebuilt_subgraph.NinjaGraph(ru)
408 ru.comment('')
409
410 yaks_subgraph.NinjaGraph(ru)
411 ru.comment('')
412
413 # Materialize all the cc_binary() rules
414 ru.WriteRules()
415
416 # Collect sources for metrics, tarball, shell script
417 cc_sources = ru.SourcesForBinary('_gen/bin/%s.mycpp.cc' % app_name)
418
419 if 0:
420 from pprint import pprint
421 pprint(cc_sources)
422
423 # TODO: could thin these out, not generate for unit tests, etc.
424 Preprocessed(n, cc_sources)
425
426 ru.WritePhony()
427
428 n.default(['_bin/cxx-asan/osh', '_bin/cxx-asan/ysh'])
429
430 if action == 'ninja':
431 log(' (%s) -> %s (%d targets)', argv[0], BUILD_NINJA,
432 n.num_build_targets())
433
434 elif action == 'shell':
435 ShellFunctions(app_name, cc_sources, sys.stdout, argv[0])
436
437 elif action == 'tarball-manifest':
438 h = ru.HeadersForBinary('_gen/bin/%s.mycpp.cc' % app_name)
439 tar_cc_sources = cc_sources + [
440 '_gen/bin/%s.mycpp-souffle.cc' % app_name
441 ]
442 TarballManifest(tar_cc_sources + h)
443
444 else:
445 raise RuntimeError('Invalid action %r' % action)
446
447
448if __name__ == '__main__':
449 try:
450 main(sys.argv)
451 except RuntimeError as e:
452 print('FATAL: %s' % e, file=sys.stderr)
453 sys.exit(1)