Skip to content

Commit 0a815fc

Browse files
authored
Parallel compiling: arduino-builder will leverage your multi core pc
If -jobs X param is specified and X is greater than 0, arduino-builder won't spawn more than X processes. Fixes arduino#17 Rebased from Federico Fissore branch arduino@0a3d888#diff-8f074e2b3fc911dbe8255891fc1a9b13R96 Remove an if which suppress some GCC error messages here: arduino@0a3d888#diff-8f074e2b3fc911dbe8255891fc1a9b13R96
1 parent 7883e01 commit 0a815fc

File tree

5 files changed

+166
-24
lines changed

5 files changed

+166
-24
lines changed

Diff for: src/arduino.cc/arduino-builder/main.go

+10
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import (
3737
"io/ioutil"
3838
"os"
3939
"os/exec"
40+
"runtime"
4041
"strings"
4142
"syscall"
4243

@@ -79,6 +80,7 @@ const FLAG_LOGGER_HUMANTAGS = "humantags"
7980
const FLAG_LOGGER_MACHINE = "machine"
8081
const FLAG_VERSION = "version"
8182
const FLAG_VID_PID = "vid-pid"
83+
const FLAG_JOBS = "jobs"
8284

8385
type foldersFlag []string
8486

@@ -136,6 +138,7 @@ var warningsLevelFlag *string
136138
var loggerFlag *string
137139
var versionFlag *bool
138140
var vidPidFlag *string
141+
var jobsFlag *int
139142

140143
func init() {
141144
compileFlag = flag.Bool(FLAG_ACTION_COMPILE, false, "compiles the given sketch")
@@ -159,6 +162,7 @@ func init() {
159162
loggerFlag = flag.String(FLAG_LOGGER, FLAG_LOGGER_HUMAN, "Sets type of logger. Available values are '"+FLAG_LOGGER_HUMAN+"', '"+FLAG_LOGGER_HUMANTAGS+"', '"+FLAG_LOGGER_MACHINE+"'")
160163
versionFlag = flag.Bool(FLAG_VERSION, false, "prints version and exits")
161164
vidPidFlag = flag.String(FLAG_VID_PID, "", "specify to use vid/pid specific build properties, as defined in boards.txt")
165+
jobsFlag = flag.Int(FLAG_JOBS, 0, "specify how many concurrent gcc processes should run at the same time. Defaults to the number of available cores on the running machine")
162166
}
163167

164168
func main() {
@@ -172,6 +176,12 @@ func main() {
172176
fmt.Println("warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.")
173177
return
174178
}
179+
180+
if *jobsFlag > 0 {
181+
runtime.GOMAXPROCS(*jobsFlag)
182+
} else {
183+
runtime.GOMAXPROCS(runtime.NumCPU())
184+
}
175185

176186
ctx := &types.Context{}
177187

Diff for: src/arduino.cc/builder/builder_utils/utils.go

+56-11
Original file line numberDiff line numberDiff line change
@@ -31,16 +31,17 @@ package builder_utils
3131

3232
import (
3333
"bytes"
34-
"fmt"
3534
"io"
3635
"os"
3736
"os/exec"
3837
"path/filepath"
3938
"strings"
39+
"sync"
4040

4141
"arduino.cc/builder/constants"
4242
"arduino.cc/builder/i18n"
4343
"arduino.cc/builder/utils"
44+
"arduino.cc/builder/types"
4445
"arduino.cc/properties"
4546
)
4647

@@ -146,16 +147,49 @@ func findAllFilesInFolder(sourcePath string, recurse bool) ([]string, error) {
146147
}
147148

148149
func compileFilesWithRecipe(objectFiles []string, sourcePath string, sources []string, buildPath string, buildProperties properties.Map, includes []string, recipe string, verbose bool, warningsLevel string, logger i18n.Logger) ([]string, error) {
150+
if len(sources) == 0 {
151+
return objectFiles, nil
152+
}
153+
objectFilesChan := make(chan string)
154+
errorsChan := make(chan error)
155+
doneChan := make(chan struct{})
156+
157+
var wg sync.WaitGroup
158+
wg.Add(len(sources))
159+
149160
for _, source := range sources {
150-
objectFile, err := compileFileWithRecipe(sourcePath, source, buildPath, buildProperties, includes, recipe, verbose, warningsLevel, logger)
151-
if err != nil {
152-
return nil, i18n.WrapError(err)
153-
}
161+
go func(source string) {
162+
defer wg.Done()
163+
objectFile, err := compileFileWithRecipe(sourcePath, source, buildPath, buildProperties, includes, recipe, verbose, warningsLevel, logger)
164+
if err != nil {
165+
errorsChan <- err
166+
} else {
167+
objectFilesChan <- objectFile
168+
}
169+
}(source)
170+
}
171+
172+
go func() {
173+
wg.Wait()
174+
doneChan <- struct{}{}
175+
}()
154176

155-
objectFiles = append(objectFiles, objectFile)
177+
for {
178+
select {
179+
case objectFile := <-objectFilesChan:
180+
objectFiles = append(objectFiles, objectFile)
181+
case err := <-errorsChan:
182+
return nil, i18n.WrapError(err)
183+
case <-doneChan:
184+
close(objectFilesChan)
185+
for objectFile := range objectFilesChan {
186+
objectFiles = append(objectFiles, objectFile)
187+
}
188+
return objectFiles, nil
189+
}
156190
}
157-
return objectFiles, nil
158191
}
192+
159193

160194
func compileFileWithRecipe(sourcePath string, source string, buildPath string, buildProperties properties.Map, includes []string, recipe string, verbose bool, warningsLevel string, logger i18n.Logger) (string, error) {
161195
properties := buildProperties.Clone()
@@ -361,11 +395,22 @@ func ExecRecipe(properties properties.Map, recipe string, removeUnsetProperties
361395
}
362396

363397
if echoOutput {
364-
command.Stdout = os.Stdout
398+
printToStdOut := func(data []byte) {
399+
logger.UnformattedWrite(os.Stdout, data)
400+
}
401+
stdout := &types.BufferedUntilNewLineWriter{PrintFunc: printToStdOut, Buffer: bytes.Buffer{}}
402+
defer stdout.Flush()
403+
command.Stdout = stdout
365404
}
366405

367-
command.Stderr = os.Stderr
368-
406+
printToStdErr := func(data []byte) {
407+
logger.UnformattedWrite(os.Stderr, data)
408+
}
409+
stderr := &types.BufferedUntilNewLineWriter{PrintFunc: printToStdErr, Buffer: bytes.Buffer{}}
410+
defer stderr.Flush()
411+
command.Stderr = stderr
412+
413+
369414
if echoOutput {
370415
err := command.Run()
371416
return nil, i18n.WrapError(err)
@@ -396,7 +441,7 @@ func PrepareCommandForRecipe(buildProperties properties.Map, recipe string, remo
396441
}
397442

398443
if echoCommandLine {
399-
fmt.Println(commandLine)
444+
logger.UnformattedFprintln(os.Stdout, commandLine)
400445
}
401446

402447
return command, nil

Diff for: src/arduino.cc/builder/ctags_runner.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
package builder
3131

3232
import (
33-
"fmt"
33+
"os"
3434

3535
"arduino.cc/builder/constants"
3636
"arduino.cc/builder/ctags"
@@ -63,7 +63,7 @@ func (s *CTagsRunner) Run(ctx *types.Context) error {
6363

6464
verbose := ctx.Verbose
6565
if verbose {
66-
fmt.Println(commandLine)
66+
logger.UnformattedFprintln(os.Stdout, commandLine)
6767
}
6868

6969
sourceBytes, err := command.Output()

Diff for: src/arduino.cc/builder/i18n/i18n.go

+64-11
Original file line numberDiff line numberDiff line change
@@ -38,12 +38,15 @@ import (
3838
"regexp"
3939
"strconv"
4040
"strings"
41+
"sync"
4142
)
4243

4344
var PLACEHOLDER = regexp.MustCompile("{(\\d)}")
4445

4546
type Logger interface {
4647
Fprintln(w io.Writer, level string, format string, a ...interface{})
48+
UnformattedFprintln(w io.Writer, s string)
49+
UnformattedWrite(w io.Writer, data []byte)
4750
Println(level string, format string, a ...interface{})
4851
Name() string
4952
}
@@ -52,6 +55,10 @@ type NoopLogger struct{}
5255

5356
func (s NoopLogger) Fprintln(w io.Writer, level string, format string, a ...interface{}) {}
5457

58+
func (s NoopLogger) UnformattedFprintln(w io.Writer, str string) {}
59+
60+
func (s NoopLogger) UnformattedWrite(w io.Writer, data []byte) {}
61+
5562
func (s NoopLogger) Println(level string, format string, a ...interface{}) {}
5663

5764
func (s NoopLogger) Name() string {
@@ -62,55 +69,101 @@ type HumanTagsLogger struct{}
6269

6370
func (s HumanTagsLogger) Fprintln(w io.Writer, level string, format string, a ...interface{}) {
6471
format = "[" + level + "] " + format
65-
fmt.Fprintln(w, Format(format, a...))
72+
fprintln(w, Format(format, a...))
6673
}
6774

6875
func (s HumanTagsLogger) Println(level string, format string, a ...interface{}) {
6976
s.Fprintln(os.Stdout, level, format, a...)
7077
}
7178

79+
func (s HumanTagsLogger) UnformattedFprintln(w io.Writer, str string) {
80+
fprintln(w, str)
81+
}
82+
83+
func (s HumanTagsLogger) UnformattedWrite(w io.Writer, data []byte) {
84+
write(w, data)
85+
}
86+
7287
func (s HumanTagsLogger) Name() string {
7388
return "humantags"
7489
}
7590

91+
7692
type HumanLogger struct{}
7793

7894
func (s HumanLogger) Fprintln(w io.Writer, level string, format string, a ...interface{}) {
79-
fmt.Fprintln(w, Format(format, a...))
95+
fprintln(w, Format(format, a...))
8096
}
8197

8298
func (s HumanLogger) Println(level string, format string, a ...interface{}) {
8399
s.Fprintln(os.Stdout, level, format, a...)
84100
}
85101

102+
func (s HumanLogger) UnformattedFprintln(w io.Writer, str string) {
103+
fprintln(w, str)
104+
}
105+
106+
func (s HumanLogger) UnformattedWrite(w io.Writer, data []byte) {
107+
write(w, data)
108+
}
109+
86110
func (s HumanLogger) Name() string {
87111
return "human"
88112
}
89113

114+
90115
type MachineLogger struct{}
91116

92-
func (s MachineLogger) printWithoutFormatting(w io.Writer, level string, format string, a []interface{}) {
117+
func (s MachineLogger) Fprintln(w io.Writer, level string, format string, a ...interface{}) {
118+
printMachineFormattedLogLine(w, level, format, a)
119+
}
120+
121+
func (s MachineLogger) Println(level string, format string, a ...interface{}) {
122+
printMachineFormattedLogLine(os.Stdout, level, format, a)
123+
}
124+
125+
func (s MachineLogger) UnformattedFprintln(w io.Writer, str string) {
126+
fprintln(w, str)
127+
}
128+
129+
func (s MachineLogger) Name() string {
130+
return "machine"
131+
}
132+
133+
func (s MachineLogger) UnformattedWrite(w io.Writer, data []byte) {
134+
write(w, data)
135+
}
136+
137+
138+
func printMachineFormattedLogLine(w io.Writer, level string, format string, a []interface{}) {
93139
a = append([]interface{}(nil), a...)
94140
for idx, value := range a {
95141
typeof := reflect.Indirect(reflect.ValueOf(value)).Kind()
96142
if typeof == reflect.String {
97143
a[idx] = url.QueryEscape(value.(string))
98144
}
99145
}
100-
fmt.Fprintf(w, "===%s ||| %s ||| %s", level, format, a)
101-
fmt.Fprintln(w)
146+
fprintf(w, "===%s ||| %s ||| %s\n", level, format, a)
102147
}
103148

104-
func (s MachineLogger) Fprintln(w io.Writer, level string, format string, a ...interface{}) {
105-
s.printWithoutFormatting(w, level, format, a)
149+
var lock sync.Mutex
150+
151+
func fprintln(w io.Writer, s string) {
152+
lock.Lock()
153+
defer lock.Unlock()
154+
fmt.Fprintln(w, s)
106155
}
107156

108-
func (s MachineLogger) Println(level string, format string, a ...interface{}) {
109-
s.printWithoutFormatting(os.Stdout, level, format, a)
157+
func write(w io.Writer, data []byte) {
158+
lock.Lock()
159+
defer lock.Unlock()
160+
w.Write(data)
110161
}
111162

112-
func (s MachineLogger) Name() string {
113-
return "machine"
163+
func fprintf(w io.Writer, format string, a ...interface{}) {
164+
lock.Lock()
165+
defer lock.Unlock()
166+
fmt.Fprintf(w, format, a...)
114167
}
115168

116169
func FromJavaToGoSyntax(s string) string {

Diff for: src/arduino.cc/builder/types/accessories.go

+34
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,11 @@
2929

3030
package types
3131

32+
import (
33+
"bytes"
34+
"sync"
35+
)
36+
3237
type UniqueStringQueue []string
3338

3439
func (queue UniqueStringQueue) Len() int { return len(queue) }
@@ -74,3 +79,32 @@ func (queue *UniqueSourceFileQueue) Pop() SourceFile {
7479
func (queue *UniqueSourceFileQueue) Empty() bool {
7580
return queue.Len() == 0
7681
}
82+
83+
type BufferedUntilNewLineWriter struct {
84+
PrintFunc PrintFunc
85+
Buffer bytes.Buffer
86+
lock sync.Mutex
87+
}
88+
89+
type PrintFunc func([]byte)
90+
91+
func (w *BufferedUntilNewLineWriter) Write(p []byte) (n int, err error) {
92+
w.lock.Lock()
93+
defer w.lock.Unlock()
94+
95+
writtenToBuffer, err := w.Buffer.Write(p)
96+
return writtenToBuffer, err
97+
}
98+
99+
func (w *BufferedUntilNewLineWriter) Flush() {
100+
w.lock.Lock()
101+
defer w.lock.Unlock()
102+
103+
remainingBytes := w.Buffer.Bytes()
104+
if len(remainingBytes) > 0 {
105+
if remainingBytes[len(remainingBytes)-1] != '\n' {
106+
remainingBytes = append(remainingBytes, '\n')
107+
}
108+
w.PrintFunc(remainingBytes)
109+
}
110+
}

0 commit comments

Comments
 (0)