(zilch lang ninja): add ninja build file parser
This commit is contained in:
parent
0dc3c7e154
commit
40d213520a
4 changed files with 438 additions and 0 deletions
16
lang/ninja/default.nix
Normal file
16
lang/ninja/default.nix
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
{ chickenPackages, libsodium, callPackage, xxd, yj }:
|
||||
(callPackage ../../lib/build-chicken-parallel {}) {
|
||||
name = "zilch-lang-ninja";
|
||||
src = ./.;
|
||||
|
||||
buildInputs = with chickenPackages.chickenEggs; [
|
||||
chickenPackages.chicken
|
||||
r7rs
|
||||
json
|
||||
srfi-152
|
||||
srfi-113
|
||||
srfi-207
|
||||
(callPackage ../../core {})
|
||||
(callPackage ../../planner {})
|
||||
];
|
||||
}
|
||||
412
lang/ninja/src/ninja.sld
Normal file
412
lang/ninja/src/ninja.sld
Normal file
|
|
@ -0,0 +1,412 @@
|
|||
(define-library (zilch lang ninja)
|
||||
(import
|
||||
(scheme base) (scheme write) (scheme process-context) (scheme lazy)
|
||||
(zilch file) (zilch magic) (zilch nix drv) (zilch nix path) (scheme char)
|
||||
(zilch nixpkgs) (zilch zexpr) (zilch semver)
|
||||
json
|
||||
(chicken process)
|
||||
(chicken base) (chicken format)
|
||||
(chicken foreign)
|
||||
(srfi 4) (srfi 128) (srfi 146) (srfi 152) (srfi 207))
|
||||
|
||||
(export
|
||||
<build-file> make-build-file build-file?
|
||||
build-file-global-variables build-file-default-targets build-file-rules build-file-build-edges
|
||||
build-file-pools
|
||||
|
||||
<build-rule> make-build-rule build-rule?
|
||||
build-rule-name build-rule-command build-rule-depfile build-rule-deps build-rule-description
|
||||
build-rule-restat build-rule-rspfile build-rule-rspfile-content
|
||||
|
||||
<build-edge> make-build-edge build-edge?
|
||||
build-edge-rule build-edge-outputs build-edge-inputs
|
||||
build-edge-implicit-dependencies build-edge-order-only-dependencies build-edge-validations
|
||||
build-edge-implicit-outputs build-edge-variables build-edge-resolved
|
||||
|
||||
read-ninja-file)
|
||||
|
||||
(begin
|
||||
;; Write a properly escaped string to the provided port.
|
||||
(define (write-variable-string port var as-path)
|
||||
(string-for-each
|
||||
(lambda (c)
|
||||
(cond
|
||||
((and as-path (member c '(#\space #\: #\$))) (write-char #\$ port))
|
||||
((char=? c #\newline) (write-char #\$ port)))
|
||||
(write-char c port)
|
||||
(when (char=? c #\newline) (write-string " " port)))
|
||||
var))
|
||||
|
||||
;; Writes a variable, prefixed with $ and curly-bracketed, to a port.
|
||||
(define (write-variable port var)
|
||||
(write-string (string-append "${" var "}") port))
|
||||
|
||||
;; Returns a string containing the contents of the evalstring, properly escaped.
|
||||
(define (render-evalstring var as-path)
|
||||
(define out (open-output-string))
|
||||
(for-each
|
||||
(lambda (chunk)
|
||||
(cond
|
||||
((string? chunk) (write-variable-string out chunk as-path))
|
||||
((pair? chunk) (write-variable out (cdr chunk))))) (if (string? var) (list var) var))
|
||||
(define str (get-output-string out))
|
||||
(close-output-port out)
|
||||
str)
|
||||
|
||||
(define (is-valid-varname-char ch simple)
|
||||
(or
|
||||
(and (char>=? ch #\a) (char<=? ch #\z))
|
||||
(and (char>=? ch #\A) (char<=? ch #\Z))
|
||||
(and (char>=? ch #\0) (char<=? ch #\9))
|
||||
(char=? ch #\_) (char=? ch #\-)
|
||||
(and (not simple) (char=? ch #\.))))
|
||||
|
||||
(define (is-valid-eval-string-char ch path)
|
||||
(not
|
||||
(or
|
||||
(char=? ch #\null)
|
||||
(char=? ch #\newline)
|
||||
(char=? ch #\return)
|
||||
(char=? ch #\$)
|
||||
(and path
|
||||
(or
|
||||
(char=? ch #\space)
|
||||
(char=? ch #\:)
|
||||
(char=? ch #\|))))))
|
||||
|
||||
(define (resolve-evalstring evalstr file rule edge is-command)
|
||||
(define (find-value val)
|
||||
(define res #f)
|
||||
(when edge
|
||||
(cond
|
||||
((string=? val "in") (set! res (string-join (build-edge-inputs edge))))
|
||||
((string=? val "in_newline") (set! res (string-join (build-edge-inputs edge) "\n")))
|
||||
((string=? val "out") (set! res (string-join (build-edge-outputs edge))))
|
||||
(else (set! res (mapping-ref/default (build-edge-variables edge) val #f)))))
|
||||
(when rule
|
||||
(cond
|
||||
((member val '("command" "depfile" "deps" "msvc_deps_prefix" "description" "dyndep" "generator" "restat" "rspfile" "rspfile_content") string=?) (error "ambiguity in how to process build rules depending on build rules!"))))
|
||||
(unless res
|
||||
(set! res
|
||||
(mapping-ref/default (build-file-global-variables file) val "")))
|
||||
res)
|
||||
(apply string-append
|
||||
(map
|
||||
(lambda (part)
|
||||
(if (string? part)
|
||||
part
|
||||
(find-value (cdr part))))
|
||||
evalstr)))
|
||||
|
||||
(define (pr out name value) (unless (eq? value #f) (fprintf out " ~A = ~A\n" name (render-evalstring value #f))))
|
||||
|
||||
;; Represents a Ninja build file.
|
||||
(define-record-type <build-file>
|
||||
(make-build-file global-variables default-targets rules build-edges pools)
|
||||
build-file?
|
||||
(global-variables build-file-global-variables set-build-file-global-variables!)
|
||||
(default-targets build-file-default-targets set-build-file-default-targets!)
|
||||
(rules build-file-rules set-build-file-rules!)
|
||||
(build-edges build-file-build-edges set-build-file-build-edges!)
|
||||
(pools build-file-pools set-build-file-pools!))
|
||||
|
||||
(define-record-printer (<build-file> file out)
|
||||
(fprintf out "# debug build file\n")
|
||||
(mapping-for-each (lambda (k v) (fprintf out "~A = ~A\n" k (render-evalstring v #f))) (build-file-global-variables file))
|
||||
(unless (null? (build-file-default-targets file))
|
||||
(fprintf out "default")
|
||||
(for-each (lambda (v) (fprintf out " ~A" (render-evalstring v #t))) (build-file-default-targets file))
|
||||
(fprintf out "\n"))
|
||||
(mapping-for-each (lambda (k rule) (fprintf out "~S" rule)) (build-file-rules file))
|
||||
(for-each (lambda (edge) (fprintf out "~S" edge)) (build-file-build-edges file)))
|
||||
|
||||
;; Represents a Ninja build rule.
|
||||
; variables that are ignored for now: dyndep, generator, pool, msvc_deps_prefix
|
||||
(define-record-type <build-rule>
|
||||
(make-build-rule name command depfile deps description restat rspfile rspfile-content)
|
||||
build-rule?
|
||||
(name build-rule-name set-build-rule-name!)
|
||||
(command build-rule-command set-build-rule-command!)
|
||||
(depfile build-rule-depfile set-build-rule-depfile!)
|
||||
(deps build-rule-deps set-build-rule-deps!)
|
||||
(description build-rule-description set-build-rule-description!)
|
||||
(restat build-rule-restat set-build-rule-restat!)
|
||||
(rspfile build-rule-rspfile set-build-rule-rspfile!)
|
||||
(rspfile-content build-rule-rspfile-content set-build-rule-rspfile-content!))
|
||||
|
||||
(define-record-printer (<build-rule> rule out)
|
||||
(fprintf out "rule ~A\n" (build-rule-name rule))
|
||||
(pr out "command" (build-rule-command rule))
|
||||
(pr out "depfile" (build-rule-depfile rule))
|
||||
(pr out "deps" (build-rule-deps rule))
|
||||
(pr out "description" (build-rule-description rule))
|
||||
(pr out "restat" (build-rule-restat rule))
|
||||
(pr out "rspfile" (build-rule-rspfile rule))
|
||||
(pr out "rspfile-content" (build-rule-rspfile-content rule))
|
||||
(fprintf out "\n"))
|
||||
|
||||
;; Represents a Ninja build edge (aka one or more files built by this Ninja file)
|
||||
(define-record-type <build-edge>
|
||||
(make-build-edge rule outputs inputs implicit-dependencies order-only-dependencies validations implicit-outputs variables resolved)
|
||||
build-edge?
|
||||
(rule build-edge-rule)
|
||||
(outputs build-edge-outputs)
|
||||
(inputs build-edge-inputs)
|
||||
(implicit-dependencies build-edge-implicit-dependencies)
|
||||
(order-only-dependencies build-edge-order-only-dependencies)
|
||||
(validations build-edge-validations)
|
||||
(implicit-outputs build-edge-implicit-outputs)
|
||||
(variables build-edge-variables set-build-edge-variables!)
|
||||
(resolved build-edge-resolved set-build-edge-resolved!))
|
||||
(define-record-printer (<build-edge> edge out)
|
||||
(fprintf out "build")
|
||||
(for-each (lambda (var) (fprintf out " ~A" (render-evalstring var #t))) (build-edge-outputs edge))
|
||||
(fprintf out ": ~A" (build-edge-rule edge))
|
||||
(for-each (lambda (var) (fprintf out " ~A" (render-evalstring var #t))) (build-edge-inputs edge))
|
||||
(unless (null? (build-edge-implicit-dependencies edge))
|
||||
(fprintf out " |")
|
||||
(for-each (lambda (var) (fprintf out " ~A" (render-evalstring var #t))) (build-edge-implicit-dependencies edge)))
|
||||
(unless (null? (build-edge-order-only-dependencies edge))
|
||||
(fprintf out " ||")
|
||||
(for-each (lambda (var) (fprintf out " ~A" (render-evalstring var #t))) (build-edge-order-only-dependencies edge)))
|
||||
(unless (null? (build-edge-validations edge))
|
||||
(fprintf out " |@")
|
||||
(for-each (lambda (var) (fprintf out " ~A" (render-evalstring var #t))) (build-edge-validations edge)))
|
||||
(fprintf out "\n")
|
||||
(mapping-for-each (lambda (k v) (pr out k v)) (build-edge-variables edge))
|
||||
(define rule (build-edge-resolved edge))
|
||||
(when rule
|
||||
(fprintf out " resolved rule ~A\n" (build-rule-name rule))
|
||||
(pr out " command" (build-rule-command rule))
|
||||
(pr out " depfile" (build-rule-depfile rule))
|
||||
(pr out " deps" (build-rule-deps rule))
|
||||
(pr out " description" (build-rule-description rule))
|
||||
(pr out " restat" (build-rule-restat rule))
|
||||
(pr out " rspfile" (build-rule-rspfile rule))
|
||||
(pr out " rspfile-content" (build-rule-rspfile-content rule))
|
||||
(fprintf out "\n"))
|
||||
(fprintf out "\n"))
|
||||
(define (build-rule-resolve rule edge file)
|
||||
(define (try-resolve v is-command)
|
||||
(and v (resolve-evalstring v file rule edge is-command)))
|
||||
(make-build-rule #f
|
||||
(try-resolve (build-rule-command rule) #t)
|
||||
(try-resolve (build-rule-depfile rule) #f)
|
||||
(try-resolve (build-rule-deps rule) #f)
|
||||
(try-resolve (build-rule-description rule) #f)
|
||||
(try-resolve (build-rule-restat rule) #f)
|
||||
(try-resolve (build-rule-rspfile rule) #f)
|
||||
(try-resolve (build-rule-rspfile-content rule) #f)))
|
||||
|
||||
;; Reads a full Ninja file, returning a <build-file> record.
|
||||
(define (read-ninja-file strval)
|
||||
(define i 0)
|
||||
(define (eat-whitespace)
|
||||
(cond
|
||||
((>= i (string-length strval)))
|
||||
((string-prefix? "$\r\n" strval 0 3 i) (set! i (+ i 3)) (eat-whitespace))
|
||||
((string-prefix? "$\n" strval 0 2 i) (set! i (+ i 2)) (eat-whitespace))
|
||||
((char=? (string-ref strval i) #\space) (set! i (string-skip strval (lambda (ch) (char=? ch #\space)) i)) (eat-whitespace))))
|
||||
(define (read-eval-string-text path)
|
||||
(define collected '())
|
||||
(define start-i i)
|
||||
(define (tick)
|
||||
(cond
|
||||
((>= i (string-length strval)) (error "unexpected EOF"))
|
||||
((is-valid-eval-string-char (string-ref strval i) path)
|
||||
(let ((start-i i))
|
||||
(do () ((or (>= i (string-length strval)) (not (is-valid-eval-string-char (string-ref strval i) path))))
|
||||
(set! i (+ i 1)))
|
||||
(set! collected (cons (string-copy strval start-i i) collected)))
|
||||
(tick))
|
||||
((and path (member (string-ref strval i) '(#\space #\: #\| #\newline))))
|
||||
((and (not path) (char=? #\newline (string-ref strval i)))
|
||||
(set! i (+ i 1)))
|
||||
((string-prefix? "\r\n" strval 0 2 i)
|
||||
(unless path
|
||||
(set! i (+ i 2))))
|
||||
((string-prefix? "$\r\n" strval 0 3 i)
|
||||
(set! i (string-skip strval (lambda (ch) (char=? ch #\space)) (+ i 3)))
|
||||
(tick))
|
||||
((string-prefix? "$\n" strval 0 2 i)
|
||||
(set! i (string-skip strval (lambda (ch) (char=? ch #\space)) (+ i 2)))
|
||||
(tick))
|
||||
((string-prefix? "${" strval 0 2 i)
|
||||
(let ((end-of-varname (string-skip strval (lambda (ch) (is-valid-varname-char ch #f)) (+ i 2))))
|
||||
(unless (char=? (string-ref strval end-of-varname) #\}) (error "unexpected non-varname character at index" end-of-varname))
|
||||
(set! collected (cons (cons 'varname (string-copy strval (+ i 2) end-of-varname)) collected))
|
||||
(set! i (+ end-of-varname 1)))
|
||||
(tick))
|
||||
((or (string-prefix? "$$" strval 0 2 i) (string-prefix? "$:" strval 0 2 i) (string-prefix? "$ " strval 0 2 i))
|
||||
(set! collected (cons (string-copy strval (+ i 1) (+ i 2)) collected))
|
||||
(set! i (+ i 2))
|
||||
(tick))
|
||||
((and (char=? (string-ref strval i) #\$) (is-valid-varname-char (string-ref strval (+ i 1)) #t))
|
||||
(let ((end-of-varname (string-skip strval (lambda (ch) (is-valid-varname-char ch #t)) (+ i 1))))
|
||||
(set! collected (cons (cons 'varname (string-copy strval (+ i 1) end-of-varname)) collected))
|
||||
(set! i end-of-varname))
|
||||
(tick))
|
||||
((char=? (string-ref strval i) #\$)
|
||||
(error "bad $-escape at index" i))
|
||||
(else (error "Unknown character at index" i))))
|
||||
(tick)
|
||||
(when path (eat-whitespace))
|
||||
(if (= i start-i) #f (reverse collected)))
|
||||
|
||||
(define (try-read-eval-string-text path)
|
||||
(define start-i i)
|
||||
(call/cc
|
||||
(lambda (cc)
|
||||
(with-exception-handler
|
||||
(lambda (h) (set! i start-i) (cc #f))
|
||||
(lambda () (read-eval-string-text path))))))
|
||||
|
||||
(define (read-token)
|
||||
(define start-i i)
|
||||
(define post-space (or (string-skip strval (lambda (ch) (char=? ch #\space)) i) i))
|
||||
(define token
|
||||
(cond
|
||||
((>= i (string-length strval))
|
||||
'eof)
|
||||
((string-prefix? "#" strval 0 1 post-space)
|
||||
(let ((end-of-comment (or (string-index strval (lambda (ch) (char=? ch #\newline)) post-space) (string-length strval))))
|
||||
(set! i end-of-comment)
|
||||
(read-token)))
|
||||
((string-prefix? "\r\n" strval 0 2 post-space)
|
||||
(set! i (+ post-space 2))
|
||||
'newline)
|
||||
((string-prefix? "\n" strval 0 1 post-space)
|
||||
(set! i (+ post-space 1))
|
||||
'newline)
|
||||
((> post-space i)
|
||||
'indent)
|
||||
((char=? (string-ref strval i) #\=)
|
||||
(set! i (+ i 1))
|
||||
'equals)
|
||||
((char=? (string-ref strval i) #\:)
|
||||
(set! i (+ i 1))
|
||||
'colon)
|
||||
((string-prefix? "|@" strval 0 2 i)
|
||||
(set! i (+ i 2))
|
||||
'pipe-at)
|
||||
((string-prefix? "||" strval 0 2 i)
|
||||
(set! i (+ i 2))
|
||||
'pipe-pipe)
|
||||
((char=? (string-ref strval i) #\|)
|
||||
(set! i (+ i 1))
|
||||
'pipe)
|
||||
((is-valid-varname-char (string-ref strval i) #f)
|
||||
(let* ((token-end (string-skip strval (lambda (ch) (is-valid-varname-char ch #f)) i))
|
||||
(token (string-copy strval i token-end)))
|
||||
(set! i token-end)
|
||||
(cond
|
||||
((string=? token "build") 'build)
|
||||
((string=? token "pool") 'pool)
|
||||
((string=? token "rule") 'rule)
|
||||
((string=? token "default") 'default)
|
||||
((string=? token "include") 'include)
|
||||
((string=? token "subninja") 'subninja)
|
||||
(else token))))
|
||||
(else #f)))
|
||||
(unless (member token '(newline eof #f)) (eat-whitespace))
|
||||
token)
|
||||
(define (expect-token expected)
|
||||
(define prev-i i)
|
||||
(define token (read-token))
|
||||
(if (eqv? expected token)
|
||||
token
|
||||
(begin (set! i prev-i) #f)))
|
||||
|
||||
(define (read-rule)
|
||||
(define name (read-token))
|
||||
(unless (string? name) (error "reading rule: expected valid ident, found" name))
|
||||
(unless (expect-token 'newline) (error "reading rule: expected name to be followed by newline"))
|
||||
(define rule (make-build-rule name #f #f #f #f #f #f #f))
|
||||
(define (parse-variable)
|
||||
(define name #f)
|
||||
(define contents #f)
|
||||
(when (expect-token 'indent)
|
||||
(set! name (read-token))
|
||||
(unless (string? name) (error "reading rule: expected name, found" name))
|
||||
(unless (expect-token 'equals) (error "reading rule: expected equals, found" (read-token)))
|
||||
(set! contents (read-eval-string-text #f))
|
||||
(cond
|
||||
((string=? name "command") (set-build-rule-command! rule contents))
|
||||
((string=? name "depfile") (set-build-rule-depfile! rule contents))
|
||||
((string=? name "deps") (set-build-rule-deps! rule contents))
|
||||
((string=? name "description") (set-build-rule-description! rule contents))
|
||||
((string=? name "restat") (set-build-rule-restat! rule contents))
|
||||
((string=? name "rspfile") (set-build-rule-rspfile! rule contents))
|
||||
((string=? name "rspfile_content") (set-build-rule-rspfile-content! rule contents))
|
||||
((member name '("dyndep" "generator" "pool" "msvc_deps_prefix") string=?))
|
||||
(else (error "reading rule: unexpected rule" name)))
|
||||
(parse-variable)))
|
||||
(parse-variable)
|
||||
rule)
|
||||
|
||||
(define (read-build-edge file)
|
||||
(define (read-list tail)
|
||||
(define eval-string (try-read-eval-string-text #t))
|
||||
(if eval-string
|
||||
(read-list (cons (resolve-evalstring eval-string file #f #f #f) tail))
|
||||
(reverse tail)))
|
||||
|
||||
(define outputs (read-list '()))
|
||||
(define implicit-outputs '())
|
||||
(when (expect-token 'pipe)
|
||||
(set! implicit-outputs (read-list '())))
|
||||
(unless (expect-token 'colon)
|
||||
(error "reading build edge: expected colon, found" (read-token)))
|
||||
(define rule-name (read-token))
|
||||
(unless (string? rule-name)
|
||||
(error "reading build edge: expected rule name, found" rule-name))
|
||||
(define inputs (read-list '()))
|
||||
(define implicit-dependencies '())
|
||||
(when (expect-token 'pipe)
|
||||
(set! implicit-dependencies (read-list '())))
|
||||
(define order-only-dependencies '())
|
||||
(when (expect-token 'pipe-pipe)
|
||||
(set! order-only-dependencies (read-list '())))
|
||||
(define validations '())
|
||||
(when (expect-token 'pipe-at)
|
||||
(set! validations (read-list '())))
|
||||
(unless (expect-token 'newline) (error "reading build edge: expected rule name / dependencies to be followed by newline"))
|
||||
(define edge (make-build-edge rule-name outputs inputs implicit-dependencies order-only-dependencies validations implicit-outputs (mapping (make-default-comparator)) #f))
|
||||
(define (parse-variable)
|
||||
(define name #f)
|
||||
(define contents #f)
|
||||
(when (expect-token 'indent)
|
||||
(set! name (read-token))
|
||||
(unless (string? name) (error "reading build edge var: expected name, found" name))
|
||||
(unless (expect-token 'equals) (error "reading build edge var: expected equals, found" (read-token)))
|
||||
(set! contents (resolve-evalstring (read-eval-string-text #f) file #f #f #f))
|
||||
(set-build-edge-variables! edge (mapping-set! (build-edge-variables edge) name contents))
|
||||
(parse-variable)))
|
||||
(parse-variable)
|
||||
edge)
|
||||
|
||||
(define (read-toplevel file)
|
||||
(define token (read-token))
|
||||
(case token
|
||||
((eof) #f)
|
||||
((rule)
|
||||
(let ((rule (read-rule))) (set-build-file-rules! file (mapping-set! (build-file-rules file) (build-rule-name rule) rule)))
|
||||
(read-toplevel file))
|
||||
((build)
|
||||
(let ((edge (read-build-edge file))) (set-build-file-build-edges! file (cons edge (build-file-build-edges file))))
|
||||
(read-toplevel file))
|
||||
((default)
|
||||
(do () ((expect-token 'newline)) (set-build-file-default-targets! file (cons (resolve-evalstring (read-eval-string-text #t) file #f #f #f) (build-file-default-targets file)))))
|
||||
((subninja include) (error "todo: includes"))
|
||||
((pool) (error "todo: pool declaration"))
|
||||
((newline) (read-toplevel file))
|
||||
(else
|
||||
(unless (string? token) (error "unexpected" token))
|
||||
(unless (expect-token 'equals) (error "expected =, found" (read-token)))
|
||||
(let* ((value (read-eval-string-text #f)) (resolved (resolve-evalstring value file #f #f #f)))
|
||||
(set-build-file-global-variables! file (mapping-set! (build-file-global-variables file) token resolved)))
|
||||
(read-toplevel file)))
|
||||
file)
|
||||
(define out-file (read-toplevel (make-build-file (mapping (make-default-comparator)) '() (mapping (make-default-comparator)) '() (mapping (make-default-comparator)))))
|
||||
(for-each (lambda (f) (unless (string=? (build-edge-rule f) "phony") (set-build-edge-resolved! f (build-rule-resolve (mapping-ref (build-file-rules out-file) (build-edge-rule f)) f out-file)))) (build-file-build-edges out-file))
|
||||
out-file)))
|
||||
9
lang/ninja/zilch-lang-ninja.egg
Normal file
9
lang/ninja/zilch-lang-ninja.egg
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
((version "0.0.1")
|
||||
(synopsis "Nix. Noppes. Nada.")
|
||||
(author "puck")
|
||||
(dependencies r7rs json zilch zilch.planner srfi-207)
|
||||
(component-options
|
||||
(csc-options "-X" "r7rs" "-X" "zilch.zexpr" "-R" "r7rs" "-optimize-level" "3"))
|
||||
(components
|
||||
(extension zilch.lang.ninja
|
||||
(source "src/ninja.sld"))))
|
||||
|
|
@ -11,6 +11,7 @@ pkgs.mkShell {
|
|||
(pkgs.callPackage ./planner {})
|
||||
(pkgs.callPackage ./lang/go {})
|
||||
(pkgs.callPackage ./lang/rust {})
|
||||
(pkgs.callPackage ./lang/ninja {})
|
||||
(pkgs.callPackage ./docs/docread {})
|
||||
(pkgs.callPackage ./cli {})
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue