From 1887d56568013b2a5888a6af988b35423fc28aee Mon Sep 17 00:00:00 2001 From: Federico Fissore Date: Fri, 27 Nov 2015 17:06:30 +0100 Subject: [PATCH 1/4] 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 --- main.go | 10 +++++ src/arduino.cc/builder/builder_utils/utils.go | 43 ++++++++++++++++--- src/arduino.cc/builder/test/builder_test.go | 34 +++++++++++++++ .../test/sketch_with_syntax_error/sketch.ino | 3 ++ 4 files changed, 85 insertions(+), 5 deletions(-) create mode 100644 src/arduino.cc/builder/test/sketch_with_syntax_error/sketch.ino diff --git a/main.go b/main.go index cdba4273..5f1f0fa3 100644 --- a/main.go +++ b/main.go @@ -43,6 +43,7 @@ import ( "io/ioutil" "os" "os/exec" + "runtime" "strings" "syscall" ) @@ -74,6 +75,7 @@ const FLAG_LOGGER_HUMAN = "human" const FLAG_LOGGER_MACHINE = "machine" const FLAG_VERSION = "version" const FLAG_VID_PID = "vid-pid" +const FLAG_JOBS = "jobs" type slice []string @@ -116,6 +118,7 @@ var warningsLevelFlag *string var loggerFlag *string var versionFlag *bool var vidPidFlag *string +var jobsFlag *int func init() { compileFlag = flag.Bool(FLAG_ACTION_COMPILE, false, "compiles the given sketch") @@ -137,6 +140,7 @@ func init() { loggerFlag = flag.String(FLAG_LOGGER, FLAG_LOGGER_HUMAN, "Sets type of logger. Available values are '"+FLAG_LOGGER_HUMAN+"', '"+FLAG_LOGGER_MACHINE+"'") versionFlag = flag.Bool(FLAG_VERSION, false, "prints version and exits") vidPidFlag = flag.String(FLAG_VID_PID, "", "specify to use vid/pid specific build properties, as defined in boards.txt") + 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") } func main() { @@ -152,6 +156,12 @@ func main() { return } + if *jobsFlag > 0 { + runtime.GOMAXPROCS(*jobsFlag) + } else { + runtime.GOMAXPROCS(runtime.NumCPU()) + } + context := make(map[string]interface{}) buildOptions := make(map[string]string) diff --git a/src/arduino.cc/builder/builder_utils/utils.go b/src/arduino.cc/builder/builder_utils/utils.go index 18ab284e..6844b943 100644 --- a/src/arduino.cc/builder/builder_utils/utils.go +++ b/src/arduino.cc/builder/builder_utils/utils.go @@ -40,6 +40,7 @@ import ( "os/exec" "path/filepath" "strings" + "sync" ) func CompileFilesRecursive(objectFiles []string, sourcePath string, buildPath string, buildProperties map[string]string, includes []string, verbose bool, warningsLevel string, logger i18n.Logger) ([]string, error) { @@ -116,15 +117,47 @@ func findFilesInFolder(sourcePath string, extension string, recurse bool) ([]str } func compileFilesWithRecipe(objectFiles []string, sourcePath string, sources []string, buildPath string, buildProperties map[string]string, includes []string, recipe string, verbose bool, warningsLevel string, logger i18n.Logger) ([]string, error) { + if len(sources) == 0 { + return objectFiles, nil + } + objectFilesChan := make(chan string) + errorsChan := make(chan error) + doneChan := make(chan struct{}) + + var wg sync.WaitGroup + wg.Add(len(sources)) + for _, source := range sources { - objectFile, err := compileFileWithRecipe(sourcePath, source, buildPath, buildProperties, includes, recipe, verbose, warningsLevel, logger) - if err != nil { + go func(source string) { + defer wg.Done() + objectFile, err := compileFileWithRecipe(sourcePath, source, buildPath, buildProperties, includes, recipe, verbose, warningsLevel, logger) + if err != nil { + errorsChan <- err + } else { + objectFilesChan <- objectFile + } + }(source) + } + + go func() { + wg.Wait() + doneChan <- struct{}{} + }() + + for { + select { + case objectFile := <-objectFilesChan: + objectFiles = append(objectFiles, objectFile) + case err := <-errorsChan: return nil, utils.WrapError(err) + case <-doneChan: + close(objectFilesChan) + for objectFile := range objectFilesChan { + objectFiles = append(objectFiles, objectFile) + } + return objectFiles, nil } - - objectFiles = append(objectFiles, objectFile) } - return objectFiles, nil } func compileFileWithRecipe(sourcePath string, source string, buildPath string, buildProperties map[string]string, includes []string, recipe string, verbose bool, warningsLevel string, logger i18n.Logger) (string, error) { diff --git a/src/arduino.cc/builder/test/builder_test.go b/src/arduino.cc/builder/test/builder_test.go index 3ec2c459..857bdf4f 100644 --- a/src/arduino.cc/builder/test/builder_test.go +++ b/src/arduino.cc/builder/test/builder_test.go @@ -36,11 +36,13 @@ import ( "os" "os/exec" "path/filepath" + "runtime" "testing" ) func TestBuilderEmptySketch(t *testing.T) { DownloadCoresAndToolsAndLibraries(t) + runtime.GOMAXPROCS(runtime.NumCPU()) context := make(map[string]interface{}) @@ -75,6 +77,7 @@ func TestBuilderEmptySketch(t *testing.T) { func TestBuilderBridge(t *testing.T) { DownloadCoresAndToolsAndLibraries(t) + runtime.GOMAXPROCS(runtime.NumCPU()) context := make(map[string]interface{}) @@ -110,6 +113,7 @@ func TestBuilderBridge(t *testing.T) { func TestBuilderSketchWithConfig(t *testing.T) { DownloadCoresAndToolsAndLibraries(t) + runtime.GOMAXPROCS(runtime.NumCPU()) context := make(map[string]interface{}) @@ -144,6 +148,7 @@ func TestBuilderSketchWithConfig(t *testing.T) { func TestBuilderBridgeTwice(t *testing.T) { DownloadCoresAndToolsAndLibraries(t) + runtime.GOMAXPROCS(runtime.NumCPU()) context := make(map[string]interface{}) @@ -182,6 +187,7 @@ func TestBuilderBridgeTwice(t *testing.T) { func TestBuilderBridgeSAM(t *testing.T) { DownloadCoresAndToolsAndLibraries(t) + runtime.GOMAXPROCS(runtime.NumCPU()) context := make(map[string]interface{}) @@ -226,6 +232,7 @@ func TestBuilderBridgeSAM(t *testing.T) { func TestBuilderBridgeRedBearLab(t *testing.T) { DownloadCoresAndToolsAndLibraries(t) + runtime.GOMAXPROCS(runtime.NumCPU()) context := make(map[string]interface{}) @@ -260,6 +267,7 @@ func TestBuilderBridgeRedBearLab(t *testing.T) { func TestBuilderSketchNoFunctions(t *testing.T) { DownloadCoresAndToolsAndLibraries(t) + runtime.GOMAXPROCS(runtime.NumCPU()) context := make(map[string]interface{}) @@ -281,6 +289,7 @@ func TestBuilderSketchNoFunctions(t *testing.T) { func TestBuilderSketchWithBackup(t *testing.T) { DownloadCoresAndToolsAndLibraries(t) + runtime.GOMAXPROCS(runtime.NumCPU()) context := make(map[string]interface{}) @@ -302,6 +311,7 @@ func TestBuilderSketchWithBackup(t *testing.T) { func TestBuilderSketchWithOldLib(t *testing.T) { DownloadCoresAndToolsAndLibraries(t) + runtime.GOMAXPROCS(runtime.NumCPU()) context := make(map[string]interface{}) @@ -323,6 +333,7 @@ func TestBuilderSketchWithOldLib(t *testing.T) { func TestBuilderSketchWithSubfolders(t *testing.T) { DownloadCoresAndToolsAndLibraries(t) + runtime.GOMAXPROCS(runtime.NumCPU()) context := make(map[string]interface{}) @@ -370,3 +381,26 @@ func TestBuilderSketchBuildPathContainsUnusedPreviouslyCompiledLibrary(t *testin _, err = os.Stat(filepath.Join(buildPath, constants.FOLDER_LIBRARIES, "Bridge")) NoError(t, err) } + +func TestBuilderSketchWithSyntaxError(t *testing.T) { + DownloadCoresAndToolsAndLibraries(t) + runtime.GOMAXPROCS(runtime.NumCPU()) + + context := make(map[string]interface{}) + + buildPath := SetupBuildPath(t, context) + defer os.RemoveAll(buildPath) + + context[constants.CTX_HARDWARE_FOLDERS] = []string{filepath.Join("..", "hardware"), "hardware", "downloaded_hardware"} + context[constants.CTX_TOOLS_FOLDERS] = []string{"downloaded_tools"} + context[constants.CTX_FQBN] = "arduino:avr:uno" + context[constants.CTX_SKETCH_LOCATION] = filepath.Join("sketch_with_syntax_error", "sketch.ino") + context[constants.CTX_BUILT_IN_LIBRARIES_FOLDERS] = []string{"downloaded_libraries"} + context[constants.CTX_OTHER_LIBRARIES_FOLDERS] = []string{"libraries"} + context[constants.CTX_BUILD_PROPERTIES_RUNTIME_IDE_VERSION] = "10600" + context[constants.CTX_VERBOSE] = true + + command := builder.Builder{} + err := command.Run(context) + require.Error(t, err) +} diff --git a/src/arduino.cc/builder/test/sketch_with_syntax_error/sketch.ino b/src/arduino.cc/builder/test/sketch_with_syntax_error/sketch.ino new file mode 100644 index 00000000..ba3f6fce --- /dev/null +++ b/src/arduino.cc/builder/test/sketch_with_syntax_error/sketch.ino @@ -0,0 +1,3 @@ +void setup() { + +void loop() {} From 5abb51e61252ababe040b7dabe8a49a901534b69 Mon Sep 17 00:00:00 2001 From: Federico Fissore Date: Wed, 2 Dec 2015 16:25:51 +0100 Subject: [PATCH 2/4] External processes output is printed on stdout/stderr in a synchronized way. Thus we are sure that those outputs do not interfere with our own output, otherwise leading to wrongly formatted log messages. Fixes #17 Signed-off-by: Federico Fissore --- src/arduino.cc/builder/builder_utils/utils.go | 16 ++++-- src/arduino.cc/builder/coan_runner.go | 4 +- src/arduino.cc/builder/ctags_runner.go | 4 +- src/arduino.cc/builder/i18n/i18n.go | 49 ++++++++++++++----- src/arduino.cc/builder/types/accessories.go | 31 ++++++++++++ 5 files changed, 84 insertions(+), 20 deletions(-) diff --git a/src/arduino.cc/builder/builder_utils/utils.go b/src/arduino.cc/builder/builder_utils/utils.go index 6844b943..5ff5d1be 100644 --- a/src/arduino.cc/builder/builder_utils/utils.go +++ b/src/arduino.cc/builder/builder_utils/utils.go @@ -33,9 +33,9 @@ import ( "arduino.cc/builder/constants" "arduino.cc/builder/i18n" "arduino.cc/builder/props" + "arduino.cc/builder/types" "arduino.cc/builder/utils" "bytes" - "fmt" "os" "os/exec" "path/filepath" @@ -319,10 +319,18 @@ func ExecRecipe(properties map[string]string, recipe string, removeUnsetProperti } if echoOutput { - command.Stdout = os.Stdout + printToStdOut := func(s string) { + logger.UnformattedFprintln(os.Stdout, s) + } + stdout := &types.BufferedUntilNewLineWriter{PrintFunc: printToStdOut, Buffer: bytes.Buffer{}} + command.Stdout = stdout } - command.Stderr = os.Stderr + printToStdErr := func(s string) { + logger.UnformattedFprintln(os.Stderr, s) + } + stderr := &types.BufferedUntilNewLineWriter{PrintFunc: printToStdErr, Buffer: bytes.Buffer{}} + command.Stderr = stderr if echoOutput { err := command.Run() @@ -354,7 +362,7 @@ func PrepareCommandForRecipe(properties map[string]string, recipe string, remove } if echoCommandLine { - fmt.Println(commandLine) + logger.UnformattedFprintln(os.Stdout, commandLine) } return command, nil diff --git a/src/arduino.cc/builder/coan_runner.go b/src/arduino.cc/builder/coan_runner.go index f4630621..e776c5af 100644 --- a/src/arduino.cc/builder/coan_runner.go +++ b/src/arduino.cc/builder/coan_runner.go @@ -34,7 +34,7 @@ import ( "arduino.cc/builder/i18n" "arduino.cc/builder/props" "arduino.cc/builder/utils" - "fmt" + "os" "path/filepath" "regexp" ) @@ -74,7 +74,7 @@ func (s *CoanRunner) Run(context map[string]interface{}) error { command, err := utils.PrepareCommandFilteredArgs(commandLine, filterAllowedArg, logger) if verbose { - fmt.Println(commandLine) + logger.UnformattedFprintln(os.Stdout, commandLine) } sourceBytes, _ := command.Output() diff --git a/src/arduino.cc/builder/ctags_runner.go b/src/arduino.cc/builder/ctags_runner.go index 20c9eb7c..b7ca3877 100644 --- a/src/arduino.cc/builder/ctags_runner.go +++ b/src/arduino.cc/builder/ctags_runner.go @@ -34,7 +34,7 @@ import ( "arduino.cc/builder/i18n" "arduino.cc/builder/props" "arduino.cc/builder/utils" - "fmt" + "os" ) type CTagsRunner struct{} @@ -60,7 +60,7 @@ func (s *CTagsRunner) Run(context map[string]interface{}) error { verbose := context[constants.CTX_VERBOSE].(bool) if verbose { - fmt.Println(commandLine) + logger.UnformattedFprintln(os.Stdout, commandLine) } sourceBytes, err := command.Output() diff --git a/src/arduino.cc/builder/i18n/i18n.go b/src/arduino.cc/builder/i18n/i18n.go index 2769e587..9b5a9837 100644 --- a/src/arduino.cc/builder/i18n/i18n.go +++ b/src/arduino.cc/builder/i18n/i18n.go @@ -38,12 +38,14 @@ import ( "regexp" "strconv" "strings" + "sync" ) var PLACEHOLDER = regexp.MustCompile("{(\\d)}") type Logger interface { Fprintln(w io.Writer, level string, format string, a ...interface{}) + UnformattedFprintln(w io.Writer, s string) Println(level string, format string, a ...interface{}) Name() string } @@ -52,6 +54,8 @@ type NoopLogger struct{} func (s NoopLogger) Fprintln(w io.Writer, level string, format string, a ...interface{}) {} +func (s NoopLogger) UnformattedFprintln(w io.Writer, str string) {} + func (s NoopLogger) Println(level string, format string, a ...interface{}) {} func (s NoopLogger) Name() string { @@ -61,10 +65,14 @@ func (s NoopLogger) Name() string { type HumanLogger struct{} func (s HumanLogger) Fprintln(w io.Writer, level string, format string, a ...interface{}) { - fmt.Fprintln(w, Format(format, a...)) + fprintln(w, Format(format, a...)) +} + +func (s HumanLogger) UnformattedFprintln(w io.Writer, str string) { + fprintln(w, str) } -func (s HumanLogger) Println(level string, format string, a ...interface{}) { +func (s HumanLogger) Println(format string, level string, a ...interface{}) { s.Fprintln(os.Stdout, level, Format(format, a...)) } @@ -74,7 +82,23 @@ func (s HumanLogger) Name() string { type MachineLogger struct{} -func (s MachineLogger) printWithoutFormatting(w io.Writer, level string, format string, a []interface{}) { +func (s MachineLogger) Fprintln(w io.Writer, level string, format string, a ...interface{}) { + printMachineFormattedLogLine(w, level, format, a) +} + +func (s MachineLogger) Println(level string, format string, a ...interface{}) { + printMachineFormattedLogLine(os.Stdout, level, format, a) +} + +func (s MachineLogger) UnformattedFprintln(w io.Writer, str string) { + fprintln(w, str) +} + +func (s MachineLogger) Name() string { + return "machine" +} + +func printMachineFormattedLogLine(w io.Writer, level string, format string, a []interface{}) { a = append([]interface{}(nil), a...) for idx, value := range a { typeof := reflect.Indirect(reflect.ValueOf(value)).Kind() @@ -82,20 +106,21 @@ func (s MachineLogger) printWithoutFormatting(w io.Writer, level string, format a[idx] = url.QueryEscape(value.(string)) } } - fmt.Fprintf(w, "===%s ||| %s ||| %s", level, format, a) - fmt.Fprintln(w) + fprintf(w, "===%s ||| %s ||| %s\n", level, format, a) } -func (s MachineLogger) Fprintln(w io.Writer, level string, format string, a ...interface{}) { - s.printWithoutFormatting(w, level, format, a) -} +var lock sync.Mutex -func (s MachineLogger) Println(level string, format string, a ...interface{}) { - s.printWithoutFormatting(os.Stdout, level, format, a) +func fprintln(w io.Writer, s string) { + lock.Lock() + defer lock.Unlock() + fmt.Fprintln(w, s) } -func (s MachineLogger) Name() string { - return "machine" +func fprintf(w io.Writer, format string, a ...interface{}) { + lock.Lock() + defer lock.Unlock() + fmt.Fprintf(w, format, a...) } func FromJavaToGoSyntax(s string) string { diff --git a/src/arduino.cc/builder/types/accessories.go b/src/arduino.cc/builder/types/accessories.go index 1d7b7fc2..f8ae62b9 100644 --- a/src/arduino.cc/builder/types/accessories.go +++ b/src/arduino.cc/builder/types/accessories.go @@ -29,6 +29,11 @@ package types +import ( + "bytes" + "sync" +) + type UniqueStringQueue []string func (queue UniqueStringQueue) Len() int { return len(queue) } @@ -74,3 +79,29 @@ func (queue *UniqueSourceFolderQueue) Pop() interface{} { func (queue *UniqueSourceFolderQueue) Empty() bool { return queue.Len() == 0 } + +type BufferedUntilNewLineWriter struct { + PrintFunc PrintFunc + Buffer bytes.Buffer + lock sync.Mutex +} + +type PrintFunc func(string) + +func (w *BufferedUntilNewLineWriter) Write(p []byte) (n int, err error) { + w.lock.Lock() + defer w.lock.Unlock() + + writtenToBuffer, err := w.Buffer.Write(p) + if err != nil { + return writtenToBuffer, err + } + + bytesUntilNewLine, err := w.Buffer.ReadBytes('\n') + if err != nil && len(bytesUntilNewLine) > 0 { + bytesUntilNewLine = bytesUntilNewLine[:len(bytesUntilNewLine)-1] + w.PrintFunc(string(bytesUntilNewLine)) + } + + return writtenToBuffer, err +} From f8b3d6b34a32894e1d7f35d9fc335029e57a9760 Mon Sep 17 00:00:00 2001 From: Federico Fissore Date: Thu, 3 Dec 2015 12:26:44 +0100 Subject: [PATCH 3/4] Flushing remaining output from BufferedUntilNewLineWriter once process has ended. Also printing raw bytes instead of converting them into chars. Signed-off-by: Federico Fissore --- src/arduino.cc/builder/builder_utils/utils.go | 10 ++++++---- src/arduino.cc/builder/i18n/i18n.go | 17 ++++++++++++++++ src/arduino.cc/builder/test/builder_test.go | 4 ++++ src/arduino.cc/builder/types/accessories.go | 20 +++++++++++++++---- 4 files changed, 43 insertions(+), 8 deletions(-) diff --git a/src/arduino.cc/builder/builder_utils/utils.go b/src/arduino.cc/builder/builder_utils/utils.go index 5ff5d1be..6fa5f0ce 100644 --- a/src/arduino.cc/builder/builder_utils/utils.go +++ b/src/arduino.cc/builder/builder_utils/utils.go @@ -319,17 +319,19 @@ func ExecRecipe(properties map[string]string, recipe string, removeUnsetProperti } if echoOutput { - printToStdOut := func(s string) { - logger.UnformattedFprintln(os.Stdout, s) + printToStdOut := func(data []byte) { + logger.UnformattedWrite(os.Stdout, data) } stdout := &types.BufferedUntilNewLineWriter{PrintFunc: printToStdOut, Buffer: bytes.Buffer{}} + defer stdout.Flush() command.Stdout = stdout } - printToStdErr := func(s string) { - logger.UnformattedFprintln(os.Stderr, s) + printToStdErr := func(data []byte) { + logger.UnformattedWrite(os.Stderr, data) } stderr := &types.BufferedUntilNewLineWriter{PrintFunc: printToStdErr, Buffer: bytes.Buffer{}} + defer stderr.Flush() command.Stderr = stderr if echoOutput { diff --git a/src/arduino.cc/builder/i18n/i18n.go b/src/arduino.cc/builder/i18n/i18n.go index 9b5a9837..6540da79 100644 --- a/src/arduino.cc/builder/i18n/i18n.go +++ b/src/arduino.cc/builder/i18n/i18n.go @@ -46,6 +46,7 @@ var PLACEHOLDER = regexp.MustCompile("{(\\d)}") type Logger interface { Fprintln(w io.Writer, level string, format string, a ...interface{}) UnformattedFprintln(w io.Writer, s string) + UnformattedWrite(w io.Writer, data []byte) Println(level string, format string, a ...interface{}) Name() string } @@ -56,6 +57,8 @@ func (s NoopLogger) Fprintln(w io.Writer, level string, format string, a ...inte func (s NoopLogger) UnformattedFprintln(w io.Writer, str string) {} +func (s NoopLogger) UnformattedWrite(w io.Writer, data []byte) {} + func (s NoopLogger) Println(level string, format string, a ...interface{}) {} func (s NoopLogger) Name() string { @@ -76,6 +79,10 @@ func (s HumanLogger) Println(format string, level string, a ...interface{}) { s.Fprintln(os.Stdout, level, Format(format, a...)) } +func (s HumanLogger) UnformattedWrite(w io.Writer, data []byte) { + write(w, data) +} + func (s HumanLogger) Name() string { return "human" } @@ -98,6 +105,10 @@ func (s MachineLogger) Name() string { return "machine" } +func (s MachineLogger) UnformattedWrite(w io.Writer, data []byte) { + write(w, data) +} + func printMachineFormattedLogLine(w io.Writer, level string, format string, a []interface{}) { a = append([]interface{}(nil), a...) for idx, value := range a { @@ -117,6 +128,12 @@ func fprintln(w io.Writer, s string) { fmt.Fprintln(w, s) } +func write(w io.Writer, data []byte) { + lock.Lock() + defer lock.Unlock() + w.Write(data) +} + func fprintf(w io.Writer, format string, a ...interface{}) { lock.Lock() defer lock.Unlock() diff --git a/src/arduino.cc/builder/test/builder_test.go b/src/arduino.cc/builder/test/builder_test.go index 857bdf4f..31ae5fbc 100644 --- a/src/arduino.cc/builder/test/builder_test.go +++ b/src/arduino.cc/builder/test/builder_test.go @@ -32,6 +32,7 @@ package test import ( "arduino.cc/builder" "arduino.cc/builder/constants" + "arduino.cc/builder/i18n" "github.com/stretchr/testify/require" "os" "os/exec" @@ -92,6 +93,7 @@ func TestBuilderBridge(t *testing.T) { context[constants.CTX_OTHER_LIBRARIES_FOLDERS] = []string{"libraries"} context[constants.CTX_BUILD_PROPERTIES_RUNTIME_IDE_VERSION] = "10600" context[constants.CTX_VERBOSE] = true + context[constants.CTX_LOGGER] = i18n.MachineLogger{} command := builder.Builder{} err := command.Run(context) @@ -127,6 +129,8 @@ func TestBuilderSketchWithConfig(t *testing.T) { context[constants.CTX_BUILT_IN_LIBRARIES_FOLDERS] = []string{"downloaded_libraries"} context[constants.CTX_OTHER_LIBRARIES_FOLDERS] = []string{"libraries"} context[constants.CTX_BUILD_PROPERTIES_RUNTIME_IDE_VERSION] = "10600" + context[constants.CTX_VERBOSE] = true + context[constants.CTX_LOGGER] = i18n.NoopLogger{} command := builder.Builder{} err := command.Run(context) diff --git a/src/arduino.cc/builder/types/accessories.go b/src/arduino.cc/builder/types/accessories.go index f8ae62b9..54199c05 100644 --- a/src/arduino.cc/builder/types/accessories.go +++ b/src/arduino.cc/builder/types/accessories.go @@ -86,7 +86,7 @@ type BufferedUntilNewLineWriter struct { lock sync.Mutex } -type PrintFunc func(string) +type PrintFunc func([]byte) func (w *BufferedUntilNewLineWriter) Write(p []byte) (n int, err error) { w.lock.Lock() @@ -98,10 +98,22 @@ func (w *BufferedUntilNewLineWriter) Write(p []byte) (n int, err error) { } bytesUntilNewLine, err := w.Buffer.ReadBytes('\n') - if err != nil && len(bytesUntilNewLine) > 0 { - bytesUntilNewLine = bytesUntilNewLine[:len(bytesUntilNewLine)-1] - w.PrintFunc(string(bytesUntilNewLine)) + if err == nil { + w.PrintFunc(bytesUntilNewLine) } return writtenToBuffer, err } + +func (w *BufferedUntilNewLineWriter) Flush() { + w.lock.Lock() + defer w.lock.Unlock() + + remainingBytes := w.Buffer.Bytes() + if len(remainingBytes) > 0 { + if remainingBytes[len(remainingBytes)-1] != '\n' { + remainingBytes = append(remainingBytes, '\n') + } + w.PrintFunc(remainingBytes) + } +} From 459114493cd0b4619a49748d86d97b06cf6f8563 Mon Sep 17 00:00:00 2001 From: Federico Fissore Date: Wed, 2 Dec 2015 16:41:58 +0100 Subject: [PATCH 4/4] COMMIT TO REMOVE: fake version Signed-off-by: Federico Fissore --- main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.go b/main.go index 5f1f0fa3..8ce072c3 100644 --- a/main.go +++ b/main.go @@ -48,7 +48,7 @@ import ( "syscall" ) -const VERSION = "1.3.8" +const VERSION = "1.3.8-dev-parallel" const FLAG_ACTION_COMPILE = "compile" const FLAG_ACTION_PREPROCESS = "preprocess"