-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathapi.go
More file actions
147 lines (123 loc) · 4.1 KB
/
api.go
File metadata and controls
147 lines (123 loc) · 4.1 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
package orchideous
import (
"bytes"
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
)
// BuildResult contains the results of a build operation.
type BuildResult struct {
OutputExecutable string // name of the produced executable (relative to sourceDir)
Output []byte // combined compiler output (stdout+stderr)
CommandsRun []string // shell-style command strings that were executed
}
// Build compiles the C/C++ project in the given directory.
// It detects sources, dependencies, and required flags automatically.
// The sourceDir is the directory containing the source files.
func Build(sourceDir string, opts BuildOptions) (BuildResult, error) {
var result BuildResult
// Save and restore the working directory
origDir, err := os.Getwd()
if err != nil {
return result, fmt.Errorf("could not get working directory: %w", err)
}
if err := os.Chdir(sourceDir); err != nil {
return result, fmt.Errorf("could not change to source directory: %w", err)
}
defer os.Chdir(origDir)
proj := detectProject()
// Auto-detect win64 from source
if proj.HasWin64 && !opts.Win64 {
opts.Win64 = true
}
if proj.MainSource == "" {
return result, fmt.Errorf("no source files found")
}
exe := executableName()
if opts.Win64 || proj.HasWin64 {
exe += ".exe"
}
result.OutputExecutable = exe
flags := assembleFlags(proj, opts)
// Override directory defines with install paths if InstallPrefix is set
if opts.InstallPrefix != "" {
flags.Defines = installDirDefines(opts.InstallPrefix)
}
srcs := append([]string{proj.MainSource}, proj.DepSources...)
output, cmds, err := compileSourcesCaptured(srcs, exe, flags)
result.Output = output
result.CommandsRun = cmds
if err != nil {
recommendPackage(proj.Includes)
return result, err
}
return result, nil
}
// compileSourcesCaptured compiles and links the given source files, capturing
// all output instead of printing to stdout/stderr. Returns combined output,
// the list of commands that were run, and any error.
func compileSourcesCaptured(srcs []string, output string, flags BuildFlags) ([]byte, []string, error) {
var combinedOutput bytes.Buffer
var commandsRun []string
// For a single source file, compile directly
if len(srcs) == 1 {
args := buildCompileArgs(flags, srcs, output)
cmd := exec.Command(flags.Compiler, args...)
commandsRun = append(commandsRun, cmdToString(cmd))
cmdOutput, err := cmd.CombinedOutput()
combinedOutput.Write(cmdOutput)
if err != nil {
return combinedOutput.Bytes(), commandsRun, fmt.Errorf("compilation failed: %w", err)
}
return combinedOutput.Bytes(), commandsRun, nil
}
// Incremental: compile each source to .o, then link
var objFiles []string
needLink := false
for _, src := range srcs {
obj := strings.TrimSuffix(src, filepath.Ext(src)) + ".o"
objFiles = append(objFiles, obj)
if !needsRecompile(src, obj) {
continue
}
needLink = true
args := []string{"-std=" + flags.Std, "-MMD"}
args = append(args, flags.CFlags...)
args = append(args, flags.Defines...)
for _, ip := range flags.IncPaths {
args = append(args, "-I"+ip)
}
args = append(args, "-c", "-o", obj, src)
cmd := exec.Command(flags.Compiler, args...)
commandsRun = append(commandsRun, cmdToString(cmd))
cmdOutput, err := cmd.CombinedOutput()
combinedOutput.Write(cmdOutput)
if err != nil {
return combinedOutput.Bytes(), commandsRun, fmt.Errorf("compiling %s: %w", src, err)
}
}
if !fileExists(output) {
needLink = true
}
if !needLink {
return combinedOutput.Bytes(), commandsRun, nil
}
// Link
args := []string{"-o", output}
args = append(args, objFiles...)
args = append(args, flags.LDFlags...)
cmd := exec.Command(flags.Compiler, args...)
commandsRun = append(commandsRun, cmdToString(cmd))
cmdOutput, err := cmd.CombinedOutput()
combinedOutput.Write(cmdOutput)
if err != nil {
return combinedOutput.Bytes(), commandsRun, fmt.Errorf("linking failed: %w", err)
}
return combinedOutput.Bytes(), commandsRun, nil
}
// cmdToString returns a shell-style string representation of a command.
func cmdToString(cmd *exec.Cmd) string {
return cmd.Path + " " + strings.Join(cmd.Args[1:], " ")
}