Skip to content

Reduce boilerplate code by rationalization of context data into a struct #134

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 25 commits into from
May 3, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
da86fe8
Introducing types.Context structure
cmaglie Jan 20, 2016
691b76d
Migrated all buildProperties into types.Context
cmaglie Apr 1, 2016
559454c
Refactored logger in context.
cmaglie Apr 1, 2016
48c0180
Migrated verbosity parameters into Context
cmaglie Apr 1, 2016
9a1f215
Moving include detection into Context struct
cmaglie Apr 1, 2016
d5ae87c
Refactored a bunch of ctags related context variables
cmaglie Apr 2, 2016
fa317a6
Added buildOptions refactoring to main.go
cmaglie Apr 13, 2016
a7e0694
Slightly simplified usage of os.Exit(..)
cmaglie Apr 13, 2016
ef3d04a
Added some build paths to Context.
cmaglie Apr 13, 2016
ab56609
Moved WarningsLevel into Context
cmaglie Apr 13, 2016
96a5ca5
Moved BuildOptionsJSON into Context structure
cmaglie Apr 14, 2016
c68454f
SourceFile collectors data are now in Context
cmaglie Apr 14, 2016
57203ac
Moved Target* fields in Context
cmaglie Apr 14, 2016
310f2e2
BuildProperties are now part of Context
cmaglie Apr 15, 2016
377bebe
IncludeFolders now in Context
cmaglie Apr 15, 2016
a9fd1c6
Moved Sketch in Context
cmaglie Apr 15, 2016
7520e66
Removed no more used "coan" tools
cmaglie Apr 15, 2016
67a198f
Moved Source and SourceGccMinusE into Context
cmaglie Apr 15, 2016
6784cbf
Moved Tools, Hardware and USBVidPid in Context
cmaglie Apr 15, 2016
169bcd9
Moved "gcc -M" output in Context
cmaglie Apr 15, 2016
1ff9632
Moved some paths into Context
cmaglie Apr 15, 2016
2161b9e
Moved PlatformRewrite* structures in Context
cmaglie Apr 16, 2016
602e02b
FileToRead field moved in Context
cmaglie Apr 16, 2016
8b74855
Moved Include preprocessing and discovery fields into Context
cmaglie Apr 16, 2016
e6f25db
Removed context map[string]interface{}
cmaglie Apr 16, 2016
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
bin
src/github.com
src/golang.org
arduino-builder
./arduino-builder

