Skip to content

Commit c7f55b5

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 - Add HumanTagsLogger compatibility - Remove an if which suppress some GCC error messages here: arduino@0a3d888#diff-8f074e2b3fc911dbe8255891fc1a9b13R96
1 parent 7883e01 commit c7f55b5

File tree

5 files changed

+159
-22
lines changed

5 files changed

+159
-22
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() {
@@ -173,6 +177,12 @@ func main() {
173177
return
174178
}
175179

180+
if *jobsFlag > 0 {
181+
runtime.GOMAXPROCS(*jobsFlag)
182+
} else {
183+
runtime.GOMAXPROCS(runtime.NumCPU())
184+
}
185+
176186
ctx := &types.Context{}
177187

178188
if *buildOptionsFileFlag != "" {

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

+52-9
Original file line numberDiff line numberDiff line change
@@ -31,15 +31,16 @@ 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"
43+
"arduino.cc/builder/types"
4344
"arduino.cc/builder/utils"
4445
"arduino.cc/properties"
4546
)
@@ -146,15 +147,47 @@ 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 {
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+
}()
176+
177+
for {
178+
select {
179+
case objectFile := <-objectFilesChan:
180+
objectFiles = append(objectFiles, objectFile)
181+
case err := <-errorsChan:
152182
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
153189
}
154-
155-
objectFiles = append(objectFiles, objectFile)
156190
}
157-
return objectFiles, nil
158191
}
159192

160193
func compileFileWithRecipe(sourcePath string, source string, buildPath string, buildProperties properties.Map, includes []string, recipe string, verbose bool, warningsLevel string, logger i18n.Logger) (string, error) {
@@ -361,10 +394,20 @@ func ExecRecipe(properties properties.Map, recipe string, removeUnsetProperties
361394
}
362395

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

367-
command.Stderr = os.Stderr
405+
printToStdErr := func(data []byte) {
406+
logger.UnformattedWrite(os.Stderr, data)
407+
}
408+
stderr := &types.BufferedUntilNewLineWriter{PrintFunc: printToStdErr, Buffer: bytes.Buffer{}}
409+
defer stderr.Flush()
410+
command.Stderr = stderr
368411

369412
if echoOutput {
370413
err := command.Run()
@@ -396,7 +439,7 @@ func PrepareCommandForRecipe(buildProperties properties.Map, recipe string, remo
396439
}
397440

398441
if echoCommandLine {
399-
fmt.Println(commandLine)
442+
logger.UnformattedFprintln(os.Stdout, commandLine)
400443
}
401444

402445
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

+61-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,98 @@ 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

7691
type HumanLogger struct{}
7792

7893
func (s HumanLogger) Fprintln(w io.Writer, level string, format string, a ...interface{}) {
79-
fmt.Fprintln(w, Format(format, a...))
94+
fprintln(w, Format(format, a...))
8095
}
8196

8297
func (s HumanLogger) Println(level string, format string, a ...interface{}) {
8398
s.Fprintln(os.Stdout, level, format, a...)
8499
}
85100

101+
func (s HumanLogger) UnformattedFprintln(w io.Writer, str string) {
102+
fprintln(w, str)
103+
}
104+
105+
func (s HumanLogger) UnformattedWrite(w io.Writer, data []byte) {
106+
write(w, data)
107+
}
108+
86109
func (s HumanLogger) Name() string {
87110
return "human"
88111
}
89112

90113
type MachineLogger struct{}
91114

92-
func (s MachineLogger) printWithoutFormatting(w io.Writer, level string, format string, a []interface{}) {
115+
func (s MachineLogger) Fprintln(w io.Writer, level string, format string, a ...interface{}) {
116+
printMachineFormattedLogLine(w, level, format, a)
117+
}
118+
119+
func (s MachineLogger) Println(level string, format string, a ...interface{}) {
120+
printMachineFormattedLogLine(os.Stdout, level, format, a)
121+
}
122+
123+
func (s MachineLogger) UnformattedFprintln(w io.Writer, str string) {
124+
fprintln(w, str)
125+
}
126+
127+
func (s MachineLogger) Name() string {
128+
return "machine"
129+
}
130+
131+
func (s MachineLogger) UnformattedWrite(w io.Writer, data []byte) {
132+
write(w, data)
133+
}
134+
135+
func printMachineFormattedLogLine(w io.Writer, level string, format string, a []interface{}) {
93136
a = append([]interface{}(nil), a...)
94137
for idx, value := range a {
95138
typeof := reflect.Indirect(reflect.ValueOf(value)).Kind()
96139
if typeof == reflect.String {
97140
a[idx] = url.QueryEscape(value.(string))
98141
}
99142
}
100-
fmt.Fprintf(w, "===%s ||| %s ||| %s", level, format, a)
101-
fmt.Fprintln(w)
143+
fprintf(w, "===%s ||| %s ||| %s\n", level, format, a)
102144
}
103145

104-
func (s MachineLogger) Fprintln(w io.Writer, level string, format string, a ...interface{}) {
105-
s.printWithoutFormatting(w, level, format, a)
146+
var lock sync.Mutex
147+
148+
func fprintln(w io.Writer, s string) {
149+
lock.Lock()
150+
defer lock.Unlock()
151+
fmt.Fprintln(w, s)
106152
}
107153

108-
func (s MachineLogger) Println(level string, format string, a ...interface{}) {
109-
s.printWithoutFormatting(os.Stdout, level, format, a)
154+
func write(w io.Writer, data []byte) {
155+
lock.Lock()
156+
defer lock.Unlock()
157+
w.Write(data)
110158
}
111159

112-
func (s MachineLogger) Name() string {
113-
return "machine"
160+
func fprintf(w io.Writer, format string, a ...interface{}) {
161+
lock.Lock()
162+
defer lock.Unlock()
163+
fmt.Fprintf(w, format, a...)
114164
}
115165

116166
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)