-
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmain.go
More file actions
489 lines (417 loc) · 13.3 KB
/
main.go
File metadata and controls
489 lines (417 loc) · 13.3 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
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
package main
import (
"bytes"
"fmt"
"io"
"log"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
)
func main() {
// Initialize FFmpeg library enables after AllLibraries is fully defined
CollectFFmpegEnables()
// Derive output path based on architecture
arch := runtime.GOARCH
targetOutput := filepath.Join("lib", runtime.GOOS+"_"+arch, "libffmpeg.a")
targetOutput, err := filepath.Abs(targetOutput)
if err != nil {
log.Fatalf("Failed to get absolute path for output: %v\n", err)
}
// Parse arguments
selectedLibs := make(map[string]bool)
cleanBuild := false
listMode := false
for _, arg := range os.Args[1:] {
if arg == "--clean" {
cleanBuild = true
} else if arg == "--list" {
listMode = true
} else {
selectedLibs[arg] = true
}
}
// Handle --list mode: display library information and exit
if listMode {
printLibraryList(AllLibraries)
return
}
// Setup directories
buildRoot, err := filepath.Abs(".build")
if err != nil {
log.Fatalf("Failed to get absolute path for build root: %v\n", err)
}
stagingDir := filepath.Join(buildRoot, "staging")
// Create directories (do NOT delete - incremental builds!)
dirs := []string{
filepath.Join(buildRoot, "downloads"),
filepath.Join(buildRoot, "build"),
filepath.Join(stagingDir, "lib"),
filepath.Join(stagingDir, "include"),
}
for _, dir := range dirs {
if err := os.MkdirAll(dir, 0755); err != nil {
log.Fatalf("Failed to create directory %s: %v\n", dir, err)
}
}
// Filter libraries based on selection
libs := AllLibraries
if len(selectedLibs) > 0 {
filtered := []*Library{}
for _, lib := range AllLibraries {
if selectedLibs[lib.Name] {
filtered = append(filtered, lib)
}
}
libs = filtered
}
// Handle --clean mode: clean and exit
if cleanBuild {
total := len(libs)
for i, lib := range libs {
fmt.Printf("[%d/%d] Cleaning %s...\n", i+1, total, lib.Name)
libBuildDir := filepath.Join(buildRoot, "build", lib.Name)
libSrcDir := filepath.Join(buildRoot, "src", lib.Name)
os.RemoveAll(libBuildDir)
os.RemoveAll(libSrcDir)
// Also remove installed libraries from staging
if lib.LinkLibs != nil {
for _, libName := range lib.LinkLibs {
for _, dir := range []string{"lib"} {
libPath := filepath.Join(stagingDir, dir, libName+".a")
if fileExists(libPath) {
os.Remove(libPath)
}
}
}
}
}
fmt.Printf("\n✓ Cleaned %d libraries\n", total)
return
}
// Build all libraries
total := len(libs)
built := 0
for i, lib := range libs {
fmt.Printf("\n[%d/%d] %s\n", i+1, total, lib.Name)
fmt.Println(strings.Repeat("=", 60))
// Create per-library logger
logDir := filepath.Join(buildRoot, "build", lib.Name)
os.MkdirAll(logDir, 0755)
logFile := filepath.Join(logDir, "build.log")
logFileWriter, err := os.Create(logFile)
if err != nil {
log.Fatalf("Failed to create log file for %s: %v\n", lib.Name, err)
}
// Use MultiWriter to send output to both stdout and log file
logger := io.MultiWriter(os.Stdout, logFileWriter)
if err := lib.Build(buildRoot, stagingDir, logger); err != nil {
logFileWriter.Close()
log.Fatalf("Build failed for %s: %v\nSee log: %s\n", lib.Name, err, logFile)
}
logFileWriter.Close()
if lib.ShouldBuild() {
built++
}
}
fmt.Printf("\n%s\n", strings.Repeat("=", 60))
fmt.Printf("Built %d/%d libraries\n", built, total)
fmt.Println(strings.Repeat("=", 60))
// Only combine libraries on a full build (no library filters)
if len(selectedLibs) == 0 {
if err := combineLibraries(libs, stagingDir, targetOutput); err != nil {
log.Fatalf("Failed to combine libraries: %v\n", err)
}
fmt.Printf("\n✓ Success! Output: %s\n", targetOutput)
} else {
fmt.Printf("\n✓ Success! Built %d selected libraries\n", len(selectedLibs))
}
}
// combineLibraries combines all built libraries into a single static library
func combineLibraries(libs []*Library, stagingDir, output string) error {
// Collect library files from LinkLibs of all built libraries
var libFiles []string
linkLibsMap := make(map[string]bool) // Track which libs we need
// Collect all LinkLibs from built libraries
for _, lib := range libs {
if !lib.ShouldBuild() {
continue // Skip libraries that shouldn't be built on this platform
}
for _, linkLib := range lib.LinkLibs {
linkLibsMap[linkLib] = true
}
}
// Search for the specific .a files in lib
libDirs := []string{
filepath.Join(stagingDir, "lib"),
}
for _, libDir := range libDirs {
entries, err := os.ReadDir(libDir)
if err != nil {
if os.IsNotExist(err) {
continue
}
return err
}
for _, entry := range entries {
if !strings.HasSuffix(entry.Name(), ".a") {
continue
}
// Check if this .a file matches one of our LinkLibs
// Library files are named like "libfoo.a", so we check against the base name without .a
baseName := strings.TrimSuffix(entry.Name(), ".a")
if linkLibsMap[baseName] {
libFiles = append(libFiles, filepath.Join(libDir, entry.Name()))
}
}
}
if len(libFiles) == 0 {
return fmt.Errorf("no static libraries found in %s", stagingDir)
}
log.Printf("Combining %d libraries into %s\n", len(libFiles), output)
// Platform-specific merge
if runtime.GOOS == "darwin" {
return combineMac(libFiles, output)
}
return combineLinux(libFiles, output)
}
// combineMac uses Apple's libtool to combine static libraries on macOS
// This is more efficient than ar as it doesn't require extracting all object files
func combineMac(libFiles []string, output string) error {
log.Println("Using Apple libtool -static approach (macOS)")
// Ensure output directory exists
outputDir := filepath.Dir(output)
if err := os.MkdirAll(outputDir, 0755); err != nil {
return fmt.Errorf("failed to create output directory: %w", err)
}
// Remove existing output file if present
os.Remove(output)
// Use Apple's libtool (not GNU libtool from Nix) to combine libraries directly
// Apple's libtool -static is specifically designed for this purpose
args := append([]string{"-static", "-o", output}, libFiles...)
libtoolCmd := exec.Command("/usr/bin/libtool", args...)
// Capture output for debugging
var stdout, stderr bytes.Buffer
libtoolCmd.Stdout = &stdout
libtoolCmd.Stderr = &stderr
if err := libtoolCmd.Run(); err != nil {
return fmt.Errorf("libtool failed: %w\nstdout: %s\nstderr: %s", err, stdout.String(), stderr.String())
}
// Strip the library to reduce size
if err := exec.Command("strip", "-S", output).Run(); err != nil {
return fmt.Errorf("strip failed: %w", err)
}
return nil
}
// combineLinux uses ar to combine static libraries on Linux
func combineLinux(libFiles []string, output string) error {
log.Println("Using thin archive approach to merge libraries (Linux)")
// Stack Overflow solution: create thin archive (low memory), then convert to normal archive
// Thin archives use pointers instead of copying data, avoiding memory exhaustion
// Source: https://stackoverflow.com/a/23621751
var stderr bytes.Buffer
// Ensure output directory exists
outputDir := filepath.Dir(output)
if err := os.MkdirAll(outputDir, 0755); err != nil {
return fmt.Errorf("failed to create output directory: %w", err)
}
// Remove existing output file if present
os.Remove(output)
// Step 1: Create thin archive with -T flag (pointers only, minimal memory)
log.Println(" Creating thin archive...")
args := append([]string{"cqT", output}, libFiles...)
thinCmd := exec.Command("ar", args...)
thinCmd.Stderr = &stderr
stderr.Reset()
if err := thinCmd.Run(); err != nil {
return fmt.Errorf("thin archive creation failed: %w (stderr: %s)", err, stderr.String())
}
// Step 2: Convert thin archive to normal archive using MRI script
// This extracts and rebuilds, but from a single thin archive (less memory than 32 separate libraries)
log.Println(" Converting to normal archive...")
mriScript := fmt.Sprintf("create %s.tmp\naddlib %s\nsave\nend", output, output)
convertCmd := exec.Command("ar", "-M")
convertCmd.Stdin = bytes.NewBufferString(mriScript)
convertCmd.Stderr = &stderr
stderr.Reset()
if err := convertCmd.Run(); err != nil {
return fmt.Errorf("thin archive conversion failed: %w (stderr: %s)", err, stderr.String())
}
// Replace original with converted archive
if err := os.Rename(output+".tmp", output); err != nil {
return fmt.Errorf("failed to rename converted archive: %w", err)
}
// Strip the library
stripCmd := exec.Command("strip", "--strip-unneeded", output)
stripCmd.Stderr = &stderr
stderr.Reset()
if err := stripCmd.Run(); err != nil {
return fmt.Errorf("strip failed: %w (stderr: %s)", err, stderr.String())
}
return nil
}
// ANSI color codes
const (
colorReset = "\033[0m"
colorBold = "\033[1m"
colorRed = "\033[31m"
colorGreen = "\033[32m"
colorYellow = "\033[33m"
colorBlue = "\033[34m"
colorCyan = "\033[36m"
colorGray = "\033[90m"
)
// colorize wraps text with an ANSI colour code and reset.
func colorize(text, color string) string {
return color + text + colorReset
}
// bold wraps text with ANSI bold formatting.
func bold(text string) string {
return colorBold + text + colorReset
}
// tableCell formats a left-aligned cell with the given width and colour.
func tableCell(text string, width int, color string) string {
return fmt.Sprintf("%s%-*s%s", color, width, text, colorReset)
}
// tableBorder returns a horizontal border segment of the given width.
func tableBorder(width int) string {
return strings.Repeat("═", width+2)
}
// printLibraryList displays a formatted table of all libraries
func printLibraryList(libs []*Library) {
// Calculate column widths
maxName := len("Library")
maxPlatform := len("Platform")
maxBuildSys := len("Build System")
maxLinkLibs := len("Link Libraries")
for _, lib := range libs {
if len(lib.Name) > maxName {
maxName = len(lib.Name)
}
platform := getPlatformString(lib)
if len(platform) > maxPlatform {
maxPlatform = len(platform)
}
buildSys := getBuildSystemString(lib)
if len(buildSys) > maxBuildSys {
maxBuildSys = len(buildSys)
}
linkLibs := getLinkLibsString(lib)
if len(linkLibs) > maxLinkLibs {
maxLinkLibs = len(linkLibs)
}
}
// Print header
fmt.Printf("\n%s╔═%s╦%s╦%s╦%s╗%s\n",
colorize("", colorBold+colorCyan),
tableBorder(maxName+3),
tableBorder(maxPlatform),
tableBorder(maxBuildSys),
tableBorder(maxLinkLibs),
colorReset)
fmt.Printf("%s║%s %s %s %s ║%s %s %s║%s %s %s║%s %s %s║%s\n",
colorCyan, colorReset,
tableCell("#", 2, colorBold+colorYellow),
tableCell("Library", maxName, colorBold+colorYellow),
colorCyan, colorReset,
tableCell("Platform", maxPlatform, colorReset),
colorCyan, colorReset,
tableCell("Build System", maxBuildSys, colorReset),
colorCyan, colorReset,
tableCell("Link Libraries", maxLinkLibs, colorReset),
colorCyan, colorReset)
fmt.Printf("%s╠═%s╬%s╬%s╬%s╣%s\n",
colorCyan,
tableBorder(maxName+3),
tableBorder(maxPlatform),
tableBorder(maxBuildSys),
tableBorder(maxLinkLibs),
colorReset)
// Print rows
for i, lib := range libs {
num := fmt.Sprintf("%2d", i+1)
platform := getPlatformString(lib)
buildSys := getBuildSystemString(lib)
// Get link libs display string (without embedded colors for now)
var linkLibsDisplay string
if lib.LinkLibs == nil {
linkLibsDisplay = "(headers only)"
} else if len(lib.LinkLibs) == 0 {
linkLibsDisplay = "-"
} else {
linkLibsDisplay = strings.Join(lib.LinkLibs, ", ")
}
// Color code based on library type
nameColor := colorGreen
linkLibsColor := colorReset
if lib.LinkLibs == nil {
nameColor = colorGray // Header-only libraries in gray
linkLibsColor = colorGray
}
fmt.Printf("%s║%s %s %s %s ║%s %s %s║%s %s %s║%s %s %s║%s\n",
colorCyan, colorReset,
tableCell(num, 2, colorBlue+colorBold),
tableCell(lib.Name, maxName, nameColor),
colorCyan, colorReset,
tableCell(platform, maxPlatform, colorReset),
colorCyan, colorReset,
tableCell(buildSys, maxBuildSys, colorReset),
colorCyan, colorReset,
tableCell(linkLibsDisplay, maxLinkLibs, linkLibsColor),
colorCyan, colorReset)
}
// Print footer
fmt.Printf("%s╚═%s╩%s╩%s╩%s╝%s\n\n",
colorCyan,
tableBorder(maxName+3),
tableBorder(maxPlatform),
tableBorder(maxBuildSys),
tableBorder(maxLinkLibs),
colorReset)
// Summary
totalLibs := len(libs)
headerOnly := 0
for _, lib := range libs {
if lib.LinkLibs == nil {
headerOnly++
}
}
fmt.Printf("%sTotal: %d libraries (%d build, %d header-only)%s\n",
colorBold, totalLibs, totalLibs-headerOnly, headerOnly, colorReset)
}
// getPlatformString returns a formatted platform string
func getPlatformString(lib *Library) string {
if len(lib.Platform) == 0 {
return "-"
}
return strings.Join(lib.Platform, ", ")
}
// getBuildSystemString returns the build system type name
func getBuildSystemString(lib *Library) string {
switch lib.BuildSystem.(type) {
case *AutoconfBuild:
return "autoconf"
case *CMakeBuild:
return "cmake"
case *MesonBuild:
return "meson"
case *CargoBuild:
return "cargo"
case *MakefileBuild:
return "makefile"
default:
return "unknown"
}
}
// getLinkLibsString returns the display length of link libraries for column width calculation
func getLinkLibsString(lib *Library) string {
if lib.LinkLibs == nil {
return "(headers only)"
}
if len(lib.LinkLibs) == 0 {
return "-"
}
return strings.Join(lib.LinkLibs, ", ")
}