forked from mighty-gerbils/gerbil
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmake.ss
928 lines (834 loc) · 36.6 KB
/
make.ss
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
;;; -*- Gerbil -*-
;;; (C) vyzo at hackzen.org
;;; support for library build scripts
;;; build-spec: see documentation in doc/reference/make.md
(import :gerbil/compiler
:gerbil/expander
:gerbil/gambit
./srfi/1
./misc/hash
./misc/list
./misc/number
./misc/path
./misc/pqueue
./misc/queue
./misc/symbol
./misc/concurrent-plan
./sort
./sugar
./error
./assert)
(extern namespace: #f with-cons-load-path load-path)
(export make
make-clean
shell-config
env-cppflags
env-ldflags
include-gambit-sharp
pkg-config
pkg-config-libs
pkg-config-cflags
ldflags
cppflags
append-options)
(def default-gambit-gsc (path-expand "~~bin/gsc"))
(def default-gerbil-gxc (path-expand "~~bin/gxc"))
#|
TODO:
* Allow static-include: and gsc: without a ssi:, just a gxc:
and don't nest, just add a dependency.
* Allow for extra-dependency: to a list of other targets (as identified by spec-file ?)
in the plist of gxc:, etc.
* Divide the building of a spec into multiple actions, some foreground
(using Gerbil's non-thread-safe compiler), some background (spawning gsc),
each having inputs and outputs, backgroundability, and a perform method.
The inputs can have changed from the cache if the dependencies were out of date.
The outputs can change compare to the cache if the action was out of date.
* Provide a DSL to extend the spec language, each rule being defining one
or multiple actions, each with inputs and outputs, its backgroundability,
is perform method.
* Learn from the successes and failures of ASDF, Bazel, Dune, Cmake, etc.
* Should it be that, as in ASDF, there are in-memory actions?
Should they be, as in ASDF, exactly those without outputs?
Maybe, if we want to build session state, but they are not strictly needed to build Gerbil,
since all actions we are considering have outputs.
Unless we introduce intermediate steps (as in ASDF's prepare-op).
* Could they be exactly the non-backgroundable ones?
No, because we can't fork the current image state, so for the sake of not having to rebuild
that state (if expensive, naturally, or artificially, just because I (fare) don't know how
to do it so far), we might want to keep such actions in the foreground.
;; |#
;;; Functions that should be better moved some library...
(def (force-outputs) (force-output (current-error-port)) (force-output)) ;; move to std/misc/ports ?
(def (message . lst) (apply displayln lst) (force-outputs)) ;; move to std/misc/ports ?
(def (writeln x) (write x) (newline) (force-outputs)) ;; move to std/misc/ports ?
(def (prefix/ prefix path) (if prefix (string-append prefix "/" path) path)) ;; move to std/misc/path ?
;; from srfi/43
(def (srfi43-vector-for-each proc vec)
(vector-for-each proc (list->vector (iota (vector-length vec))) vec))
(def (vector-ensure-ref v i f)
(or (vector-ref v i) (let ((x (f))) (vector-set! v i x) x)))
;;; Settings: see details in doc/reference/make.md
(defstruct settings
(srcdir libdir bindir prefix force optimize debug verbose build-deps
libdir-prefix parallelize
full-program-optimization
build-release
build-optimized)
transparent: #t constructor: :init!)
(defmethod {:init! settings}
(lambda (self
srcdir: (srcdir_ #f) libdir: (libdir_ #f) bindir: (bindir_ #f)
prefix: (prefix_ #f) force: (force? #f)
optimize: (optimize #t) debug: (debug_ #f)
static: (_ignore-static #t)
verbose: (verbose_ #f) build-deps: (build-deps_ #f)
parallelize: (parallelize_ #f)
full-program-optimization: (full-program-optimization #f)
build-release: (build-release #f)
build-optimized: (build-optimized #f))
(def gerbil-path_ (delay (gerbil-path)))
(def srcdir (or srcdir_ (error "srcdir must be specified")))
(def libdir (or libdir_ (path-expand "lib" (force gerbil-path_))))
(def bindir (or bindir_ (path-expand "bin" (force gerbil-path_))))
(def prefix (or prefix_ (read-package-prefix srcdir)))
(def libdir-prefix (if prefix (path-expand prefix libdir) libdir))
(def build-deps (path-expand (or build-deps_ "build-deps") srcdir))
(def parallelize (gerbil-build-cores parallelize))
(def verbose
(cond
(verbose_)
((getenv "GERBIL_BUILD_VERBOSE" #f) => string->number)
(else #f)))
(def debug
(cond
(debug_)
((getenv "GERBIL_BUILD_DEBUG" #f) #t)
(else #f)))
(struct-instance-init!
self
srcdir libdir bindir prefix force? optimize debug verbose build-deps
libdir-prefix parallelize
full-program-optimization
build-release
build-optimized)))
(def (gerbil-build-cores (cpu-count-spec #t))
;; TODO: for the default (catch) case, use something like
;; (min (##cpu-count) (/ (available-memory) (memory-per-compilation-cpu)))
;; Except we need to compute (available-memory) and calibrate (memory-per-compilation-cpu)...
;; Until then, we disable the parallel build as it confuses a lot of beginners.
(cond
((real? cpu-count-spec) cpu-count-spec)
((eq? cpu-count-spec #t)
(with-catch (lambda (_) 0) ;; if not defined and an integer, default to 0
(lambda () (string->number (getenv "GERBIL_BUILD_CORES")))))
(else 0)))
(def (settings-verbose>=? settings level)
(def verbose (settings-verbose settings))
(and (real? level) (real? verbose) (>= verbose level)))
(def (read-package-prefix srcdir)
(let (plist (with-catch false (lambda () (call-with-input-file (path-expand "gerbil.pkg" srcdir) read))))
(alet (pkg (pgetq package: plist)) (and (symbol? pkg) (symbol->string pkg)))))
;;; Build item spec
(def (spec-type spec)
(match spec
((? string? _) gxc:)
([(? keyword? type) . _] type)
(else (error "Bad buildspec" spec))))
(def (spec-file spec settings)
(match spec
((? string? modf)
(source-path modf ".ss" settings))
([gxc: modf . opts]
(source-path modf ".ss" settings))
([gsc: modf . opts]
(source-path modf ".scm" settings))
([ssi: modf . deps]
(source-path modf ".ssi" settings))
([(or exe: static-exe: optimized-exe: optimized-static-exe:) modf . opts]
(source-path modf ".ss" settings))
([static-include: file]
(static-file-path file settings))
([copy: file]
file)
(else
(error "Bad buildspec" spec))))
(def (spec-inputs spec settings)
[(spec-file spec settings)
(spec-extra-inputs spec settings) ...
(append-map (cut spec-inputs <> settings) (spec-submodules spec)) ...])
(def (spec-extra-inputs spec settings)
(match spec
([gxc: . _] (pgetq extra-inputs: (spec-plist spec) []))
([gsc: . _] (pgetq extra-inputs: (spec-plist spec) []))
(_ [])))
(def (spec-submodules spec)
(match spec
([gxc: . _] (pgetq submodules: (spec-plist spec) []))
([ssi: _ . submodules] submodules)
(_ [])))
(def (spec-plist spec)
(match spec
([(? (cut member <> '(gxc: gsc:))) _ [plist ...] . _] plist)
(_ [])))
(def (spec-outputs spec settings)
(match spec
((? string? modf) (gxc-outputs modf #f settings))
([gxc: modf . opts] (gxc-outputs modf opts settings))
([gsc: modf . opts] [(gsc-c-path modf settings)])
([ssi: modf . submodules] [(library-path modf ".ssi" settings)
(append-map (cut spec-outputs <> settings) submodules) ...])
([(or exe: static-exe: optimized-exe: optimized-static-exe:) modf . opts]
[(binary-path modf opts settings)
(static-path modf settings)])
([static-include: file] [(static-file-path file settings)])
([copy: file] [(library-path file #f settings)])
(else (error "Bad buildspec" spec))))
(def (spec-backgroundable? spec)
(case (spec-type spec)
((gxc:) (not (pgetq foreground: (spec-plist spec))))
((gsc:) #t)
(else #f)))
;;; A cache to minimize filesystem access AND to avoid inconsistencies
;;; due to race-conditions while querying the filesystem.
(def cache (make-parameter #f))
(def (cache-put! key value)
(def c (cache))
(when c (hash-put! c key value))
value)
(def (cache-remove! key)
(def c (cache))
(when c (hash-remove! c key)))
;; Consult the cache entry for the key if present;
;; if not present, compute it by calling the thunk,
;; and set the cache entry accordingly, if the cache is defined.
;; return the values from the cache and/or the thunk computation.
(def (call-with-cache key (thunk false))
(def c (cache))
(if c (hash-ensure-ref c key thunk) (thunk)))
(defrule (with-cache key body ...) (call-with-cache key (lambda () body ...)))
(def (call-with-fresh-cache thunk) (parameterize ((cache (hash))) (thunk)))
(defrule (with-fresh-cache body ...) (call-with-fresh-cache (lambda () body ...)))
(def (modification-time file)
(let/cc return
(def info (with-catch (lambda (_) (return #f)) (lambda () (file-info file #t))))
(time->seconds (file-info-last-modification-time info))))
(def (modification-time/cache file)
(with-cache ['modification-time file] (modification-time file)))
(def (file-timestamp file)
(or (modification-time/cache file) +inf.0))
(def (module-strip-nesting module)
(def name (symbol->string module))
(cond ((string-rindex name #\$) => (lambda (ix) (string->symbol (substring name 0 ix))))
(else module)))
(def (library-symbol module)
(make-symbol ":" (module-strip-nesting module)))
(def (library-file library)
(with-catch
(lambda (e) (error "missing library"
context: 'library-file
library: library symbol: (library-symbol library)
load-path: (values->list (load-path))
error-message: (error-message e)
error: e))
(lambda () (core-resolve-library-module-path (library-symbol library)))))
(def (library-timestamp library)
(file-timestamp (library-file library)))
;;; The build-deps-file contains a list in dependency order of (id spec deps) entries,
;;; where id is a symbol, inputs are non-empty lists of strings denoting files in srcdir,
;;; and deps is a list of symbols denoting module dependencies outside srcdir.
(def build-deps-tag gerbil-build-deps-v2:)
(def (read-build-deps file)
(match (with-catch void (cut call-with-input-file file read))
([(? (cut equal? <> build-deps-tag)) . s] s)
(_ [])))
(def (write-build-deps file build-deps)
(create-directory* (path-directory file))
(call-with-output-file file
(lambda (port) (pretty-print [build-deps-tag . build-deps] port) (newline port))))
(def (make . args)
;; 0. Compute settings, setup a surrounding cache, change directory
(defvalues (positionals keywords) (separate-keyword-arguments args #t))
(def buildspec (match positionals ([x] x) (_ (error "invalid arguments" make positionals))))
(def settings (apply make-settings keywords))
(parameterize ((current-directory (settings-srcdir settings))
(current-expander-compiling? #t))
(with-fresh-cache (%make buildspec settings))))
(def (%make buildspec settings)
;;; 0. (Continued) Extract settings
(def force? (settings-force settings))
(def build-deps-file (settings-build-deps settings))
(def verbose (settings-verbose settings))
(def (verbose>=? n) (settings-verbose>=? settings n))
(when (verbose>=? 3) (writeln [Step: 0 settings: settings]))
;;; 1. Instantiate the data model that represents the build
(def spec@ (list->vector (normalize-buildspec buildspec))) ; specs, indexed by target for random access
(def nspec (vector-length spec@)) ; total number of targets
(def (specvec) (make-vector nspec #f)) ; vector indexed by target number
(def spec-index% (invert-hash<-vector spec@)) ; target number by spec
(def id@ (specvec)) ; dependency id by target number
(def id-index% (hash)) ; target number by dependency id
(def deps@ (specvec)) ; list of dependencies (as symbols) by target number
(def timestamp% (hash)) ; timestamp by dependency id
(def blocked-by@ (specvec)) ;; #f or hash of numbers of specs blocked by the numbered target
(def blocking@ (specvec)) ;; #f or hash of numbers of specs blocking the numbered target
;; Actions are prioritized by order in user build-spec
(def ready-fg (make-pqueue identity <)) ;; foreground-only targets that are ready to build
(def ready-bg (make-pqueue identity >)) ;; backgroundable targets that are ready to build (negative)
(when (verbose>=? 3) (writeln [Step: 1 spec@: spec@]))
;;; 2. Reinstate those cached dependencies from previous build that are still up-to-date:
(def (dependency-timestamp dep) ;; Get timestamp for each dependency by id symbol.
;; We must be careful to only call that for modules outside the current project
(with-catch (lambda (_) +inf.0)
(lambda () (hash-ensure-ref timestamp% dep (cut library-timestamp dep)))))
(alet ((build-deps-timestamp (modification-time/cache build-deps-file))
(previous@ (read-build-deps build-deps-file)))
(def previous-out-of-date% (hash))
(def (previous-file-up-to-date? file)
(<= (file-timestamp file) build-deps-timestamp))
(def (previous-dependency-up-to-date? dep)
(or (hash-get id-index% dep) ;; NB: we depend on the previous build-deps being topologically sorted
(and (not (hash-get previous-out-of-date% dep))
(<= (dependency-timestamp dep) build-deps-timestamp))))
(def (previous-entry-up-to-date? spec deps)
(and (andmap previous-file-up-to-date? (spec-inputs spec settings))
(andmap previous-dependency-up-to-date? deps)))
(def (consider-previous id spec deps)
(when (verbose>=? 7) (writeln [previous: id spec: spec deps: deps target: (hash-get spec-index% spec) up-to-date?: (and (hash-get spec-index% spec) (previous-entry-up-to-date? spec deps))]))
(def target (hash-get spec-index% spec))
(if (and target (previous-entry-up-to-date? spec deps))
(begin (vector-set! id@ target id)
(hash-put! id-index% id target)
(vector-set! deps@ target deps))
(hash-put! previous-out-of-date% id #t)))
(for-each (cut apply consider-previous <>) previous@))
(when (verbose>=? 3)
(writeln [Step: 2 previous-up-to-date: (hash->list/sort id-index% symbol<?)]))
;; 3. Compute dependencies for entries that are out-of-date, or at least not cached
;; NB: We assume this will catch any circular dependency and error out.
((cut srfi43-vector-for-each <> spec@)
(lambda (target spec)
(when (verbose>=? 7) (writeln [Step: 3.0 target: target spec]))
(unless (vector-ref id@ target) ; skip if already done as up-to-date from previous build-deps
(let-values (((id deps) (file-dependencies (spec-file spec settings) settings)))
(when (verbose>=? 7) (writeln [target spec id deps]))
(vector-set! id@ target id)
(hash-put! id-index% id target)
(vector-set! deps@ target deps)))))
(when (verbose>=? 3) (writeln [Step: 3 deps: (apply map list (map vector->list [id@ spec@ deps@]))]))
;; 4. Update the build-deps cache, topologically sorted.
(def (blockings blocks)
(let loop ((i (1- nspec)) (l []))
(if (> 0 i) l
(let* ((h1 (vector-ref blocks i))
(h2 (and h1 (hash-keys h1)))
(h3 (and h2 (pair? h2) h2)))
(loop (1- i) (if h3 [[i h3] . l] l))))))
(def (init-walk) ;; Initialize a walk of the build graph, which we do twice, in steps 4 and 5.
(when (verbose>=? 8)
(writeln [ready-fg: (pqueue-contents ready-fg)
ready-bg: (pqueue-contents ready-bg)
blocked-by: (blockings blocked-by@) len: (length (blockings blocked-by@))
blocking: (blockings blocking@) len: (length (blockings blocking@))]))
(assert! (null? (blockings blocked-by@)))
(assert! (null? (blockings blocking@)))
(assert! (pqueue-empty? ready-fg))
(assert! (pqueue-empty? ready-bg))
((cut srfi43-vector-for-each <> deps@)
(lambda (target deps)
((cut for-each <> deps)
(lambda (dep)
(let ((other-target (hash-get id-index% dep)))
(when other-target
(hash-put! (vector-ensure-ref blocked-by@ target make-hash-table) other-target #t)
(hash-put! (vector-ensure-ref blocking@ other-target make-hash-table) target #t)))))))
((cut srfi43-vector-for-each <> spec@)
(lambda (target _) (unless (vector-ref blocked-by@ target) (pqueue-push! ready-fg target)))))
(def (mark-built target . _)
(alet (blocking (vector-ref blocking@ target))
((cut hash-for-each <> blocking)
(lambda (unblocked _)
(hash-remove! blocking unblocked)
(def blocked-by (vector-ref blocked-by@ unblocked))
(hash-remove! blocked-by target)
(when (hash-empty? blocked-by)
(pqueue-push! ready-fg unblocked))))))
;; Walk the dependencies without parallelism, to topologically sort the build-deps.
(init-walk)
(def build-list ; actual build order by target number
(with-list-builder (c)
(perform-plan/threads
ready-fg ready-bg
perform: void on-failure: void ;; nothing to perform, no failure possible doing nothing.
announce: c ;; record the target in the order they are issued
on-success: mark-built
deterministic-order: #t max-workers: 1)))
;; Write down the build-deps cache file.
(write-build-deps
build-deps-file
(map (lambda (target) (map (cut vector-ref <> target) [id@ spec@ deps@])) build-list))
(unless (= (length build-list) nspec)
;; This should never happen except by corrupted build-deps:
;; Any circularity should be caught in 3.
(error "Circular dependency in build??? Remove build-deps and try again?"))
(when (verbose>=? 3) (writeln [Step: 4 build-deps: build-deps-file]))
;; 5. Now, actually build that stuff in order, and update timestamps as we go.
(def (compute-timestamp target)
(def spec (vector-ref spec@ target))
(def deps (vector-ref deps@ target))
(def inputs (spec-inputs spec settings))
(def outputs (spec-outputs spec settings))
(def dep-timestamps (map dependency-timestamp deps))
(def in-timestamps (map file-timestamp inputs))
(def out-timestamps (map file-timestamp outputs))
(def dep-max (xmax/list dep-timestamps))
(def in-max (xmax dep-max (xmax/list in-timestamps)))
(def out-min (xmin/list out-timestamps))
(def out-max (xmax/list out-timestamps))
(when (verbose>=? 6)
(writeln [compute-timestamp:
[target: target] [spec: spec]
[in-max: in-max] [out-min: out-min] [out-max: out-max]
[deps: (map list dep-timestamps deps)]
[ins: (map list in-timestamps inputs)]
[outs: (map list outputs out-timestamps)]
[result: (if (<= in-max out-min) out-max +inf.0)]]))
(if (<= in-max out-min) out-max +inf.0))
(def (compute-new-timestamp target)
(def spec (vector-ref spec@ target))
(def outputs (spec-outputs spec settings))
(for-each (lambda (x) (cache-remove! ['modification-time x])) outputs) ; clear stale pre-build timestamps
(def out-timestamp (xmax/map file-timestamp outputs))
(unless (< out-timestamp +inf.0)
(when verbose
(writeln [compute-timestamp-failed: spec (map (lambda (x) [x (file-timestamp x)]) outputs)]))
(error "Build failed to generate expected outputs" spec outputs))
out-timestamp)
(def (target<-item item) (if (<= 0 item) item (bitwise-not item)))
(def (perform target)
(if (<= 0 target) (fg-perform target) (bg-perform target)))
(def (fg-perform target)
(def spec (vector-ref spec@ target))
(def id (vector-ref id@ target))
(when (verbose>=? 7) (writeln [Perform: target spec id (hash-get timestamp% id)]))
(cond
((and (not force?)
(< (hash-ensure-ref timestamp% id (cut compute-timestamp target)) +inf.0))
(when (verbose>=? 5) (writeln [Up-to-date: target spec (hash-ref timestamp% id)]))
(hash-ref timestamp% id))
((spec-backgroundable? spec)
(pqueue-push! ready-bg (bitwise-not target))
+inf.0)
(else
(build-target target))))
(def (build-target target)
(build (vector-ref spec@ target) settings)
(compute-new-timestamp target))
(def (bg-perform target)
(build-target (bitwise-not target)))
(def (on-success item timestamp)
(def target (target<-item item))
(def spec (vector-ref spec@ target))
(def id (vector-ref id@ target))
(when (verbose>=? 7) (writeln [on-success: item target id spec timestamp]))
(when (< timestamp +inf.0)
(hash-put! timestamp% id timestamp)
(mark-built target)))
(def (on-failure item exception)
(raise (build-failure (vector-ref spec@ (target<-item item)) exception)))
(create-directory* (settings-bindir settings))
(create-directory* (settings-libdir settings))
(create-directory* (path-expand "static" (settings-libdir settings)))
(init-walk)
(when (verbose>=? 6) (writeln [Step: 5. queue: (sort (pqueue-contents ready-fg) <)]))
(perform-plan/threads
ready-fg ready-bg
perform: perform on-success: on-success on-failure: on-failure
announce: (if (verbose>=? 8)
(lambda (item)
(def target (target<-item item))
(writeln [announcing: item (vector-ref spec@ target)]))
void)
deterministic-order: #f max-workers: (max (settings-parallelize settings) 1))
(when (verbose>=? 3) (writeln [Step: 5 "All built"])))
(def (make-clean . args)
(defvalues (positionals keywords) (separate-keyword-arguments args #t))
(def buildspec (match positionals ([x] x) (_ (error "invalid arguments" make positionals))))
(def settings (apply make-settings keywords))
(for-each
(lambda (spec)
(for-each
(lambda (f)
(when (file-exists? f)
(displayln "... remove " f)
(delete-file-or-directory f)))
(spec-outputs spec settings)))
buildspec))
(defstruct build-failure (item exception))
(defmethod {display-exception build-failure}
(lambda (self port)
(display "Build Failure at " port)
(display (build-failure-item self) port)
(newline port)
(display-exception (build-failure-exception self) port)))
;; file-dependencies : file -> dependencies
(def (file-dependencies file settings)
(def ht (make-hash-table-eq)) ; dependencies found so far, so we only find them once
(def q (make-queue)) ; queue of objects to unwrap to find the dependencies
(def mod ; load the source file with srcdir in the load-path for dependencies, extract the module
(with-cons-load-path (cut import-module file) (settings-srcdir settings)))
(def mod-id (expander-context-id mod)) ; id of the module we're interested in
(def (consider m)
(alet (id (expander-context-id m)) ; maybe it's root (#f), then stop.
(let (module-id (module-strip-nesting id))
(if (eq? module-id mod-id)
(for-each (cut enqueue! q <>)
(cons (core-context-prelude m)
(module-context-import m)))
(hash-put! ht module-id #t)))))
(consider mod) ; start from the current module
(until (queue-empty? q)
(let ((hd (dequeue! q #f)))
(cond
((module-context? hd) (consider hd))
((prelude-context? hd) (consider hd))
((module-import? hd) (enqueue! q (module-import-source hd)))
((module-export? hd) (enqueue! q (module-export-context hd)))
((import-set? hd) (enqueue! q (import-set-source hd)))
(else (error "Unexpected module import" hd)))))
(values mod-id (sort (hash-keys ht) symbol<?)))
;; Normalize-buildspec : buildspec -> buildspec
;; Groups the gsc: and static-include: and copy: specs inside the immediately following ssi:
(def (normalize-buildspec buildspec)
(def submodules '())
(def (get-submodules)
(begin0 (reverse submodules) (set! submodules '())))
(def (no-submodules spec)
(unless (null? submodules)
(error "incompatible sequence of build specifications" (reverse (cons spec submodules)))))
(def (push-submodule spec)
(set! submodules (cons spec submodules)))
(with-list-builder (c)
((cut for-each <> buildspec)
(lambda (spec)
(case (spec-type spec)
((gxc:) (let (s (get-submodules))
(if (null? s) (c spec)
(c
(match spec
([gxc: name (? pair? plist) . args]
[gxc: name (psetq plist submodules: (append (pgetq plist submodules:) s))])
([gxc: name . args]
[gxc: name [submodules: s] . args])
((? string? spec)
[gxc: spec [submodules: s]]))))))
((exe: static-exe: optimized-exe: optimized-static-exe:) (no-submodules spec) (c spec))
((gsc: static-include: copy:) (push-submodule spec))
((ssi:) (c (append spec (get-submodules))))
(else (error "Unrecognized spec type" spec)))))
(no-submodules #!eof)))
(def (shell-config cmd . args)
(let* ((proc (open-input-process [path: cmd arguments: args]))
(status (process-status proc)))
(unless (zero? status)
(error "Error executing config script" cmd args status))
(read-line proc)))
(def (env-ldflags)
(cond
((getenv "LDFLAGS" #f)
=> (lambda (flags)
(lambda (lib)
(string-append flags " " lib))))
(else
identity)))
(def (env-cppflags)
(cond
((getenv "CPPFLAGS" #f)
=> (lambda (flags)
(lambda (more)
(if (string-empty? more)
flags
(string-append flags " " more)))))
(else
identity)))
(def (include-gambit-sharp)
(let* ((gambit-sharp
(path-expand "lib/_gambit#.scm"
(getenv "GERBIL_BUILD_PREFIX" (gerbil-home))))
(include-gambit-sharp
(string-append "(include \"" gambit-sharp "\")")))
(cond
((gerbil-runtime-smp?)
`("-e" "(define-cond-expand-feature|enable-smp|)"
"-e" ,include-gambit-sharp))
(else
`("-e" ,include-gambit-sharp)))))
(def (build spec settings)
(match spec
((? string? modf)
(gxc-compile modf #f settings #t))
([gxc: modf [submodules: submodules . _] . opts]
(for-each (cut build <> settings) submodules)
(gxc-compile modf opts settings #t))
([gxc: modf . opts]
(gxc-compile modf opts settings #t))
([gsc: modf . opts]
(gsc-compile modf opts settings))
([ssi: modf . submodules]
(for-each (cut build <> settings) submodules)
(compile-ssi modf '() settings))
([exe: modf . opts]
(compile-exe/context modf opts settings))
([static-exe: modf . opts]
(compile-exe/static-linkage modf opts settings))
([optimized-exe: modf . opts]
(compile-optimized-exe modf opts settings))
([optimized-static-exe: modf . opts]
(compile-optimized-exe/static-linkage modf opts settings))
([static-include: file]
(copy-target file settings)
(copy-static file settings))
([copy: file]
(copy-compiled file settings))
(else
(error "Bad buildspec" spec))))
(def (gxc-outputs mod opts settings)
[(library-path mod ".ssi" settings)
(static-path mod settings)])
(def (gsc-compile-opts opts)
(match opts
([[plist ...] . rest] (listify rest))
(_ (listify opts))))
;; TODO: split that (and more?) into many action. See :gerbil/compiler/driver
(def (gxc-compile mod opts settings (invoke-gsc? #t))
(message "... compile " mod)
(def foreground? (and (pair? opts) (pair? (car opts)) (pgetq foreground: (car opts))))
(def gsc-opts (gsc-compile-opts opts))
(def srcpath (source-path mod ".ss" settings))
(if (or foreground? (> 1 (settings-parallelize settings)))
(let ((gxc-opts
[invoke-gsc: invoke-gsc?
keep-scm: (not invoke-gsc?)
output-dir: (settings-libdir settings)
optimize: (settings-optimize settings)
debug: (settings-debug settings)
generate-ssxi: #t
verbose: (settings-verbose>=? settings 9)
(when/list gsc-opts [gsc-options: gsc-opts]) ...]))
(compile-module srcpath gxc-opts))
(let* ((arguments
["-d" (settings-libdir settings)
(when/list (not invoke-gsc?) ["-s" "-S"]) ...
(when/list (settings-optimize settings) ["-O"]) ...
(when/list (settings-debug settings) ["-g"]) ...
(when/list (settings-verbose>=? settings 9) ["-v"]) ...
(when/list gsc-opts (append-map (lambda (x) ["-gsc-flag" x]) gsc-opts)) ...
srcpath])
(__ (when (settings-verbose>=? settings 7) (writeln [invoking: (gerbil-gxc) arguments ...])))
(proc (open-process [path: (gerbil-gxc)
arguments: arguments
stdout-redirection: #f]))
(status (process-status proc)))
(close-port proc)
(unless (zero? status)
(error "Compilation error; gxc exited with nonzero status" status)))))
(def (gsc-libpath mod settings)
(def libdir-prefix (settings-libdir-prefix settings))
(cond
((string-rindex mod #\/) => (lambda (ix) (path-expand (substring mod 0 ix) libdir-prefix)))
(else libdir-prefix)))
(def (gsc-base mod)
(cond
((string-rindex mod #\/)
=> (lambda (ix) (substring mod (fx1+ ix) (string-length mod))))
(else mod)))
(def (gsc-c-path modf settings)
(def mod (path-strip-extension modf))
(def libpath (gsc-libpath mod settings))
(def base (gsc-base mod))
(let lp ((n 1) (cpath #f))
(let (next (path-expand (string-append base ".o" (number->string n))
libpath))
(if (file-exists? next)
(lp (fx1+ n) next)
cpath))))
(def (gsc-compile mod opts settings)
(def gsc-opts (gsc-compile-opts opts))
(def srcpath (source-path mod ".scm" settings))
(def libpath (gsc-libpath mod settings))
(create-directory* libpath)
(message "... compile foreign " mod)
(let* ((proc (open-process [path: (gerbil-gsc)
arguments: ["-o" libpath gsc-opts ... srcpath]
stdout-redirection: #f]))
(status (process-status proc)))
(close-port proc)
(unless (zero? status)
(error "Compilation error; gsc exited with nonzero status" status)))
;; just copy to libdir/static/ with properly mangled module name
(let (statpath (static-path mod settings))
(when (file-exists? statpath)
(delete-file statpath))
(copy-file srcpath statpath)))
(def (gerbil-gsc)
(getenv "GERBIL_GSC" default-gambit-gsc))
(def (gerbil-gxc)
(getenv "GERBIL_GXC" default-gerbil-gxc))
(def (compile-ssi mod deps settings)
(def srcpath (source-path mod ".ssi" settings))
(def libpath (library-path mod ".ssi" settings))
(def rtpath (library-path mod "__rt.scm" settings))
(def prefix (settings-prefix settings))
(message "... copy ssi " mod)
(create-directory* (path-directory libpath))
(when (file-exists? libpath)
(delete-file libpath))
(copy-file srcpath libpath)
(message "... compile loader " mod)
(with-output-to-file rtpath
(lambda ()
(for-each (lambda (dep) (pretty-print `(load-module ,dep))) deps)
(pretty-print `(load-module ,(prefix/ prefix mod)))))
(let* ((proc (open-process [path: (gerbil-gsc)
arguments: [rtpath]
stdout-redirection: #f]))
(status (process-status proc)))
(close-port proc)
(unless (zero? status)
(error "Compilation error; gsc exited with nonzero status" status))
(delete-file rtpath)))
(def (compile-exe-gsc-opts opts)
(match opts
([(? keyword?) opt . rest]
(compile-exe-gsc-opts rest))
(else opts)))
(def (compile-exe/context mod opts settings)
(cond
((or (settings-build-release settings)
(getenv "GERBIL_BUILD_RELEASE" #f))
(cond
((or (settings-build-optimized settings)
(getenv "GERBIL_BUILD_OPTIMIZED" #f))
(compile-optimized-exe/static-linkage mod opts settings))
(else
(compile-exe/static-linkage mod opts settings))))
((or (settings-build-optimized settings)
(getenv "GERBIL_BUILD_OPTIMIZED" #f))
(compile-optimized-exe mod opts settings))
(else
(compile-exe mod opts settings))))
(def (compile-exe mod opts settings)
(def srcpath (source-path mod ".ss" settings))
(def binpath (binary-path mod opts settings))
(def gsc-opts (compile-exe-gsc-opts opts))
(def gxc-opts
[invoke-gsc: #t
output-file: binpath
verbose: (settings-verbose>=? settings 9)
debug: (settings-debug settings)
full-program-optimization: (settings-full-program-optimization settings)
(when/list gsc-opts [gsc-options: gsc-opts]) ...])
(gxc-compile mod gsc-opts settings #f)
(message "... compile exe " mod " -> " binpath)
(gxc#compile-exe srcpath gxc-opts))
(def (compile/static-linkage mod opts settings compile-e)
(when (member "--enable-shared" (string-split (configure-command-string) #\'))
(error "system is configured with --enable-shared and cannot build static executables"))
(let (static-opts (append opts '("-cc-options" "-Bstatic" "-ld-options" "-static")))
(compile-e mod static-opts settings)))
(def (compile-exe/static-linkage mod opts settings)
(compile/static-linkage mod opts settings compile-exe))
(def (make-settings-optimized settings)
(def s (struct-copy settings))
(set! (settings-full-program-optimization s) #t)
s)
(def (compile-optimized-exe mod opts settings)
(compile-exe mod opts (make-settings-optimized settings)))
(def (compile-optimized-exe/static-linkage mod opts settings)
(compile/static-linkage mod opts settings compile-optimized-exe))
(def (copy-static file settings)
(def spath (static-file-path file settings))
(message "... copy static include " file)
(when (file-exists? spath)
(delete-file spath))
(copy-file file spath))
(def (copy-target file settings)
(let* ((libdir (settings-libdir-prefix settings))
(srcdir (settings-srcdir settings))
(spath (path-expand file srcdir))
(tpath (path-expand file libdir)))
(message "... copy static include to target directory " file)
(create-directory* (path-directory tpath))
(when (file-exists? tpath)
(delete-file tpath))
(copy-file spath tpath)))
(def (copy-compiled file settings)
(def srcpath (source-path file #f settings))
(def libpath (library-path file #f settings))
(message "... copy std/" file)
(when (file-exists? libpath)
(delete-file libpath))
(copy-file srcpath libpath))
(def (source-path mod ext settings)
(path-expand (path-default-extension mod ext) (settings-srcdir settings)))
(def (library-path mod ext settings)
(path-expand (path-force-extension mod ext) (settings-libdir-prefix settings)))
(def (binary-path mod opts settings)
(let* ((bindir (settings-bindir settings))
(_ (unless bindir (error "bindir must be specified")))
(bin
(cond
((pgetq bin: opts) => values)
((string-rindex mod #\/)
=> (lambda (ix) (substring mod (fx1+ ix) (string-length mod))))
(else mod)))
(bin
(if (string-empty? (path-extension bin))
bin
(path-strip-extension bin))))
(path-expand bin bindir)))
(def (static-path mod settings)
(let* ((libdir (settings-libdir settings))
(staticdir (path-expand "static" libdir))
(mod (prefix/ (settings-prefix settings) mod))
(base (string-join (string-split mod #\/) "__"))
(scm (path-force-extension base ".scm")))
(path-expand scm staticdir)))
(def (static-file-path file settings)
(let* ((libdir (settings-libdir settings))
(staticdir (path-expand "static" libdir))
(filename (path-strip-directory file)))
(path-expand filename staticdir)))
;; lib: library to ask configuration for
;; args: [<arg> ...]
;; <arg>: additionnal arguments to pass to pkg-config
(def (pkg-config lib . args)
(apply shell-config "pkg-config" "--silence-errors" lib args))
;; calls pkg-config in order to return `ld-options`.
;;
;; libs: [<lib> ...]
;; <lib>: library to get ld-options for
(def (pkg-config-libs . libs)
(string-join (map (lambda (l) (pkg-config l "--libs")) libs) " "))
;; calls pkg-config in order to return `cc-options`.
;;
;; libs: [<lib> ...]
;; <lib>: library to get cc-options for
(def (pkg-config-cflags . libs)
(string-join (map (lambda (l) (pkg-config l "--cflags")) libs) " "))
;; tries pkg-config-libs with fallback to env-ldflags
(def (ldflags lib flags)
(try
(pkg-config-libs lib)
(catch (e)
((env-ldflags) flags))))
;; tries pkg-confg-cflags with fallback to env-cppflags
(def (cppflags lib flags)
(try
(pkg-config-cflags lib)
(catch (e)
((env-cppflags) flags))))
;; appends flags together; useful for -cc-options and -ld-options
(def (append-options . opts)
(let (opts (filter (? (not string-empty?)) opts))
(string-join opts " ")))