-
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathbuildsystems.go
More file actions
313 lines (263 loc) · 10 KB
/
buildsystems.go
File metadata and controls
313 lines (263 loc) · 10 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
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
package main
import (
"fmt"
"io"
"os"
"path/filepath"
"runtime"
)
// stagingDir derives the staging/install directory from a build directory path.
// Build directories follow the pattern: .build/<lib>/build, so staging is at .build/staging.
func stagingDir(buildDir string) string {
return filepath.Join(filepath.Dir(filepath.Dir(buildDir)), "staging")
}
// openBuildLog opens a build log file and returns a multiwriter that writes to both
// the log file and stdout. The returned cleanup function must be called when done.
// If append is true, the log file is opened in append mode; otherwise it is truncated.
func openBuildLog(buildDir string, append bool) (io.Writer, func(), error) {
logFile := filepath.Join(buildDir, "build.log")
var logger *os.File
var err error
if append {
logger, err = os.OpenFile(logFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
} else {
logger, err = os.Create(logFile)
}
if err != nil {
return nil, nil, err
}
return io.MultiWriter(logger, os.Stdout), func() { logger.Close() }, nil
}
// withBuildLog opens a build log and executes the provided function with the logger.
// This helper consolidates the repeated log setup/cleanup pattern across build systems.
func withBuildLog(buildDir string, append bool, fn func(output io.Writer) error) error {
output, cleanup, err := openBuildLog(buildDir, append)
if err != nil {
return err
}
defer cleanup()
return fn(output)
}
// AutoconfBuild implements the BuildSystem interface for autoconf-based builds
type AutoconfBuild struct{}
func (a *AutoconfBuild) Configure(lib *Library, srcPath, buildDir, installDir string) error {
args := []string{
fmt.Sprintf("--prefix=%s", installDir),
}
if lib.ConfigureArgs != nil {
args = append(args, lib.ConfigureArgs(runtime.GOOS)...)
}
// Add standard compiler and linker flags (unless library opts out)
// Some libraries like zlib have non-standard configure scripts that reject these
if !lib.SkipAutoFlags {
incDir := filepath.Join(installDir, "include")
libDir := filepath.Join(installDir, "lib")
cflags := fmt.Sprintf("-O3 -I%s", incDir)
cppflags := fmt.Sprintf("-I%s", incDir)
cxxflags := "-O3" // Start with optimization flag
ldflags := fmt.Sprintf("-L%s", libDir)
// On macOS, configure proper SDK and header paths
// This is required for CGO compilation and C++ builds
if runtime.GOOS == "darwin" {
// CGO_CFLAGS format: -isysroot /path/to/sdk -I/nix/store/.../lib/clang/18/include
cgoCflags := os.Getenv("CGO_CFLAGS")
libcxxInclude := os.Getenv("LIBCXX_INCLUDE")
// For C: add SDK path and clang builtins
if cgoCflags != "" {
cflags = fmt.Sprintf("%s %s", cflags, cgoCflags)
}
// For CPPFLAGS: only add -isysroot, NOT the clang builtin include path
// This prevents C++ from finding clang's stddef.h before libc++'s wrapper
if cgoCflags != "" {
// Extract just the -isysroot part from CGO_CFLAGS
// CGO_CFLAGS = "-isysroot /path/to/sdk -I/path/to/clang/include"
// We only want the -isysroot portion for CPPFLAGS
if sdkPath := extractSDKPath(cgoCflags); sdkPath != "" {
cppflags = fmt.Sprintf("%s -isysroot %s", cppflags, sdkPath)
}
}
// For C++: use -nostdinc++ to disable built-in paths, then add libc++ first,
// then clang builtins (for stddef.h, stdarg.h), then SDK path
if libcxxInclude != "" && cgoCflags != "" {
cxxflags = fmt.Sprintf("%s -nostdinc++ -I%s %s", cxxflags, libcxxInclude, cgoCflags)
} else if cgoCflags != "" {
cxxflags = fmt.Sprintf("%s %s", cxxflags, cgoCflags)
}
}
args = append(args,
fmt.Sprintf("CFLAGS=%s", cflags),
fmt.Sprintf("CPPFLAGS=%s", cppflags),
fmt.Sprintf("CXXFLAGS=%s", cxxflags),
fmt.Sprintf("LDFLAGS=%s", ldflags),
)
}
// Run configure from source directory
configurePath := "./configure"
absConfigurePath := filepath.Join(srcPath, "configure")
if !fileExists(absConfigurePath) {
return fmt.Errorf("configure script not found at %s", absConfigurePath)
}
// Make configure executable
if err := os.Chmod(absConfigurePath, 0755); err != nil {
return fmt.Errorf("failed to make configure executable: %w", err)
}
return withBuildLog(buildDir, false, func(output io.Writer) error {
return runCommand(srcPath, output, installDir, configurePath, args...)
})
}
func (a *AutoconfBuild) Build(lib *Library, srcPath, buildDir string) error {
// Touch automake files to prevent regeneration
touchAutomakeFiles(srcPath)
installDir := stagingDir(buildDir)
return withBuildLog(buildDir, true, func(output io.Writer) error {
// make
if err := runCommand(srcPath, output, installDir, "make", "-j", fmt.Sprintf("%d", runtime.NumCPU())); err != nil {
return err
}
// make install
return runCommand(srcPath, output, installDir, "make", "install")
})
}
// CMakeBuild implements the BuildSystem interface for CMake-based builds
type CMakeBuild struct {
SourceSubdir string // Optional subdirectory containing source (e.g. "source" for x265)
}
func (c *CMakeBuild) Configure(lib *Library, srcPath, buildDir, installDir string) error {
// Determine actual source path
actualSrcPath := srcPath
if c.SourceSubdir != "" {
actualSrcPath = filepath.Join(srcPath, c.SourceSubdir)
}
args := []string{
actualSrcPath,
fmt.Sprintf("-DCMAKE_INSTALL_PREFIX=%s", installDir),
"-DCMAKE_BUILD_TYPE=Release",
"-DCMAKE_INSTALL_LIBDIR=lib",
}
if lib.ConfigureArgs != nil {
args = append(args, lib.ConfigureArgs(runtime.GOOS)...)
}
return withBuildLog(buildDir, false, func(output io.Writer) error {
return runCommand(buildDir, output, installDir, "cmake", args...)
})
}
func (c *CMakeBuild) Build(lib *Library, srcPath, buildDir string) error {
installDir := stagingDir(buildDir)
return withBuildLog(buildDir, true, func(output io.Writer) error {
// cmake --build . --target install
if err := runCommand(buildDir, output, installDir, "cmake", "--build", ".", "--parallel", fmt.Sprintf("%d", runtime.NumCPU())); err != nil {
return err
}
return runCommand(buildDir, output, installDir, "cmake", "--build", ".", "--target", "install")
})
}
// MesonBuild implements the BuildSystem interface for Meson-based builds
type MesonBuild struct{}
func (m *MesonBuild) Configure(lib *Library, srcPath, buildDir, installDir string) error {
args := []string{
"setup",
buildDir,
srcPath,
fmt.Sprintf("--prefix=%s", installDir),
"--buildtype=release",
"--default-library=static",
"--libdir=lib",
}
if lib.ConfigureArgs != nil {
args = append(args, lib.ConfigureArgs(runtime.GOOS)...)
}
return withBuildLog(buildDir, false, func(output io.Writer) error {
return runCommand(".", output, installDir, "meson", args...)
})
}
func (m *MesonBuild) Build(lib *Library, srcPath, buildDir string) error {
installDir := stagingDir(buildDir)
return withBuildLog(buildDir, true, func(output io.Writer) error {
// meson compile
if err := runCommand(buildDir, output, installDir, "meson", "compile"); err != nil {
return err
}
// meson install
return runCommand(buildDir, output, installDir, "meson", "install")
})
}
// CargoBuild implements the BuildSystem interface for Cargo/Rust-based builds
type CargoBuild struct {
InstallFunc func(srcPath, installDir string) error // Custom install function
}
func (c *CargoBuild) Configure(lib *Library, srcPath, buildDir, installDir string) error {
// Cargo doesn't have a separate configure step
return nil
}
func (c *CargoBuild) Build(lib *Library, srcPath, buildDir string) error {
installDir := stagingDir(buildDir)
// Custom install func handles the full cargo build process if provided
if c.InstallFunc != nil {
return c.InstallFunc(srcPath, installDir)
}
return nil
}
// MakefileBuild implements the BuildSystem interface for Makefile-based builds
type MakefileBuild struct {
Targets []string
InstallFunc func(srcPath, installDir string) error
}
func (m *MakefileBuild) Configure(lib *Library, srcPath, buildDir, installDir string) error {
// Makefile builds don't have a configure step
return nil
}
func (m *MakefileBuild) Build(lib *Library, srcPath, buildDir string) error {
installDir := stagingDir(buildDir)
return withBuildLog(buildDir, false, func(output io.Writer) error {
// Build the targets
args := append([]string{"-j", fmt.Sprintf("%d", runtime.NumCPU())}, m.Targets...)
if err := runCommand(srcPath, output, installDir, "make", args...); err != nil {
return err
}
// If a custom install function is provided, use it
if m.InstallFunc != nil {
return m.InstallFunc(srcPath, installDir)
}
return nil
})
}
// OpenSSLBuild implements the BuildSystem interface for OpenSSL's Configure/make
type OpenSSLBuild struct{}
func (o *OpenSSLBuild) Configure(lib *Library, srcPath, buildDir, installDir string) error {
// OpenSSL uses 'Configure' (capital C) Perl script, not autoconf
args := []string{
fmt.Sprintf("--prefix=%s", installDir),
fmt.Sprintf("--openssldir=%s", filepath.Join(installDir, "ssl")),
"--libdir=lib",
fmt.Sprintf("--with-zlib-include=%s/include", installDir),
fmt.Sprintf("--with-zlib-lib=%s/lib", installDir),
"zlib", // Enable zlib support
}
if lib.ConfigureArgs != nil {
args = append(args, lib.ConfigureArgs(runtime.GOOS)...)
}
// Run Configure from source directory
configurePath := "./Configure"
absConfigurePath := filepath.Join(srcPath, "Configure")
if !fileExists(absConfigurePath) {
return fmt.Errorf("Configure script not found at %s", absConfigurePath)
}
// Make Configure executable
if err := os.Chmod(absConfigurePath, 0755); err != nil {
return fmt.Errorf("failed to make Configure executable: %w", err)
}
return withBuildLog(buildDir, false, func(output io.Writer) error {
return runCommand(srcPath, output, installDir, configurePath, args...)
})
}
func (o *OpenSSLBuild) Build(lib *Library, srcPath, buildDir string) error {
installDir := stagingDir(buildDir)
return withBuildLog(buildDir, true, func(output io.Writer) error {
// make
if err := runCommand(srcPath, output, installDir, "make", "-j", fmt.Sprintf("%d", runtime.NumCPU())); err != nil {
return err
}
// make install_sw (install software only, skip docs)
return runCommand(srcPath, output, installDir, "make", "install_sw")
})
}