# Created by .ignore support plugin (hsz.mobi)
### Go template
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ go get github.com/stretchr/testify
go get github.com/jstemmer/go-junit-report
go get golang.org/x/codereview/patch
go get golang.org/x/tools/cmd/vet
go build
go build arduino.cc/arduino-builder
```

### TDD
Expand Down
6 changes: 3 additions & 3 deletions fmt_fix_vet
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
#!/bin/bash -x

go fmt ./src/arduino.cc/builder/...
go fmt main.go
go fmt ./src/arduino.cc/arduino-builder/...
go fix ./src/arduino.cc/builder/...
go fix main.go
go fix ./src/arduino.cc/arduino-builder/...
go vet ./src/arduino.cc/builder/...
go vet main.go
go vet ./src/arduino.cc/arduino-builder/...
171 changes: 67 additions & 104 deletions main.go → src/arduino.cc/arduino-builder/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,10 @@ import (
"syscall"

"arduino.cc/builder"
"arduino.cc/builder/constants"
"arduino.cc/builder/gohasissues"
"arduino.cc/builder/i18n"
"arduino.cc/builder/props"
"arduino.cc/builder/types"
"arduino.cc/builder/utils"
"github.com/go-errors/errors"
)
Expand Down Expand Up @@ -165,207 +166,167 @@ func main() {
fmt.Println("See https://www.arduino.cc/ and https://github.com/arduino/arduino-builder/graphs/contributors")
fmt.Println("This is free software; see the source for copying conditions. There is NO")
fmt.Println("warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.")
defer os.Exit(0)
return
}

context := make(map[string]interface{})
ctx := &types.Context{}

buildOptions := make(map[string]string)
if *buildOptionsFileFlag != "" {
buildOptions := make(props.PropertiesMap)
if _, err := os.Stat(*buildOptionsFileFlag); err == nil {
data, err := ioutil.ReadFile(*buildOptionsFileFlag)
if err != nil {
printCompleteError(err)
defer os.Exit(1)
return
}
err = json.Unmarshal(data, &buildOptions)
if err != nil {
printCompleteError(err)
defer os.Exit(1)
return
}
}
ctx.InjectBuildOptions(buildOptions)
}

var err error
printStackTrace := false
err, printStackTrace = setContextSliceKeyOrLoadItFromOptions(context, hardwareFoldersFlag, buildOptions, constants.CTX_HARDWARE_FOLDERS, FLAG_HARDWARE, true)
if err != nil {
printError(err, printStackTrace)
defer os.Exit(1)
return
// FLAG_HARDWARE
if hardwareFolders, err := toSliceOfUnquoted(hardwareFoldersFlag); err != nil {
printCompleteError(err)
} else if len(hardwareFolders) > 0 {
ctx.HardwareFolders = hardwareFolders
}

err, printStackTrace = setContextSliceKeyOrLoadItFromOptions(context, toolsFoldersFlag, buildOptions, constants.CTX_TOOLS_FOLDERS, FLAG_TOOLS, true)
if err != nil {
printError(err, printStackTrace)
defer os.Exit(1)
return
if len(ctx.HardwareFolders) == 0 {
printErrorMessageAndFlagUsage(errors.New("Parameter '" + FLAG_HARDWARE + "' is mandatory"))
}

err, printStackTrace = setContextSliceKeyOrLoadItFromOptions(context, librariesFoldersFlag, buildOptions, constants.CTX_OTHER_LIBRARIES_FOLDERS, FLAG_LIBRARIES, false)
if err != nil {
printError(err, printStackTrace)
defer os.Exit(1)
return
// FLAG_TOOLS
if toolsFolders, err := toSliceOfUnquoted(toolsFoldersFlag); err != nil {
printCompleteError(err)
} else if len(toolsFolders) > 0 {
ctx.ToolsFolders = toolsFolders
}

err, printStackTrace = setContextSliceKeyOrLoadItFromOptions(context, librariesBuiltInFoldersFlag, buildOptions, constants.CTX_BUILT_IN_LIBRARIES_FOLDERS, FLAG_BUILT_IN_LIBRARIES, false)
if err != nil {
printError(err, printStackTrace)
defer os.Exit(1)
return
if len(ctx.ToolsFolders) == 0 {
printErrorMessageAndFlagUsage(errors.New("Parameter '" + FLAG_TOOLS + "' is mandatory"))
}

err, printStackTrace = setContextSliceKeyOrLoadItFromOptions(context, customBuildPropertiesFlag, buildOptions, constants.CTX_CUSTOM_BUILD_PROPERTIES, FLAG_PREFS, false)
if err != nil {
printError(err, printStackTrace)
defer os.Exit(1)
return
// FLAG_LIBRARIES
if librariesFolders, err := toSliceOfUnquoted(librariesFoldersFlag); err != nil {
printCompleteError(err)
} else if len(librariesFolders) > 0 {
ctx.OtherLibrariesFolders = librariesFolders
}

fqbn, err := gohasissues.Unquote(*fqbnFlag)
if err != nil {
// FLAG_BUILT_IN_LIBRARIES
if librariesBuiltInFolders, err := toSliceOfUnquoted(librariesBuiltInFoldersFlag); err != nil {
printCompleteError(err)
defer os.Exit(1)
return
} else if len(librariesBuiltInFolders) > 0 {
ctx.BuiltInLibrariesFolders = librariesBuiltInFolders
}

if fqbn == "" {
fqbn = buildOptions[constants.CTX_FQBN]
// FLAG_PREFS
if customBuildProperties, err := toSliceOfUnquoted(customBuildPropertiesFlag); err != nil {
printCompleteError(err)
} else if len(customBuildProperties) > 0 {
ctx.CustomBuildProperties = customBuildProperties
}

if fqbn == "" {
// FLAG_FQBN
if fqbn, err := gohasissues.Unquote(*fqbnFlag); err != nil {
printCompleteError(err)
} else if fqbn != "" {
ctx.FQBN = fqbn
}
if ctx.FQBN == "" {
printErrorMessageAndFlagUsage(errors.New("Parameter '" + FLAG_FQBN + "' is mandatory"))
defer os.Exit(1)
return
}
context[constants.CTX_FQBN] = fqbn

// FLAG_BUILD_PATH
buildPath, err := gohasissues.Unquote(*buildPathFlag)
if err != nil {
printCompleteError(err)
defer os.Exit(1)
return
}

if buildPath != "" {
_, err := os.Stat(buildPath)
if err != nil {
fmt.Fprintln(os.Stderr, err)
defer os.Exit(1)
return
os.Exit(1)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't the defer useful here? IIRC deferring the exit was useful to make sure other defer and error handlers run, right? Or does that not apply here?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh good catch, that's a nice one that I forget to comment on the PR.
I kept this change in a single commit so it can be easily reverted in case.

IIRC deferring the exit was useful to make sure other defer and error handlers run

what you said applies for deferred actions created in the current scope (i.e. the current running function and its callers), but since the main function, basically, does only command line parsing all the defers are instantiated inside "Commands" and when the control returns back to the main function all the internal defers are already out of scope and executed. Since the main function doesn't use defer I opted to skip the defer os.Exit construct.

The drawback is that new defers may be added in the main function in the future and it would be more future-proof to keep the defer os.Exit construct instead. Personally I prefer the "classic" behavior of the "exit" function, where the execution of the program is terminated immediately.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another approach may be to make the main function just call sub-functions and convert the result into an exit code, something like:

func main() {
    if err := parseCommandLine(); err != nil {
        exitWithError(err);
    }
    if err := runCommands(); err != nil {
        exitWithError(err);
    }
    os.Exit(0)
}

func exitWithError(err error) {
    var exitCode int
    // [...convert error into an exitCode...]
    os.Exit(exitCode)
}

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess you could even use a more simple approach where the effective main function can just return an exit code, just like we know from C:

func main() {
    os.Exit(realMain())
}

And then the realMain() function just does what it needs and returns an exit code?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup that's another way too, BTW my preference is to handle everything with golang's error until the very end and use the main function to convert it into an error code. BTW I guess we can work this out after merging this PR :-)

}

err = utils.EnsureFolderExists(buildPath)
if err != nil {
printCompleteError(err)
defer os.Exit(1)
return
}
}
context[constants.CTX_BUILD_PATH] = buildPath
ctx.BuildPath = buildPath

// FLAG_VID_PID
if *vidPidFlag != "" {
context[constants.CTX_VIDPID] = *vidPidFlag
ctx.USBVidPid = *vidPidFlag
}

if flag.NArg() > 0 {
sketchLocation := flag.Arg(0)
sketchLocation, err := gohasissues.Unquote(sketchLocation)
if err != nil {
printCompleteError(err)
defer os.Exit(1)
return
}
context[constants.CTX_SKETCH_LOCATION] = sketchLocation
ctx.SketchLocation = sketchLocation
}

if *verboseFlag && *quietFlag {
*verboseFlag = false
*quietFlag = false
}

context[constants.CTX_VERBOSE] = *verboseFlag
ctx.Verbose = *verboseFlag

coreAPIVersion := ""
if utils.MapStringStringHas(buildOptions, constants.CTX_BUILD_PROPERTIES_RUNTIME_IDE_VERSION) {
coreAPIVersion = buildOptions[constants.CTX_BUILD_PROPERTIES_RUNTIME_IDE_VERSION]
} else {
// if deprecated 'ideVersionFlag' has been used...
// FLAG_IDE_VERSION
if ctx.ArduinoAPIVersion == "" {
// if deprecated "--ideVersionFlag" has been used...
if *coreAPIVersionFlag == "10600" && *ideVersionFlag != "10600" {
coreAPIVersion = *ideVersionFlag
ctx.ArduinoAPIVersion = *ideVersionFlag
} else {
coreAPIVersion = *coreAPIVersionFlag
ctx.ArduinoAPIVersion = *coreAPIVersionFlag
}
}
context[constants.CTX_BUILD_PROPERTIES_RUNTIME_IDE_VERSION] = coreAPIVersion

if *warningsLevelFlag != "" {
context[constants.CTX_WARNINGS_LEVEL] = *warningsLevelFlag
ctx.WarningsLevel = *warningsLevelFlag
}

if *debugLevelFlag > -1 {
context[constants.CTX_DEBUG_LEVEL] = *debugLevelFlag
ctx.DebugLevel = *debugLevelFlag
}

if *quietFlag {
context[constants.CTX_LOGGER] = i18n.NoopLogger{}
ctx.SetLogger(i18n.NoopLogger{})
} else if *loggerFlag == FLAG_LOGGER_MACHINE {
context[constants.CTX_LOGGER] = i18n.MachineLogger{}
ctx.SetLogger(i18n.MachineLogger{})
} else {
context[constants.CTX_LOGGER] = i18n.HumanLogger{}
ctx.SetLogger(i18n.HumanLogger{})
}

if *dumpPrefsFlag {
err = builder.RunParseHardwareAndDumpBuildProperties(context)
err = builder.RunParseHardwareAndDumpBuildProperties(ctx)
} else if *preprocessFlag {
err = builder.RunPreprocess(context)
err = builder.RunPreprocess(ctx)
} else {
if flag.NArg() == 0 {
fmt.Fprintln(os.Stderr, "Last parameter must be the sketch to compile")
flag.Usage()
defer os.Exit(1)
return
os.Exit(1)
}
err = builder.RunBuilder(context)
err = builder.RunBuilder(ctx)
}

exitCode := 0
if err != nil {
err = utils.WrapError(err)
err = i18n.WrapError(err)

fmt.Fprintln(os.Stderr, err)

if utils.DebugLevel(context) >= 10 {
if ctx.DebugLevel >= 10 {
fmt.Fprintln(os.Stderr, err.(*errors.Error).ErrorStack())
}

exitCode = toExitCode(err)
os.Exit(toExitCode(err))
}

defer os.Exit(exitCode)
}

func setContextSliceKeyOrLoadItFromOptions(context map[string]interface{}, cliFlag []string, buildOptions map[string]string, contextKey string, paramName string, mandatory bool) (error, bool) {
values, err := toSliceOfUnquoted(cliFlag)
if err != nil {
return err, true
}

if len(values) == 0 && len(buildOptions[contextKey]) > 0 {
values = strings.Split(buildOptions[contextKey], ",")
}

if mandatory && len(values) == 0 {
return errors.New("Parameter '" + paramName + "' is mandatory"), false
}

context[contextKey] = values

return nil, false
}

func toExitCode(err error) int {
Expand Down Expand Up @@ -398,11 +359,13 @@ func printError(err error, printStackTrace bool) {
}

func printCompleteError(err error) {
err = utils.WrapError(err)
err = i18n.WrapError(err)
fmt.Fprintln(os.Stderr, err.(*errors.Error).ErrorStack())
os.Exit(1)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems unexpected to me - I wouldn't expect a function called "printCompleteError" to exit the current process. I guess the name should be changed? Or can this exit be removed when implementing the exit-related changes discussed elsewhere?

}

func printErrorMessageAndFlagUsage(err error) {
fmt.Fprintln(os.Stderr, err)
flag.Usage()
os.Exit(1)
}
Loading