Skip to content

Commit

Permalink
feat: option to expand properties with flag --show-properties (#2074)
Browse files Browse the repository at this point in the history
  • Loading branch information
Bikappa authored Feb 17, 2023
1 parent 425aaf0 commit a1e69dc
Show file tree
Hide file tree
Showing 2 changed files with 120 additions and 12 deletions.
63 changes: 55 additions & 8 deletions internal/cli/compile/compile.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,15 +38,36 @@ import (
"github.com/arduino/arduino-cli/table"
"github.com/arduino/arduino-cli/version"
"github.com/arduino/go-paths-helper"
"github.com/arduino/go-properties-orderedmap"
"github.com/fatih/color"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)

type showPropertiesMode int

const (
showPropertiesModeDisabled showPropertiesMode = iota
showPropertiesModeUnexpanded
showPropertiesModeExpanded
)

func parseShowPropertiesMode(showProperties string) (showPropertiesMode, error) {
val, ok := map[string]showPropertiesMode{
"disabled": showPropertiesModeDisabled,
"unexpanded": showPropertiesModeUnexpanded,
"expanded": showPropertiesModeExpanded,
}[showProperties]
if !ok {
return showPropertiesModeDisabled, fmt.Errorf(tr("invalid option '%s'.", showProperties))
}
return val, nil
}

var (
fqbnArg arguments.Fqbn // Fully Qualified Board Name, e.g.: arduino:avr:uno.
profileArg arguments.Profile // Profile to use
showProperties bool // Show all build preferences used instead of compiling.
showProperties string // Show all build preferences used instead of compiling.
preprocess bool // Print preprocessed code to stdout.
buildCachePath string // Builds of 'core.a' are saved into this path to be cached and reused.
buildPath string // Path where to save compiled files.
Expand Down Expand Up @@ -95,7 +116,13 @@ func NewCommand() *cobra.Command {
fqbnArg.AddToCommand(compileCommand)
profileArg.AddToCommand(compileCommand)
compileCommand.Flags().BoolVar(&dumpProfile, "dump-profile", false, tr("Create and print a profile configuration from the build."))
compileCommand.Flags().BoolVar(&showProperties, "show-properties", false, tr("Show all build properties used instead of compiling."))
compileCommand.Flags().StringVar(
&showProperties,
"show-properties",
"disabled",
tr(`Show build properties instead of compiling. The properties are returned exactly as they are defined. Use "--show-properties=expanded" to replace placeholders with compilation context values.`),
)
compileCommand.Flags().Lookup("show-properties").NoOptDefVal = "unexpanded" // default if the flag is present with no value
compileCommand.Flags().BoolVar(&preprocess, "preprocess", false, tr("Print preprocessed code to stdout instead of compiling."))
compileCommand.Flags().StringVar(&buildCachePath, "build-cache-path", "", tr("Builds of 'core.a' are saved into this path to be cached and reused."))
compileCommand.Flags().StringVarP(&exportDir, "output-dir", "", "", tr("Save build artifacts in this directory."))
Expand Down Expand Up @@ -188,9 +215,14 @@ func runCompileCommand(cmd *cobra.Command, args []string) {
overrides = o.Overrides
}

showPropertiesM, err := parseShowPropertiesMode(showProperties)
if err != nil {
feedback.Fatal(tr("Error parsing --show-properties flag: %v", err), feedback.ErrGeneric)
}

var stdOut, stdErr io.Writer
var stdIORes func() *feedback.OutputStreamsResult
if showProperties {
if showPropertiesM != showPropertiesModeDisabled {
stdOut, stdErr, stdIORes = feedback.NewBufferedStreams()
} else {
stdOut, stdErr, stdIORes = feedback.OutputStreams()
Expand All @@ -200,7 +232,7 @@ func runCompileCommand(cmd *cobra.Command, args []string) {
Instance: inst,
Fqbn: fqbn,
SketchPath: sketchPath.String(),
ShowProperties: showProperties,
ShowProperties: showPropertiesM != showPropertiesModeDisabled,
Preprocess: preprocess,
BuildCachePath: buildCachePath,
BuildPath: buildPath,
Expand Down Expand Up @@ -318,7 +350,7 @@ func runCompileCommand(cmd *cobra.Command, args []string) {
BuilderResult: compileRes,
ProfileOut: profileOut,
Success: compileError == nil,
showOnlyProperties: showProperties,
showPropertiesMode: showPropertiesM,
}

if compileError != nil {
Expand Down Expand Up @@ -353,9 +385,24 @@ func runCompileCommand(cmd *cobra.Command, args []string) {
}
feedback.FatalResult(res, feedback.ErrGeneric)
}
if showPropertiesM == showPropertiesModeExpanded {
expandPropertiesInResult(res)
}
feedback.PrintResult(res)
}

func expandPropertiesInResult(res *compileResult) {
expanded, err := properties.LoadFromSlice(res.BuilderResult.GetBuildProperties())
if err != nil {
res.Error = tr(err.Error())
}
expandedSlice := make([]string, expanded.Size())
for i, k := range expanded.Keys() {
expandedSlice[i] = strings.Join([]string{k, expanded.ExpandPropsInString(expanded.Get(k))}, "=")
}
res.BuilderResult.BuildProperties = expandedSlice
}

type compileResult struct {
CompilerOut string `json:"compiler_out"`
CompilerErr string `json:"compiler_err"`
Expand All @@ -364,16 +411,16 @@ type compileResult struct {
ProfileOut string `json:"profile_out,omitempty"`
Error string `json:"error,omitempty"`

showOnlyProperties bool
showPropertiesMode showPropertiesMode
}

func (r *compileResult) Data() interface{} {
return r
}

func (r *compileResult) String() string {
if r.showOnlyProperties {
return strings.Join(r.BuilderResult.BuildProperties, fmt.Sprintln())
if r.showPropertiesMode != showPropertiesModeDisabled {
return strings.Join(r.BuilderResult.GetBuildProperties(), fmt.Sprintln())
}

titleColor := color.New(color.FgHiGreen)
Expand Down
69 changes: 65 additions & 4 deletions internal/integrationtest/compile_3/compile_show_properties_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,19 @@
package compile_test

import (
"encoding/json"
"testing"

"github.com/arduino/arduino-cli/internal/integrationtest"
"github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
"github.com/arduino/go-properties-orderedmap"
"github.com/stretchr/testify/require"
"go.bug.st/testifyjson/requirejson"
)

type cliCompileResponse struct {
BuilderResult *commands.CompileResponse `json:"builder_result"`
}

func TestCompileShowProperties(t *testing.T) {
env, cli := integrationtest.CreateArduinoCLIWithEnvironment(t)
defer env.CleanUp()
Expand All @@ -36,17 +41,25 @@ func TestCompileShowProperties(t *testing.T) {
bareMinimum := cli.CopySketch("bare_minimum")

// Test --show-properties output is clean
// properties are not expanded
stdout, stderr, err := cli.Run("compile", "--fqbn", "arduino:avr:uno", "-v", "--show-properties", bareMinimum.String())
require.NoError(t, err)
_, err = properties.LoadFromBytes(stdout)
props, err := properties.LoadFromBytes(stdout)
require.NoError(t, err, "Output must be a clean property list")
require.Empty(t, stderr)
require.True(t, props.ContainsKey("archive_file_path"))
require.Contains(t, props.Get("archive_file_path"), "{build.path}")

// Test --show-properties --format JSON output is clean
// properties are not expanded
stdout, stderr, err = cli.Run("compile", "--fqbn", "arduino:avr:uno", "-v", "--show-properties", "--format", "json", bareMinimum.String())
require.NoError(t, err)
requirejson.Parse(t, stdout, "Output must be a valid JSON")
require.Empty(t, stderr)
props, err = properties.LoadFromSlice(
requireCompileResponseJson(t, stdout).BuilderResult.GetBuildProperties())
require.NoError(t, err)
require.True(t, props.ContainsKey("archive_file_path"))
require.Contains(t, props.Get("archive_file_path"), "{build.path}")

// Test --show-properties output is clean, with a wrong FQBN
stdout, stderr, err = cli.Run("compile", "--fqbn", "arduino:avr:unoa", "-v", "--show-properties", bareMinimum.String())
Expand All @@ -58,6 +71,54 @@ func TestCompileShowProperties(t *testing.T) {
// Test --show-properties --format JSON output is clean, with a wrong FQBN
stdout, stderr, err = cli.Run("compile", "--fqbn", "arduino:avr:unoa", "-v", "--show-properties", "--format", "json", bareMinimum.String())
require.Error(t, err)
requirejson.Parse(t, stdout, "Output must be a valid JSON")
require.Empty(t, stderr)
requireCompileResponseJson(t, stdout)

// Test --show-properties=unexpanded output is clean
// properties are not expanded
stdout, stderr, err = cli.Run("compile", "--fqbn", "arduino:avr:uno", "-v", "--show-properties=unexpanded", bareMinimum.String())
require.NoError(t, err)
props, err = properties.LoadFromBytes(stdout)
require.NoError(t, err, "Output must be a clean property list")
require.Empty(t, stderr)
require.True(t, props.ContainsKey("archive_file_path"))
require.Contains(t, props.Get("archive_file_path"), "{build.path}")

// Test --show-properties=unexpanded output is clean
// properties are not expanded
stdout, stderr, err = cli.Run("compile", "--fqbn", "arduino:avr:uno", "-v", "--show-properties=unexpanded", "--format", "json", bareMinimum.String())
require.NoError(t, err)
require.Empty(t, stderr)
props, err = properties.LoadFromSlice(
requireCompileResponseJson(t, stdout).BuilderResult.GetBuildProperties())
require.NoError(t, err)
require.True(t, props.ContainsKey("archive_file_path"))
require.Contains(t, props.Get("archive_file_path"), "{build.path}")

// Test --show-properties=expanded output is clean
// properties are expanded
stdout, stderr, err = cli.Run("compile", "--fqbn", "arduino:avr:uno", "-v", "--show-properties=expanded", bareMinimum.String())
require.NoError(t, err)
props, err = properties.LoadFromBytes(stdout)
require.NoError(t, err, "Output must be a clean property list")
require.Empty(t, stderr)
require.True(t, props.ContainsKey("archive_file_path"))
require.NotContains(t, props.Get("archive_file_path"), "{build.path}")

// Test --show-properties=expanded --format JSON output is clean
// properties are expanded
stdout, stderr, err = cli.Run("compile", "--fqbn", "arduino:avr:uno", "-v", "--show-properties=expanded", "--format", "json", bareMinimum.String())
require.NoError(t, err)
require.Empty(t, stderr)
props, err = properties.LoadFromSlice(
requireCompileResponseJson(t, stdout).BuilderResult.GetBuildProperties())
require.NoError(t, err)
require.True(t, props.ContainsKey("archive_file_path"))
require.NotContains(t, props.Get("archive_file_path"), "{build.path}")
}

func requireCompileResponseJson(t *testing.T, stdout []byte) *cliCompileResponse {
var compileResponse cliCompileResponse
require.NoError(t, json.Unmarshal(stdout, &compileResponse))
return &compileResponse
}

0 comments on commit a1e69dc

Please sign in to comment.