Skip to content

Commit 0a3d888

Browse files
committed
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 #17 Signed-off-by: Federico Fissore <[email protected]>
1 parent 269fb70 commit 0a3d888

File tree

5 files changed

+159
-20
lines changed

5 files changed

+159
-20
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

@@ -77,6 +78,7 @@ const FLAG_LOGGER_HUMAN = "human"
7778
const FLAG_LOGGER_MACHINE = "machine"
7879
const FLAG_VERSION = "version"
7980
const FLAG_VID_PID = "vid-pid"
81+
const FLAG_JOBS = "jobs"
8082

8183
type foldersFlag []string
8284

@@ -133,6 +135,7 @@ var warningsLevelFlag *string
133135
var loggerFlag *string
134136
var versionFlag *bool
135137
var vidPidFlag *string
138+
var jobsFlag *int
136139

137140
func init() {
138141
compileFlag = flag.Bool(FLAG_ACTION_COMPILE, false, "compiles the given sketch")
@@ -155,6 +158,7 @@ func init() {
155158
loggerFlag = flag.String(FLAG_LOGGER, FLAG_LOGGER_HUMAN, "Sets type of logger. Available values are '"+FLAG_LOGGER_HUMAN+"', '"+FLAG_LOGGER_MACHINE+"'")
156159
versionFlag = flag.Bool(FLAG_VERSION, false, "prints version and exits")
157160
vidPidFlag = flag.String(FLAG_VID_PID, "", "specify to use vid/pid specific build properties, as defined in boards.txt")
161+
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")
158162
}
159163

160164
func main() {
@@ -169,6 +173,12 @@ func main() {
169173
return
170174
}
171175

176+
if *jobsFlag > 0 {
177+
runtime.GOMAXPROCS(*jobsFlag)
178+
} else {
179+
runtime.GOMAXPROCS(runtime.NumCPU())
180+
}
181+
172182
ctx := &types.Context{}
173183

174184
if *buildOptionsFileFlag != "" {

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

+52-8
Original file line numberDiff line numberDiff line change
@@ -31,14 +31,15 @@ package builder_utils
3131

3232
import (
3333
"bytes"
34-
"fmt"
3534
"os"
3635
"os/exec"
3736
"path/filepath"
3837
"strings"
38+
"sync"
3939

4040
"arduino.cc/builder/constants"
4141
"arduino.cc/builder/i18n"
42+
"arduino.cc/builder/types"
4243
"arduino.cc/builder/utils"
4344
"arduino.cc/properties"
4445
)
@@ -117,15 +118,48 @@ func findFilesInFolder(sourcePath string, extension string, recurse bool) ([]str
117118
}
118119

119120
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) {
121+
if len(sources) == 0 {
122+
return objectFiles, nil
123+
}
124+
objectFilesChan := make(chan string)
125+
errorsChan := make(chan error)
126+
doneChan := make(chan struct{})
127+
128+
var wg sync.WaitGroup
129+
wg.Add(len(sources))
130+
120131
for _, source := range sources {
121-
objectFile, err := compileFileWithRecipe(sourcePath, source, buildPath, buildProperties, includes, recipe, verbose, warningsLevel, logger)
122-
if err != nil {
132+
go func(source string) {
133+
defer wg.Done()
134+
objectFile, err := compileFileWithRecipe(sourcePath, source, buildPath, buildProperties, includes, recipe, verbose, warningsLevel, logger)
135+
if err != nil {
136+
errorsChan <- err
137+
} else {
138+
objectFilesChan <- objectFile
139+
}
140+
}(source)
141+
}
142+
143+
go func() {
144+
wg.Wait()
145+
doneChan <- struct{}{}
146+
}()
147+
148+
for {
149+
select {
150+
case objectFile := <-objectFilesChan:
151+
objectFiles = append(objectFiles, objectFile)
152+
case err := <-errorsChan:
123153
return nil, i18n.WrapError(err)
154+
case <-doneChan:
155+
close(objectFilesChan)
156+
for objectFile := range objectFilesChan {
157+
objectFiles = append(objectFiles, objectFile)
158+
}
159+
return objectFiles, nil
124160
}
125161

126-
objectFiles = append(objectFiles, objectFile)
127162
}
128-
return objectFiles, nil
129163
}
130164

131165
func compileFileWithRecipe(sourcePath string, source string, buildPath string, buildProperties properties.Map, includes []string, recipe string, verbose bool, warningsLevel string, logger i18n.Logger) (string, error) {
@@ -310,10 +344,20 @@ func ExecRecipe(properties properties.Map, recipe string, removeUnsetProperties
310344
}
311345

312346
if echoOutput {
313-
command.Stdout = os.Stdout
347+
printToStdOut := func(data []byte) {
348+
logger.UnformattedWrite(os.Stdout, data)
349+
}
350+
stdout := &types.BufferedUntilNewLineWriter{PrintFunc: printToStdOut, Buffer: bytes.Buffer{}}
351+
defer stdout.Flush()
352+
command.Stdout = stdout
314353
}
315354

316-
command.Stderr = os.Stderr
355+
printToStdErr := func(data []byte) {
356+
logger.UnformattedWrite(os.Stderr, data)
357+
}
358+
stderr := &types.BufferedUntilNewLineWriter{PrintFunc: printToStdErr, Buffer: bytes.Buffer{}}
359+
defer stderr.Flush()
360+
command.Stderr = stderr
317361

318362
if echoOutput {
319363
err := command.Run()
@@ -345,7 +389,7 @@ func PrepareCommandForRecipe(buildProperties properties.Map, recipe string, remo
345389
}
346390

347391
if echoCommandLine {
348-
fmt.Println(commandLine)
392+
logger.UnformattedFprintln(os.Stdout, commandLine)
349393
}
350394

351395
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

+52-10
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 {
@@ -61,41 +68,76 @@ func (s NoopLogger) Name() string {
6168
type HumanLogger struct{}
6269

6370
func (s HumanLogger) Fprintln(w io.Writer, level string, format string, a ...interface{}) {
64-
fmt.Fprintln(w, Format(format, a...))
71+
fprintln(w, Format(format, a...))
6572
}
6673

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

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

7590
type MachineLogger struct{}
7691

77-
func (s MachineLogger) printWithoutFormatting(w io.Writer, level string, format string, a []interface{}) {
92+
func (s MachineLogger) Fprintln(w io.Writer, level string, format string, a ...interface{}) {
93+
printMachineFormattedLogLine(w, level, format, a)
94+
}
95+
96+
func (s MachineLogger) Println(level string, format string, a ...interface{}) {
97+
printMachineFormattedLogLine(os.Stdout, level, format, a)
98+
}
99+
100+
func (s MachineLogger) UnformattedFprintln(w io.Writer, str string) {
101+
fprintln(w, str)
102+
}
103+
104+
func (s MachineLogger) Name() string {
105+
return "machine"
106+
}
107+
108+
func (s MachineLogger) UnformattedWrite(w io.Writer, data []byte) {
109+
write(w, data)
110+
}
111+
112+
func printMachineFormattedLogLine(w io.Writer, level string, format string, a []interface{}) {
78113
a = append([]interface{}(nil), a...)
79114
for idx, value := range a {
80115
typeof := reflect.Indirect(reflect.ValueOf(value)).Kind()
81116
if typeof == reflect.String {
82117
a[idx] = url.QueryEscape(value.(string))
83118
}
84119
}
85-
fmt.Fprintf(w, "===%s ||| %s ||| %s", level, format, a)
86-
fmt.Fprintln(w)
120+
fprintf(w, "===%s ||| %s ||| %s\n", level, format, a)
87121
}
88122

89-
func (s MachineLogger) Fprintln(w io.Writer, level string, format string, a ...interface{}) {
90-
s.printWithoutFormatting(w, level, format, a)
123+
var lock sync.Mutex
124+
125+
func fprintln(w io.Writer, s string) {
126+
lock.Lock()
127+
defer lock.Unlock()
128+
fmt.Fprintln(w, s)
91129
}
92130

93-
func (s MachineLogger) Println(level string, format string, a ...interface{}) {
94-
s.printWithoutFormatting(os.Stdout, level, format, a)
131+
func write(w io.Writer, data []byte) {
132+
lock.Lock()
133+
defer lock.Unlock()
134+
w.Write(data)
95135
}
96136

97-
func (s MachineLogger) Name() string {
98-
return "machine"
137+
func fprintf(w io.Writer, format string, a ...interface{}) {
138+
lock.Lock()
139+
defer lock.Unlock()
140+
fmt.Fprintf(w, format, a...)
99141
}
100142

101143
func FromJavaToGoSyntax(s string) string {

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

+43
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,41 @@ 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+
if err != nil {
97+
return writtenToBuffer, err
98+
}
99+
100+
bytesUntilNewLine, err := w.Buffer.ReadBytes('\n')
101+
if err == nil {
102+
w.PrintFunc(bytesUntilNewLine)
103+
}
104+
105+
return writtenToBuffer, err
106+
}
107+
108+
func (w *BufferedUntilNewLineWriter) Flush() {
109+
w.lock.Lock()
110+
defer w.lock.Unlock()
111+
112+
remainingBytes := w.Buffer.Bytes()
113+
if len(remainingBytes) > 0 {
114+
if remainingBytes[len(remainingBytes)-1] != '\n' {
115+
remainingBytes = append(remainingBytes, '\n')
116+
}
117+
w.PrintFunc(remainingBytes)
118+
}
119+
}

0 commit comments

Comments
 (0)