2025-04-29 15:04:43 +00:00
|
|
|
(define-library (zilch lang ninja build)
|
|
|
|
|
(import
|
|
|
|
|
(scheme base) (scheme lazy)
|
|
|
|
|
(zilch file) (zilch magic) (scheme char)
|
|
|
|
|
(zilch nix drv) (zilch nix path)
|
|
|
|
|
(zilch nixpkgs) (zilch zexpr) (zilch vfs)
|
2025-05-01 13:20:05 +00:00
|
|
|
(chicken format)
|
2025-04-29 15:04:43 +00:00
|
|
|
(srfi 128) (srfi 146) (srfi 152)
|
2025-05-11 22:21:07 +00:00
|
|
|
(zilch lang ninja) (zilch lang ninja config))
|
2025-04-29 15:04:43 +00:00
|
|
|
|
|
|
|
|
(export process-ninja-file)
|
|
|
|
|
|
|
|
|
|
(begin
|
|
|
|
|
(define coreutils (cdr (assoc "out" (nixpkgs "coreutils"))))
|
|
|
|
|
|
|
|
|
|
;; normalize a POSIX-y path. Ninja doesn't have an internal concept of path normalisation,
|
|
|
|
|
;; so this is necessary for proper file-finding behavior.
|
|
|
|
|
(define (normalize-path path)
|
|
|
|
|
(define parts (string-split path "/"))
|
|
|
|
|
(define part-stack '())
|
|
|
|
|
(for-each
|
|
|
|
|
(lambda (part)
|
|
|
|
|
(cond
|
|
|
|
|
((string=? part "") (when (null? part-stack) (set! part-stack '(""))))
|
2025-05-11 22:21:07 +00:00
|
|
|
((string=? part "..") (if (or (null? part-stack) (string=? (car part-stack) "") (string=? (car part-stack) "..")) (set! part-stack (cons ".." part-stack)) (set! part-stack (cdr part-stack))))
|
2025-04-29 15:04:43 +00:00
|
|
|
((string=? part "."))
|
|
|
|
|
(else (set! part-stack (cons part part-stack)))))
|
|
|
|
|
parts)
|
|
|
|
|
(string-join (reverse part-stack) "/"))
|
|
|
|
|
|
|
|
|
|
(define (is-valid-store-path-char c)
|
|
|
|
|
(or
|
|
|
|
|
(and (char>=? c #\0) (char<=? c #\9))
|
|
|
|
|
(and (char>=? c #\a) (char<=? c #\z))
|
|
|
|
|
(and (char>=? c #\A) (char<=? c #\Z))
|
|
|
|
|
(member c '(#\+ #\- #\. #\_ #\? #\=))))
|
|
|
|
|
|
|
|
|
|
;; Helper to render nicer derivation names.
|
|
|
|
|
(define (make-valid-store-path-string str)
|
2025-05-01 13:20:05 +00:00
|
|
|
(if (string=? "" str)
|
|
|
|
|
"zilch-ninja"
|
|
|
|
|
(string-map (lambda (c) (if (is-valid-store-path-char c) c #\-)) (if (> (string-length str) 211) (string-copy str 0 211) str))))
|
2025-04-29 15:04:43 +00:00
|
|
|
|
|
|
|
|
|
2025-05-11 22:21:07 +00:00
|
|
|
(define (derivation-for-edge conf vfs-store-path relative-to-root edges current-edge)
|
2025-04-29 15:04:43 +00:00
|
|
|
(define resolved (build-edge-resolved current-edge))
|
|
|
|
|
(when (build-rule-rspfile resolved) (error "rspfile not yet supported" current-edge))
|
2025-05-01 13:20:05 +00:00
|
|
|
(define copy-input-files "")
|
2025-04-29 15:04:43 +00:00
|
|
|
(define (append-file path)
|
|
|
|
|
(define input-file (mapping-ref edges path (lambda () (mapping-ref/default edges (normalize-path path) #f))))
|
|
|
|
|
|
2025-05-09 13:09:49 +00:00
|
|
|
(cond
|
|
|
|
|
; if input-file is 'base, this is part of the base vfs; we don't filter that right now.
|
|
|
|
|
((eq? input-file 'base) #f)
|
|
|
|
|
|
|
|
|
|
; Phony rule; pass through the inputs literally.
|
2025-05-11 22:21:07 +00:00
|
|
|
((and (pair? input-file) (eq? (car input-file) 'phony)) (for-each append-file (cddr input-file)))
|
2025-05-09 13:09:49 +00:00
|
|
|
|
|
|
|
|
; This file is produced by another build edge. Add it to our input vfs.
|
|
|
|
|
(input-file
|
2025-05-01 13:20:05 +00:00
|
|
|
(let ((prev-copy-input-files copy-input-files))
|
2025-05-09 13:09:49 +00:00
|
|
|
(set! copy-input-files #~,(string-append #$prev-copy-input-files "\n" "$COREUTILS/mkdir -p bdir/" relative-to-root "/$($COREUTILS/dirname " path "); $COREUTILS/cp -rf " #$(force input-file) " bdir/" relative-to-root "/" path))))
|
|
|
|
|
|
|
|
|
|
(else
|
2025-05-01 13:20:05 +00:00
|
|
|
(unless (string-prefix? "/nix/store" path)
|
|
|
|
|
(error "Path doesn't exist as build edge" path))))
|
2025-04-29 15:04:43 +00:00
|
|
|
|
2025-05-01 13:20:05 +00:00
|
|
|
; Workaround for Meson not adding the .so as build dependency when linking, instead using a .symbols file.
|
|
|
|
|
; This makes sense, as it only relinks when symbols change, but it breaks the dependency link, and is the only
|
|
|
|
|
; place this happens in Ninja file processing.
|
|
|
|
|
; TODO: how does nix-ninja handle this?
|
|
|
|
|
(when (string-suffix? ".so.symbols" path)
|
|
|
|
|
(let ((index (string-contains path ".p/")))
|
|
|
|
|
(append-file (string-copy path 0 index)))))
|
2025-05-09 13:09:49 +00:00
|
|
|
|
2025-04-29 15:04:43 +00:00
|
|
|
; Add the inputs, implicit dependencies, _and_ order-only dependencies to our vfs.
|
|
|
|
|
(for-each append-file (build-edge-inputs current-edge))
|
|
|
|
|
(for-each append-file (build-edge-implicit-dependencies current-edge))
|
|
|
|
|
(for-each append-file (build-edge-order-only-dependencies current-edge))
|
|
|
|
|
|
2025-05-01 13:20:05 +00:00
|
|
|
; Create parent directories for each output (TODO: implicit outputs?)
|
|
|
|
|
(for-each
|
|
|
|
|
(lambda (path)
|
|
|
|
|
(define prev-copy-input-files copy-input-files)
|
|
|
|
|
(set! copy-input-files #~,(string-append #$prev-copy-input-files "\n" "$COREUTILS/mkdir -p bdir/" relative-to-root "/$($COREUTILS/dirname " path ")")))
|
|
|
|
|
(build-edge-outputs current-edge))
|
|
|
|
|
|
2025-04-29 15:04:43 +00:00
|
|
|
; Create the VFS.
|
|
|
|
|
(define command-to-run (build-rule-command resolved))
|
|
|
|
|
|
|
|
|
|
; For each output and implicit output, copy them to the derivation's output.
|
|
|
|
|
(define copy-output-files "")
|
|
|
|
|
(define out-placeholder (make-placeholder "out"))
|
|
|
|
|
(define (append-copy-command outpath)
|
|
|
|
|
(set! copy-output-files (string-append copy-output-files "\n" "$COREUTILS/mkdir -p " out-placeholder "/$($COREUTILS/dirname " outpath "); $COREUTILS/cp -rf " outpath " " out-placeholder "/" outpath)))
|
|
|
|
|
(for-each append-copy-command (build-edge-outputs current-edge))
|
|
|
|
|
(for-each append-copy-command (build-edge-implicit-outputs current-edge))
|
|
|
|
|
|
2025-05-11 22:21:07 +00:00
|
|
|
(define patch-commands (list))
|
|
|
|
|
(for-each
|
|
|
|
|
(lambda (patch)
|
|
|
|
|
(define result (patch current-edge))
|
|
|
|
|
(when result
|
|
|
|
|
(set! patch-commands (cons result patch-commands))))
|
|
|
|
|
(ninja-build-config-patches conf))
|
|
|
|
|
(set! patch-commands (reverse patch-commands))
|
|
|
|
|
(define processed-patch-commands
|
|
|
|
|
#~,(string-join
|
|
|
|
|
(map
|
|
|
|
|
(lambda (item)
|
|
|
|
|
(if (string? item) item
|
|
|
|
|
(string-join item "\n")))
|
|
|
|
|
#$patch-commands)
|
|
|
|
|
"\n"))
|
|
|
|
|
|
2025-04-29 15:04:43 +00:00
|
|
|
; Run the build rule inside the build environment's vfs. This requires copying the entire VFS over, sadly.
|
|
|
|
|
(define command
|
|
|
|
|
#~,(string-append
|
2025-05-11 22:21:07 +00:00
|
|
|
; Copy over the VFS, as we have to make changes to it.
|
2025-05-01 13:20:05 +00:00
|
|
|
#$coreutils "/bin/cp -rf --no-preserve=ownership " #$vfs-store-path " bdir\n"
|
|
|
|
|
#$coreutils "/bin/chmod ugo+rw -R bdir\n"
|
2025-05-11 22:21:07 +00:00
|
|
|
|
|
|
|
|
; Copy over the other input files.
|
2025-05-01 13:20:05 +00:00
|
|
|
"(COREUTILS=" #$coreutils "/bin; " #$copy-input-files ")\n"
|
2025-05-11 22:21:07 +00:00
|
|
|
#$coreutils "/bin/chmod ugo+rw -R bdir\n"
|
|
|
|
|
|
|
|
|
|
; Run any patches we have received.
|
|
|
|
|
"(cd bdir; _ZILCH_ROOT=" #$vfs-store-path "; PATH=\"" #$coreutils "/bin:$PATH\"; " #$processed-patch-commands ")\n"
|
|
|
|
|
|
|
|
|
|
; Enter the build dir and then run the command for this build.
|
|
|
|
|
"(cd bdir/" relative-to-root "; " command-to-run ") || exit 1\n"
|
|
|
|
|
|
|
|
|
|
; Create the output directory, and copyy over the output files.
|
|
|
|
|
"(COREUTILS=" #$coreutils"/bin; cd bdir/" relative-to-root "; $COREUTILS/mkdir " out-placeholder "\n" #$copy-output-files ")"))
|
2025-04-29 15:04:43 +00:00
|
|
|
|
|
|
|
|
; Create the derivation. The rest of the code, that builds up the full list of edges, will extract each output from it.
|
|
|
|
|
(define outpath
|
|
|
|
|
(cdar
|
|
|
|
|
(store-path-for-ca-drv*
|
|
|
|
|
(make-valid-store-path-string (build-rule-description resolved))
|
|
|
|
|
"x86_64-linux"
|
|
|
|
|
(list "/bin/sh" "-c" command)
|
2025-05-11 22:21:07 +00:00
|
|
|
(ninja-build-config-environment conf)
|
2025-04-29 15:04:43 +00:00
|
|
|
'("out"))))
|
|
|
|
|
outpath)
|
|
|
|
|
|
2025-05-11 22:21:07 +00:00
|
|
|
(define (phony-edge edges current-edge)
|
|
|
|
|
(define copies (list))
|
|
|
|
|
(define (process-input path)
|
|
|
|
|
(define input-file (mapping-ref edges path (lambda () (mapping-ref/default edges (normalize-path path) #f))))
|
|
|
|
|
(if (and (pair? input-file) (eq? (car input-file) 'phony))
|
|
|
|
|
(for-each process-input (cddr input-file))
|
|
|
|
|
(set! copies (cons #~,(string-append "mkdir -p \"$out/$(dirname " path ")\"; cp -rf " #$(force input-file) " $out/" path) copies))))
|
|
|
|
|
(for-each process-input (build-edge-inputs current-edge))
|
|
|
|
|
|
|
|
|
|
(define command
|
|
|
|
|
#~,(string-join #$copies "\n"))
|
|
|
|
|
|
|
|
|
|
(cdar
|
|
|
|
|
(store-path-for-ca-drv*
|
|
|
|
|
"zilch-ninja-phony-edge"
|
|
|
|
|
"x86_64-linux"
|
|
|
|
|
(list "/bin/sh" "-c" command)
|
|
|
|
|
#~(("PATH" . ,(string-append #$coreutils "/bin")))
|
|
|
|
|
'("out"))))
|
|
|
|
|
|
|
|
|
|
|
2025-04-29 15:04:43 +00:00
|
|
|
;; process a ninja file and corresponding vfs, and return two values:
|
|
|
|
|
;; - `edge-ref`, a lambda that lets one fetch any build edge;
|
|
|
|
|
;; - `defaults`, a list containing the default build edges.
|
|
|
|
|
;;
|
|
|
|
|
;; If the `environment` is a <derivation> or a <store-path>, it is considered
|
|
|
|
|
;; to be a nixpkgs-style derivation, the same way `nix-shell` works.
|
2025-05-11 22:21:07 +00:00
|
|
|
(define (process-ninja-file file conf relative-to)
|
2025-05-09 13:09:49 +00:00
|
|
|
(unless (or (string=? relative-to "") (string-suffix? "/" relative-to)) (set! relative-to (string-append relative-to "/")))
|
2025-04-29 15:04:43 +00:00
|
|
|
|
|
|
|
|
(define edges (mapping (make-default-comparator)))
|
|
|
|
|
|
|
|
|
|
; record edges for each path in the base vfs.
|
|
|
|
|
(mapping-for-each
|
|
|
|
|
(lambda (kv path)
|
|
|
|
|
(define path (if (string=? (car kv) "") (cdr kv) (string-append (car kv) "/" (cdr kv))))
|
2025-05-01 13:20:05 +00:00
|
|
|
(cond
|
|
|
|
|
((string=? relative-to "") #f)
|
|
|
|
|
((string-prefix? relative-to path) (set! path (string-copy path (string-length relative-to))))
|
|
|
|
|
(else (set! path (string-append "../" path))))
|
2025-04-29 15:04:43 +00:00
|
|
|
(set! edges (mapping-set! edges path 'base)))
|
2025-05-11 22:21:07 +00:00
|
|
|
(vfs-contents (ninja-build-config-root-dir conf)))
|
|
|
|
|
(define vfs-store-path (vfs-to-store (ninja-build-config-root-dir conf)))
|
2025-04-29 15:04:43 +00:00
|
|
|
(for-each
|
|
|
|
|
(lambda (edge)
|
2025-05-11 22:21:07 +00:00
|
|
|
(define processed (delay (derivation-for-edge conf vfs-store-path relative-to edges edge)))
|
2025-05-11 22:21:07 +00:00
|
|
|
(define all-outputs (append (build-edge-outputs edge) (build-edge-implicit-outputs edge)))
|
2025-05-09 13:09:49 +00:00
|
|
|
(if (build-edge-resolved edge)
|
|
|
|
|
(for-each (lambda (v) (set! edges (mapping-set! edges v (delay #~,(string-append #$(force processed) "/" v)))))
|
2025-05-11 22:21:07 +00:00
|
|
|
all-outputs)
|
2025-05-09 13:09:49 +00:00
|
|
|
|
|
|
|
|
; This edge is phony; mark the edge as having its outputs.
|
2025-05-11 22:21:07 +00:00
|
|
|
(for-each (lambda (v) (set! edges (mapping-set! edges v (cons 'phony (cons edge (build-edge-inputs edge))))))
|
|
|
|
|
all-outputs)))
|
2025-04-29 15:04:43 +00:00
|
|
|
(build-file-build-edges file))
|
|
|
|
|
|
2025-05-11 22:21:07 +00:00
|
|
|
(define edge-ref
|
|
|
|
|
(lambda (path)
|
|
|
|
|
(define edge (force (mapping-ref/default edges path #f)))
|
|
|
|
|
(if (and (pair? edge) (eq? (car edge) 'phony))
|
|
|
|
|
(phony-edge edges (cadr edge))
|
|
|
|
|
edge)))
|
2025-04-29 15:04:43 +00:00
|
|
|
(define defaults (build-file-default-targets file))
|
|
|
|
|
(values edge-ref defaults))))
|