-
Notifications
You must be signed in to change notification settings - Fork 11
/
Copy pathbuild.go
149 lines (131 loc) · 3.39 KB
/
build.go
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
package main
import (
"bufio"
"bytes"
"fmt"
"os"
"os/exec"
"strings"
"syscall"
)
// FIXME replace by sysconf?
const MAX_CHARS = 32 * 1024
func Command(cmd string, args ...string) *exec.Cmd {
c := exec.Command(cmd, args...)
c.Stdout = os.Stdout
c.Stderr = os.Stderr
fmt.Printf("$ %s", cmd)
for i, arg := range args {
if i == 50 {
fmt.Printf("...")
break
}
fmt.Printf(" %s", arg)
}
fmt.Println()
return c
}
func parseMissingDrvs(output *bytes.Buffer) map[string]bool {
fmt.Println(output.String())
scanner := bufio.NewScanner(output)
scanner.Split(bufio.ScanLines)
found := false
missingDrvs := make(map[string]bool)
for scanner.Scan() {
line := scanner.Text()
if strings.Contains(line, "will be fetched") || strings.HasPrefix(line, "don't know how to build these paths") {
break
}
if strings.Contains(line, "will be built:") {
found = true
} else if found {
drv := strings.TrimLeft(line, " ")
missingDrvs[drv] = true
}
}
return missingDrvs
}
func nixDryBuild(buildArgs []string) (map[string]bool, error) {
var out bytes.Buffer
args := append([]string{"--dry-run"}, buildArgs...)
cmd := Command("nix-build", args...)
cmd.Stderr = &out
if err := cmd.Run(); err != nil {
fmt.Fprint(os.Stderr, out.String())
return nil, err
}
return parseMissingDrvs(&out), nil
}
func raiseFdLimit() (uint64, error) {
var rlimit syscall.Rlimit
err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rlimit)
if err != nil {
return 0, fmt.Errorf("failed to get rlimit: %s", err)
}
if rlimit.Cur < rlimitMax(rlimit) {
oldVal := rlimit.Cur
rlimit.Cur = rlimitMax(rlimit)
err = syscall.Setrlimit(syscall.RLIMIT_NOFILE, &rlimit)
if err != nil {
fmt.Fprintf(os.Stderr, "failed setting rlimit: %s", err)
return uint64(oldVal), nil
}
}
return uint64(rlimit.Cur), nil
}
func nixBuild(drvs map[string]bool, buildArgs []string, version nixVersion) error {
buildArgs = append([]string{"build"}, buildArgs...)
numBuildChars := len("nix") + 1
for _, arg := range buildArgs {
numBuildChars += len(arg) + 1
}
numChars := numBuildChars
args := buildArgs
if numChars > MAX_CHARS {
return fmt.Errorf("too many arguments")
}
fdLimit, err := raiseFdLimit()
if err != nil {
return err
}
// nix build needs 3 fds per derivation, also add a safety margin on top.
maxConcurrentBuilds := 1024 + fdLimit*3
for drv := range drvs {
n := len(drv) + 1
if n+numChars > MAX_CHARS || uint64(len(args)-len(buildArgs)) >= maxConcurrentBuilds {
cmd := Command("nix", args...)
if err := cmd.Run(); err != nil {
return err
}
numChars = numBuildChars
args = buildArgs
}
// if nix version is higher than 2.15 we need to append ^* to the drv
if version.Major > 2 || (version.Major == 2 && version.Minor >= 15) {
drv += "^*"
}
args = append(args, drv)
numChars += n
}
if numChars > numBuildChars {
cmd := Command("nix", args...)
if err := cmd.Run(); err != nil {
return err
}
}
return nil
}
func buildUncached(installables []string, buildArgs []string, version nixVersion) ([]string, error) {
missingDrvs, err := nixDryBuild(installables)
if err != nil {
return nil, fmt.Errorf("--dry-run failed: %s", err)
}
if err := nixBuild(missingDrvs, buildArgs, version); err != nil {
return nil, fmt.Errorf("nix build failed: %s\n", err)
}
var builtDrvs []string
for drv := range missingDrvs {
builtDrvs = append(builtDrvs, drv)
}
return builtDrvs, nil
}