about summary refs log tree commit diff
path: root/nixpkgs/pkgs/development/nim-packages/nim_builder/nim_builder.nim
blob: 8bb78555c2690c522e49ff9fc5f55de044372afa (plain) (blame)
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)