-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathhost_info.go
More file actions
232 lines (192 loc) · 5.93 KB
/
host_info.go
File metadata and controls
232 lines (192 loc) · 5.93 KB
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
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
package main
// Host-toolchain info cache.
//
// Every tin invocation needs a few facts about the host clang/LLVM
// install: the version banner (used as a cache-key suffix), the LLVM
// target triple (baked into emitted IR modules), and the canonical
// linker argv prefix/suffix (used by the lld backend). Without
// caching, each fact costs one ~10-25 ms clang spawn per `tin`
// invocation -- on small programs that's a measurable fraction of the
// build.
//
// We cache the lot under `.build/host-info/<key>.json`, keyed by clang
// binary identity (path + size + mtime). If the binary moves or
// upgrades, the cached record is rejected and we re-probe.
import (
"encoding/json"
"fmt"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"sync"
"time"
)
const hostInfoCacheDir = ".build/host-info"
type hostToolchainInfo struct {
// Identity stamp -- compared on read against the live clang binary
// so an upgrade in-place busts the cache.
ClangPath string `json:"clang_path"`
ClangSize int64 `json:"clang_size"`
ClangMtime int64 `json:"clang_mtime_unix_ns"`
// Cached probe results.
ClangVersion string `json:"clang_version"` // first line of `clang --version`
ClangMajorVersion int `json:"clang_major"` // parsed major number
TargetTriple string `json:"target_triple"` // from `clang -x c - -S -emit-llvm -o -`
HostOS string `json:"host_os"`
HostArch string `json:"host_arch"`
}
var (
hostInfoCache *hostToolchainInfo
hostInfoOnce sync.Once
)
// hostInfo returns the cached host-toolchain probe results, running
// the underlying clang invocations (and writing the result to disk)
// only on first miss. Subsequent process startups load the JSON in a
// single open()+read().
//
// Returns a zero-value struct on probe failure so callers see "" for
// version / triple and can decide whether to keep going (most do --
// the version is only used as a cache-key suffix).
func hostInfo() *hostToolchainInfo {
hostInfoOnce.Do(func() {
hostInfoCache = loadOrProbeHostInfo()
})
return hostInfoCache
}
func loadOrProbeHostInfo() *hostToolchainInfo {
clangPath, _ := exec.LookPath("clang")
var (
size int64
mtime int64
)
if clangPath != "" {
if st, err := os.Stat(clangPath); err == nil {
size = st.Size()
mtime = st.ModTime().UnixNano()
}
}
if disk := readHostInfoFromDisk(clangPath, size, mtime); disk != nil {
return disk
}
info := &hostToolchainInfo{
ClangPath: clangPath,
ClangSize: size,
ClangMtime: mtime,
HostOS: runtime.GOOS,
HostArch: runtime.GOARCH,
}
probeClangVersionInto(info)
probeTargetTripleInto(info)
writeHostInfoToDisk(info)
return info
}
// readHostInfoFromDisk returns the cached record iff its binary
// identity matches the live clang. On any mismatch, IO error, or
// JSON parse failure, returns nil and the caller re-probes.
func readHostInfoFromDisk(clangPath string, size, mtime int64) *hostToolchainInfo {
if clangPath == "" {
return nil
}
path := filepath.Join(hostInfoCacheDir, "clang.json")
data, err := os.ReadFile(path)
if err != nil {
return nil
}
var rec hostToolchainInfo
if err := json.Unmarshal(data, &rec); err != nil {
return nil
}
if rec.ClangPath != clangPath || rec.ClangSize != size || rec.ClangMtime != mtime {
return nil
}
if rec.HostOS != runtime.GOOS || rec.HostArch != runtime.GOARCH {
return nil
}
return &rec
}
func writeHostInfoToDisk(info *hostToolchainInfo) {
if err := os.MkdirAll(hostInfoCacheDir, 0o755); err != nil {
return
}
body, err := json.MarshalIndent(info, "", " ")
if err != nil {
return
}
// Atomic write so a concurrent reader never sees a half-written
// file. Doesn't matter much in practice (the worst case is a
// re-probe), but the cost is negligible.
tmp := filepath.Join(hostInfoCacheDir, fmt.Sprintf("clang.json.tmp.%d.%d", os.Getpid(), time.Now().UnixNano()))
if err := os.WriteFile(tmp, body, 0o644); err != nil {
return
}
final := filepath.Join(hostInfoCacheDir, "clang.json")
if err := os.Rename(tmp, final); err != nil {
_ = os.Remove(tmp)
}
}
// probeClangVersionInto runs `clang --version` and parses the first
// line + major version into info.
func probeClangVersionInto(info *hostToolchainInfo) {
out, err := exec.Command("clang", "--version").Output()
if err != nil {
return
}
s := string(out)
first := s
if idx := strings.IndexByte(s, '\n'); idx >= 0 {
first = s[:idx]
}
info.ClangVersion = strings.TrimSpace(first)
// Parse "version <N>" then read the leading decimal run.
if idx := strings.Index(s, "version "); idx >= 0 {
rest := s[idx+len("version "):]
major := 0
for _, c := range rest {
if c >= '0' && c <= '9' {
major = major*10 + int(c-'0')
} else {
break
}
}
info.ClangMajorVersion = major
}
}
// probeTargetTripleInto compiles a trivial empty C TU to LLVM IR and
// extracts the `target triple = "..."` line clang emits. This is the
// only reliable way to get the normalized macosx-style triple (rather
// than the darwin-style one from -dumpmachine).
func probeTargetTripleInto(info *hostToolchainInfo) {
out, err := exec.Command("clang", "-x", "c", "-", "-S", "-emit-llvm", "-o", "-").Output()
if err != nil {
// Best-effort fallback by GOOS/GOARCH; matches the legacy
// behavior in codegen.detectTargetTriple.
switch runtime.GOOS + "/" + runtime.GOARCH {
case "linux/amd64":
info.TargetTriple = "x86_64-pc-linux-gnu"
case "linux/arm64":
info.TargetTriple = "aarch64-unknown-linux-gnu"
case "darwin/amd64":
info.TargetTriple = "x86_64-apple-macosx11.0.0"
case "darwin/arm64":
info.TargetTriple = "arm64-apple-macosx11.0.0"
default:
info.TargetTriple = "x86_64-pc-linux-gnu"
}
return
}
const prefix = `target triple = "`
for _, line := range strings.Split(string(out), "\n") {
if strings.HasPrefix(line, prefix) {
tr := line[len(prefix):]
if idx := strings.IndexByte(tr, '"'); idx >= 0 {
tr = tr[:idx]
}
if tr != "" {
info.TargetTriple = tr
return
}
}
}
}