diff --git a/arduino/cores/packagemanager/install_uninstall.go b/arduino/cores/packagemanager/install_uninstall.go
index 4ab87361111..87beee0a472 100644
--- a/arduino/cores/packagemanager/install_uninstall.go
+++ b/arduino/cores/packagemanager/install_uninstall.go
@@ -102,7 +102,7 @@ func (pme *Explorer) DownloadAndInstallPlatformAndTools(
// Install tools first
for _, tool := range toolsToInstall {
- if err := pme.InstallTool(tool, taskCB); err != nil {
+ if err := pme.InstallTool(tool, taskCB, skipPostInstall); err != nil {
return err
}
}
@@ -171,7 +171,10 @@ func (pme *Explorer) DownloadAndInstallPlatformAndTools(
if !skipPostInstall {
log.Info("Running post_install script")
taskCB(&rpc.TaskProgress{Message: tr("Configuring platform.")})
- if err := pme.RunPostInstallScript(platformRelease); err != nil {
+ if !platformRelease.IsInstalled() {
+ return errors.New(tr("platform not installed"))
+ }
+ if err := pme.RunPostInstallScript(platformRelease.InstallDir); err != nil {
taskCB(&rpc.TaskProgress{Message: tr("WARNING cannot configure platform: %s", err)})
}
} else {
@@ -222,22 +225,19 @@ func (pme *Explorer) cacheInstalledJSON(platformRelease *cores.PlatformRelease)
}
// RunPostInstallScript runs the post_install.sh (or post_install.bat) script for the
-// specified platformRelease.
-func (pme *Explorer) RunPostInstallScript(platformRelease *cores.PlatformRelease) error {
- if !platformRelease.IsInstalled() {
- return errors.New(tr("platform not installed"))
- }
+// specified platformRelease or toolRelease.
+func (pme *Explorer) RunPostInstallScript(installDir *paths.Path) error {
postInstallFilename := "post_install.sh"
if runtime.GOOS == "windows" {
postInstallFilename = "post_install.bat"
}
- postInstall := platformRelease.InstallDir.Join(postInstallFilename)
+ postInstall := installDir.Join(postInstallFilename)
if postInstall.Exist() && postInstall.IsNotDir() {
cmd, err := executils.NewProcessFromPath(pme.GetEnvVarsForSpawnedProcess(), postInstall)
if err != nil {
return err
}
- cmd.SetDirFromPath(platformRelease.InstallDir)
+ cmd.SetDirFromPath(installDir)
if err := cmd.Run(); err != nil {
return err
}
@@ -299,7 +299,7 @@ func (pme *Explorer) UninstallPlatform(platformRelease *cores.PlatformRelease, t
}
// InstallTool installs a specific release of a tool.
-func (pme *Explorer) InstallTool(toolRelease *cores.ToolRelease, taskCB rpc.TaskProgressCB) error {
+func (pme *Explorer) InstallTool(toolRelease *cores.ToolRelease, taskCB rpc.TaskProgressCB, skipPostInstall bool) error {
log := pme.log.WithField("Tool", toolRelease)
if toolRelease.IsInstalled() {
@@ -325,6 +325,22 @@ func (pme *Explorer) InstallTool(toolRelease *cores.ToolRelease, taskCB rpc.Task
log.WithError(err).Warn("Cannot install tool")
return &arduino.FailedInstallError{Message: tr("Cannot install tool %s", toolRelease), Cause: err}
}
+ if d, err := destDir.Abs(); err == nil {
+ toolRelease.InstallDir = d
+ } else {
+ return err
+ }
+ // Perform post install
+ if !skipPostInstall {
+ log.Info("Running tool post_install script")
+ taskCB(&rpc.TaskProgress{Message: tr("Configuring tool.")})
+ if err := pme.RunPostInstallScript(toolRelease.InstallDir); err != nil {
+ taskCB(&rpc.TaskProgress{Message: tr("WARNING cannot configure tool: %s", err)})
+ }
+ } else {
+ log.Info("Skipping tool configuration.")
+ taskCB(&rpc.TaskProgress{Message: tr("Skipping tool configuration.")})
+ }
log.Info("Tool installed")
taskCB(&rpc.TaskProgress{Message: tr("%s installed", toolRelease), Completed: true})
diff --git a/commands/instances.go b/commands/instances.go
index ddd7a58452e..4bd1cec9e8d 100644
--- a/commands/instances.go
+++ b/commands/instances.go
@@ -138,7 +138,7 @@ func installTool(pm *packagemanager.PackageManager, tool *cores.ToolRelease, dow
return fmt.Errorf(tr("downloading %[1]s tool: %[2]s"), tool, err)
}
taskCB(&rpc.TaskProgress{Completed: true})
- if err := pme.InstallTool(tool, taskCB); err != nil {
+ if err := pme.InstallTool(tool, taskCB, true); err != nil {
return fmt.Errorf(tr("installing %[1]s tool: %[2]s"), tool, err)
}
return nil
diff --git a/docs/UPGRADING.md b/docs/UPGRADING.md
index 0d867fe05db..5d14fcab452 100644
--- a/docs/UPGRADING.md
+++ b/docs/UPGRADING.md
@@ -2,6 +2,12 @@
Here you can find a list of migration guides to handle breaking changes between releases of the CLI.
+## 0.31.0
+
+### Added `post_install` script support for tools
+
+The `post_install` script now runs when a tool is correctly installed and the CLI is in "interactive" mode. This behavior can be [configured](https://arduino.github.io/arduino-cli/0.30/commands/arduino-cli_core_install/#options).
+
## 0.30.0
### Sketch name validation
diff --git a/docs/sketch-specification.md b/docs/sketch-specification.md
index 6dddad5d028..105c220cfed 100644
--- a/docs/sketch-specification.md
+++ b/docs/sketch-specification.md
@@ -35,8 +35,11 @@ The following extensions are supported:
- .cpp - C++ files.
- .c - C Files.
- .S - Assembly language files.
-- .h - Header files.
-- .tpp, .ipp - Header files (available from Arduino CLI 0.19.0).
+- .h, .hpp, .hh [1](#hpp-hh-note) - Header files.
+- .tpp, .ipp [2](#tpp-ipp-note) - Header files.
+
+ 1 `.hpp` and `.hh` supported from Arduino IDE 1.8.0/arduino-builder 1.3.22.
+ 2 Supported from Arduino CLI 0.19.0.
For information about how each of these files and other parts of the sketch are used during compilation, see the
[Sketch build process documentation](sketch-build-process.md).
diff --git a/internal/cli/compile/compile.go b/internal/cli/compile/compile.go
index cb3c460a46b..318fc021dc3 100644
--- a/internal/cli/compile/compile.go
+++ b/internal/cli/compile/compile.go
@@ -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.
@@ -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."))
@@ -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()
@@ -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,
@@ -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 {
@@ -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"`
@@ -364,7 +411,7 @@ type compileResult struct {
ProfileOut string `json:"profile_out,omitempty"`
Error string `json:"error,omitempty"`
- showOnlyProperties bool
+ showPropertiesMode showPropertiesMode
}
func (r *compileResult) Data() interface{} {
@@ -372,8 +419,8 @@ func (r *compileResult) Data() interface{} {
}
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)
diff --git a/internal/integrationtest/compile_3/compile_show_properties_test.go b/internal/integrationtest/compile_3/compile_show_properties_test.go
index 2aff1164439..780431659d2 100644
--- a/internal/integrationtest/compile_3/compile_show_properties_test.go
+++ b/internal/integrationtest/compile_3/compile_show_properties_test.go
@@ -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()
@@ -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())
@@ -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
}
diff --git a/internal/integrationtest/core/core_test.go b/internal/integrationtest/core/core_test.go
index 94e47b374f9..98117009724 100644
--- a/internal/integrationtest/core/core_test.go
+++ b/internal/integrationtest/core/core_test.go
@@ -992,3 +992,18 @@ func TestCoreInstallCreatesInstalledJson(t *testing.T) {
sortedExpected := requirejson.Parse(t, expectedInstalledJson).Query("walk(if type == \"array\" then sort else . end)").String()
require.JSONEq(t, sortedExpected, sortedInstalled)
}
+
+func TestCoreInstallRunsToolPostInstallScript(t *testing.T) {
+ env, cli := integrationtest.CreateArduinoCLIWithEnvironment(t)
+ defer env.CleanUp()
+
+ url := "http://drazzy.com/package_drazzy.com_index.json"
+
+ _, _, err := cli.Run("core", "update-index", "--additional-urls", url)
+ require.NoError(t, err)
+
+ // Checks that the post_install script is correctly skipped on the CI
+ stdout, _, err := cli.Run("core", "install", "ATTinyCore:avr", "--verbose", "--additional-urls", url)
+ require.NoError(t, err)
+ require.Contains(t, string(stdout), "Skipping tool configuration.")
+}