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
|
# SPDX-FileCopyrightText: 2021 Nixpkgs/NixOS contributors
## Custom Nim builder for Nixpkgs.
import std/[os, osproc, parseutils, sequtils, streams, strutils]
proc findNimbleFile(): string =
## Copied from Nimble.
## Copyright (c) 2015, Dominik Picheta
## BSD3
let dir = getCurrentDir()
result = ""
var hits = 0
for kind, path in walkDir(dir):
if kind in {pcFile, pcLinkToFile}:
let ext = path.splitFile.ext
if ext == ".nimble":
result = path
inc hits
if hits >= 2:
quit("Only one .nimble file should be present in " & dir)
elif hits == 0:
quit("Could not find a file with a .nimble extension in " & dir)
proc getEnvBool(key: string; default = false): bool =
## Parse a boolean environmental variable.
let val = getEnv(key)
if val == "": default
else: parseBool(val)
proc getNimbleFilePath(): string =
## Get the Nimble file for the current package.
if existsEnv"nimbleFile":
getEnv"nimbleFile"
else:
findNimbleFile()
proc getNimbleValue(filePath, key: string; default = ""): string =
## Extract a string value from the Nimble file at ``filePath``.
var
fs = newFileStream(filePath, fmRead)
line: string
if fs.isNil:
quit("could not open " & filePath)
while fs.readline(line):
if line.startsWith(key):
var i = key.len
i.inc skipWhile(line, Whitespace, i)
if line[i] == '=':
inc i
i.inc skipWhile(line, Whitespace, i)
discard parseUntil(line, result, Newlines, i)
if result.len > 0 and result[0] == '"':
result = result.unescape
return
default
proc getNimbleValues(filePath, key: string): seq[string] =
## Extract a string sequence from the Nimble file at ``filePath``.
var gunk = getNimbleValue(filePath, key)
result = gunk.strip(chars = {'@', '[', ']'}).split(',')
if result == @[""]: reset result
apply(result) do (s: var string):
s = s.strip()
if s.len > 0 and s[0] == '"':
s = s.unescape()
proc getOutputDir(name: string): string =
## Return the output directory for output `name`.
## If `name` is not a valid output then the first output
## is returned as a default.
let outputs = splitWhitespace getEnv("outputs")
doAssert(outputs.len > 0)
if outputs.contains name:
result = getEnv(name)
if result == "":
result = getEnv("out")
if result == "":
result = getEnv(outputs[0], "/dev/null")
proc configurePhase*() =
## Generate "config.nims" which will be read by the Nim
## compiler during later phases.
const configFilePath = "config.nims"
echo "generating ", configFilePath
let
nf = getNimbleFilePath()
mode =
if fileExists configFilePath: fmAppend
else: fmWrite
var cfg = newFileStream(configFilePath, mode)
proc switch(key, val: string) =
cfg.writeLine("switch(", key.escape, ",", val.escape, ")")
switch("backend", nf.getNimbleValue("backend", "c"))
switch("nimcache", getEnv("NIX_BUILD_TOP", ".") / "nimcache")
if getEnvBool("nimRelease", true):
switch("define", "release")
for def in getEnv("nimDefines").split:
if def != "":
switch("define", def)
for input in getEnv("NIX_NIM_BUILD_INPUTS").split:
if input != "":
for nimbleFile in walkFiles(input / "*.nimble"):
let inputSrc = normalizedPath(
input / nimbleFile.getNimbleValue("srcDir", "."))
echo "found nimble input ", inputSrc
switch("path", inputSrc)
close(cfg)
proc buildPhase*() =
## Build the programs listed in the Nimble file and
## optionally some documentation.
var cmds: seq[string]
proc before(idx: int) =
echo "build job ", idx, ": ", cmds[idx]
let
nf = getNimbleFilePath()
bins = nf.getNimbleValues("bin")
srcDir = nf.getNimbleValue("srcDir", ".")
binDir = getOutputDir("bin") / "bin"
if bins != @[]:
for bin in bins:
cmds.add("nim compile $# --parallelBuild:$# --outdir:$# $#" %
[getenv("nimFlags"), getenv("NIX_BUILD_CORES","1"), binDir, normalizedPath(srcDir / bin)])
if getEnvBool"nimDoc":
echo "generating documentation"
let docDir = getOutputDir("doc") / "doc"
for path in walkFiles(srcDir / "*.nim"):
cmds.add("nim doc --outdir:$# $#" % [docDir, path])
if cmds.len > 0:
let err = execProcesses(
cmds, n = 1,
beforeRunEvent = before)
if err != 0: quit("build phase failed", err)
proc installPhase*() =
## Install the Nim sources if ``nimBinOnly`` is not
## set in the environment.
if not getEnvBool"nimBinOnly":
let
nf = getNimbleFilePath()
srcDir = nf.getNimbleValue("srcDir", ".")
devDir = getOutputDir "dev"
echo "Install ", srcDir, " to ", devDir
copyDir(normalizedPath(srcDir), normalizedPath(devDir / srcDir))
copyFile(nf, devDir / nf.extractFilename)
proc checkPhase*() =
## Build and run the tests in ``tests``.
var cmds: seq[string]
proc before(idx: int) =
echo "check job ", idx, ": ", cmds[idx]
for path in walkPattern("tests/t*.nim"):
cmds.add("nim r $#" % [path])
let err = execProcesses(
cmds, n = 1,
beforeRunEvent = before)
if err != 0: quit("check phase failed", err)
when isMainModule:
import std/parseopt
var phase: string
for kind, key, val in getopt():
case kind
of cmdLongOption:
case key.toLowerAscii
of "phase":
if phase != "": quit("only a single phase may be specified")
phase = val
else: quit("unhandled argument " & key)
of cmdEnd: discard
else: quit("unhandled argument " & key)
case phase
of "configure": configurePhase()
of "build": buildPhase()
of "install": installPhase()
of "check": checkPhase()
else: quit("unhandled phase " & phase)
|