Commit 1fddba76 authored by Cristian Maglie's avatar Cristian Maglie

Removed global *viper.Viper settings instance

Now the configuration is kept inside the arduinoCoreServiceImpl struct.

No more direct access to the configuration, the required config values
are passed as arguments or available trough struct fields.

Viper object is now embedded into a new configuration.Setting object.
This would allow to make better getters and setters methods in the next
commits.

HTTP downloader configuration is generated using the configuration.
parent 6320f6eb
This diff is collapsed.
......@@ -25,6 +25,7 @@ import (
rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
"github.com/arduino/arduino-cli/version"
"github.com/arduino/go-paths-helper"
"go.bug.st/downloader/v2"
)
// coreInstance is an instance of the Arduino Core Services. The user can
......@@ -133,15 +134,15 @@ func SetLibraryManager(inst *rpc.Instance, lm *librariesmanager.LibrariesManager
}
// Create a new *rpc.Instance ready to be initialized
func Create(dataDir, packagesDir, downloadsDir *paths.Path, extraUserAgent ...string) (*rpc.Instance, error) {
func Create(dataDir, packagesDir, downloadsDir *paths.Path, extraUserAgent string, downloaderConfig downloader.Config) (*rpc.Instance, error) {
// Create package manager
userAgent := "arduino-cli/" + version.VersionInfo.VersionString
for _, ua := range extraUserAgent {
userAgent += " " + ua
if extraUserAgent != "" {
userAgent += " " + extraUserAgent
}
tempDir := dataDir.Join("tmp")
pm := packagemanager.NewBuilder(dataDir, packagesDir, downloadsDir, tempDir, userAgent).Build()
pm := packagemanager.NewBuilder(dataDir, packagesDir, downloadsDir, tempDir, userAgent, downloaderConfig).Build()
lm, _ := librariesmanager.NewBuilder().Build()
instance := &coreInstance{
......
......@@ -18,14 +18,16 @@ package commands
import (
"context"
"github.com/arduino/arduino-cli/internal/cli/configuration"
rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
)
// NewArduinoCoreServer returns an implementation of the ArduinoCoreService gRPC service
// that uses the provided version string.
func NewArduinoCoreServer(version string) rpc.ArduinoCoreServiceServer {
func NewArduinoCoreServer(version string, settings *configuration.Settings) rpc.ArduinoCoreServiceServer {
return &arduinoCoreServerImpl{
versionString: version,
settings: settings,
}
}
......@@ -33,6 +35,9 @@ type arduinoCoreServerImpl struct {
rpc.UnsafeArduinoCoreServiceServer // Force compile error for unimplemented methods
versionString string
// Settings holds configurations of the CLI and the gRPC consumers
settings *configuration.Settings
}
// Version returns the version of the Arduino CLI
......
......@@ -32,7 +32,7 @@ import (
f "github.com/arduino/arduino-cli/internal/algorithms"
"github.com/arduino/arduino-cli/internal/arduino/cores"
"github.com/arduino/arduino-cli/internal/arduino/cores/packagemanager"
"github.com/arduino/arduino-cli/internal/arduino/httpclient"
"github.com/arduino/arduino-cli/internal/cli/configuration"
"github.com/arduino/arduino-cli/internal/inventory"
rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
"github.com/arduino/go-properties-orderedmap"
......@@ -45,7 +45,7 @@ var (
validVidPid = regexp.MustCompile(`0[xX][a-fA-F\d]{4}`)
)
func cachedAPIByVidPid(vid, pid string) ([]*rpc.BoardListItem, error) {
func cachedAPIByVidPid(vid, pid string, settings *configuration.Settings) ([]*rpc.BoardListItem, error) {
var resp []*rpc.BoardListItem
cacheKey := fmt.Sprintf("cache.builder-api.v3/boards/byvid/pid/%s/%s", vid, pid)
......@@ -59,7 +59,7 @@ func cachedAPIByVidPid(vid, pid string) ([]*rpc.BoardListItem, error) {
}
}
resp, err := apiByVidPid(vid, pid) // Perform API requrest
resp, err := apiByVidPid(vid, pid, settings) // Perform API requrest
if err == nil {
if cachedResp, err := json.Marshal(resp); err == nil {
......@@ -71,7 +71,7 @@ func cachedAPIByVidPid(vid, pid string) ([]*rpc.BoardListItem, error) {
return resp, err
}
func apiByVidPid(vid, pid string) ([]*rpc.BoardListItem, error) {
func apiByVidPid(vid, pid string, settings *configuration.Settings) ([]*rpc.BoardListItem, error) {
// ensure vid and pid are valid before hitting the API
if !validVidPid.MatchString(vid) {
return nil, errors.New(tr("Invalid vid value: '%s'", vid))
......@@ -84,10 +84,7 @@ func apiByVidPid(vid, pid string) ([]*rpc.BoardListItem, error) {
req, _ := http.NewRequest("GET", url, nil)
req.Header.Set("Content-Type", "application/json")
// TODO: use proxy if set
httpClient, err := httpclient.New()
httpClient, err := settings.NewHttpClient()
if err != nil {
return nil, fmt.Errorf("%s: %w", tr("failed to initialize http client"), err)
}
......@@ -130,18 +127,18 @@ func apiByVidPid(vid, pid string) ([]*rpc.BoardListItem, error) {
}, nil
}
func identifyViaCloudAPI(props *properties.Map) ([]*rpc.BoardListItem, error) {
func identifyViaCloudAPI(props *properties.Map, settings *configuration.Settings) ([]*rpc.BoardListItem, error) {
// If the port is not USB do not try identification via cloud
if !props.ContainsKey("vid") || !props.ContainsKey("pid") {
return nil, nil
}
logrus.Debug("Querying builder API for board identification...")
return cachedAPIByVidPid(props.Get("vid"), props.Get("pid"))
return cachedAPIByVidPid(props.Get("vid"), props.Get("pid"), settings)
}
// identify returns a list of boards checking first the installed platforms or the Cloud API
func identify(pme *packagemanager.Explorer, port *discovery.Port) ([]*rpc.BoardListItem, error) {
func identify(pme *packagemanager.Explorer, port *discovery.Port, settings *configuration.Settings) ([]*rpc.BoardListItem, error) {
boards := []*rpc.BoardListItem{}
if port.Properties == nil {
return boards, nil
......@@ -173,7 +170,7 @@ func identify(pme *packagemanager.Explorer, port *discovery.Port) ([]*rpc.BoardL
// if installed cores didn't recognize the board, try querying
// the builder API if the board is a USB device port
if len(boards) == 0 {
items, err := identifyViaCloudAPI(port.Properties)
items, err := identifyViaCloudAPI(port.Properties, settings)
if err != nil {
// this is bad, but keep going
logrus.WithError(err).Debug("Error querying builder API")
......@@ -227,7 +224,7 @@ func (s *arduinoCoreServerImpl) BoardList(ctx context.Context, req *rpc.BoardLis
ports := []*rpc.DetectedPort{}
for _, port := range dm.List() {
boards, err := identify(pme, port)
boards, err := identify(pme, port, s.settings)
if err != nil {
warnings = append(warnings, err.Error())
}
......@@ -306,7 +303,7 @@ func (s *arduinoCoreServerImpl) BoardListWatch(req *rpc.BoardListWatchRequest, s
boardsError := ""
if event.Type == "add" {
boards, err := identify(pme, event.Port)
boards, err := identify(pme, event.Port, s.settings)
if err != nil {
boardsError = err.Error()
}
......
......@@ -27,12 +27,11 @@ import (
"github.com/arduino/go-properties-orderedmap"
discovery "github.com/arduino/pluggable-discovery-protocol-handler/v2"
"github.com/stretchr/testify/require"
"go.bug.st/downloader/v2"
semver "go.bug.st/relaxed-semver"
)
func TestGetByVidPid(t *testing.T) {
configuration.Settings = configuration.Init("")
configuration.Settings.Set("locale", "en")
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, `
{
......@@ -49,34 +48,31 @@ func TestGetByVidPid(t *testing.T) {
defer ts.Close()
vidPidURL = ts.URL
res, err := apiByVidPid("0xf420", "0XF069")
res, err := apiByVidPid("0xf420", "0XF069", configuration.Init(""))
require.Nil(t, err)
require.Len(t, res, 1)
require.Equal(t, "Arduino/Genuino MKR1000", res[0].GetName())
require.Equal(t, "arduino:samd:mkr1000", res[0].GetFqbn())
// wrong vid (too long), wrong pid (not an hex value)
_, err = apiByVidPid("0xfffff", "0xDEFG")
_, err = apiByVidPid("0xfffff", "0xDEFG", configuration.Init(""))
require.NotNil(t, err)
}
func TestGetByVidPidNotFound(t *testing.T) {
configuration.Settings = configuration.Init("")
configuration.Settings.Set("locale", "en")
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNotFound)
}))
defer ts.Close()
vidPidURL = ts.URL
res, err := apiByVidPid("0x0420", "0x0069")
res, err := apiByVidPid("0x0420", "0x0069", configuration.Init(""))
require.NoError(t, err)
require.Empty(t, res)
}
func TestGetByVidPid5xx(t *testing.T) {
configuration.Settings = configuration.Init("")
configuration.Settings.Set("locale", "en")
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte("500 - Ooooops!"))
......@@ -84,46 +80,39 @@ func TestGetByVidPid5xx(t *testing.T) {
defer ts.Close()
vidPidURL = ts.URL
res, err := apiByVidPid("0x0420", "0x0069")
res, err := apiByVidPid("0x0420", "0x0069", configuration.Init(""))
require.NotNil(t, err)
require.Equal(t, "the server responded with status 500 Internal Server Error", err.Error())
require.Len(t, res, 0)
}
func TestGetByVidPidMalformedResponse(t *testing.T) {
configuration.Settings = configuration.Init("")
configuration.Settings.Set("locale", "en")
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "{}")
}))
defer ts.Close()
vidPidURL = ts.URL
res, err := apiByVidPid("0x0420", "0x0069")
res, err := apiByVidPid("0x0420", "0x0069", configuration.Init(""))
require.NotNil(t, err)
require.Equal(t, "wrong format in server response", err.Error())
require.Len(t, res, 0)
}
func TestBoardDetectionViaAPIWithNonUSBPort(t *testing.T) {
configuration.Settings = configuration.Init("")
configuration.Settings.Set("locale", "en")
items, err := identifyViaCloudAPI(properties.NewMap())
items, err := identifyViaCloudAPI(properties.NewMap(), configuration.Init(""))
require.NoError(t, err)
require.Empty(t, items)
}
func TestBoardIdentifySorting(t *testing.T) {
configuration.Settings = configuration.Init("")
configuration.Settings.Set("locale", "en")
dataDir := paths.TempDir().Join("test", "data_dir")
t.Setenv("ARDUINO_DATA_DIR", dataDir.String())
dataDir.MkdirAll()
defer paths.TempDir().Join("test").RemoveAll()
// We don't really care about the paths in this case
pmb := packagemanager.NewBuilder(dataDir, dataDir, dataDir, dataDir, "test")
pmb := packagemanager.NewBuilder(dataDir, dataDir, dataDir, dataDir, "test", downloader.GetDefaultConfig())
// Create some boards with identical VID:PID combination
pack := pmb.GetOrCreatePackage("packager")
......@@ -159,7 +148,8 @@ func TestBoardIdentifySorting(t *testing.T) {
pme, release := pm.NewExplorer()
defer release()
res, err := identify(pme, &discovery.Port{Properties: idPrefs})
settings := configuration.Init("")
res, err := identify(pme, &discovery.Port{Properties: idPrefs}, settings)
require.NoError(t, err)
require.NotNil(t, res)
require.Len(t, res, 4)
......
......@@ -24,7 +24,7 @@ import (
// CleanDownloadCacheDirectory clean the download cache directory (where archives are downloaded).
func (s *arduinoCoreServerImpl) CleanDownloadCacheDirectory(ctx context.Context, req *rpc.CleanDownloadCacheDirectoryRequest) (*rpc.CleanDownloadCacheDirectoryResponse, error) {
cachePath := configuration.DownloadsDir(configuration.Settings)
cachePath := configuration.DownloadsDir(s.settings)
err := cachePath.RemoveAll()
if err != nil {
return nil, err
......
......@@ -20,8 +20,6 @@ import (
"strings"
"time"
"github.com/arduino/arduino-cli/internal/arduino/httpclient"
"github.com/arduino/arduino-cli/internal/cli/configuration"
"github.com/arduino/arduino-cli/internal/cli/feedback"
"github.com/arduino/arduino-cli/internal/inventory"
rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
......@@ -35,7 +33,7 @@ func (s *arduinoCoreServerImpl) CheckForArduinoCLIUpdates(ctx context.Context, r
return nil, err
}
if !shouldCheckForUpdate(currentVersion) && !req.GetForceCheck() {
if !s.shouldCheckForUpdate(currentVersion) && !req.GetForceCheck() {
return &rpc.CheckForArduinoCLIUpdatesResponse{}, nil
}
......@@ -45,7 +43,7 @@ func (s *arduinoCoreServerImpl) CheckForArduinoCLIUpdates(ctx context.Context, r
inventory.WriteStore()
}()
latestVersion, err := semver.Parse(getLatestRelease())
latestVersion, err := semver.Parse(s.getLatestRelease())
if err != nil {
return nil, err
}
......@@ -62,13 +60,13 @@ func (s *arduinoCoreServerImpl) CheckForArduinoCLIUpdates(ctx context.Context, r
// shouldCheckForUpdate return true if it actually makes sense to check for new updates,
// false in all other cases.
func shouldCheckForUpdate(currentVersion *semver.Version) bool {
func (s *arduinoCoreServerImpl) shouldCheckForUpdate(currentVersion *semver.Version) bool {
if strings.Contains(currentVersion.String(), "git-snapshot") || strings.Contains(currentVersion.String(), "nightly") {
// This is a dev build, no need to check for updates
return false
}
if !configuration.Settings.GetBool("updater.enable_notification") {
if !s.settings.GetBool("updater.enable_notification") {
// Don't check if the user disabled the notification
return false
}
......@@ -84,8 +82,8 @@ func shouldCheckForUpdate(currentVersion *semver.Version) bool {
// getLatestRelease queries the official Arduino download server for the latest release,
// if there are no errors or issues a version string is returned, in all other case an empty string.
func getLatestRelease() string {
client, err := httpclient.New()
func (s *arduinoCoreServerImpl) getLatestRelease() string {
client, err := s.settings.NewHttpClient()
if err != nil {
return ""
}
......
......@@ -22,6 +22,7 @@ import (
"io"
"sort"
"strings"
"time"
"github.com/arduino/arduino-cli/commands/cmderrors"
"github.com/arduino/arduino-cli/commands/internal/instances"
......@@ -66,7 +67,7 @@ func (s *arduinoCoreServerImpl) Compile(req *rpc.CompileRequest, stream rpc.Ardu
ctx := stream.Context()
syncSend := NewSynchronizedSend(stream.Send)
exportBinaries := configuration.Settings.GetBool("sketch.always_export_binaries")
exportBinaries := s.settings.GetBool("sketch.always_export_binaries")
if e := req.ExportBinaries; e != nil {
exportBinaries = *e
}
......@@ -172,7 +173,10 @@ func (s *arduinoCoreServerImpl) Compile(req *rpc.CompileRequest, stream rpc.Ardu
}
buildcache.New(buildPath.Parent()).GetOrCreate(buildPath.Base())
// cache is purged after compilation to not remove entries that might be required
defer maybePurgeBuildCache()
defer maybePurgeBuildCache(
s.settings.GetUint("build_cache.compilations_before_purge"),
s.settings.GetDuration("build_cache.ttl").Abs())
var coreBuildCachePath *paths.Path
if req.GetBuildCachePath() == "" {
......@@ -194,7 +198,7 @@ func (s *arduinoCoreServerImpl) Compile(req *rpc.CompileRequest, stream rpc.Ardu
actualPlatform := buildPlatform
otherLibrariesDirs := paths.NewPathList(req.GetLibraries()...)
otherLibrariesDirs.Add(configuration.LibrariesDir(configuration.Settings))
otherLibrariesDirs.Add(configuration.LibrariesDir(s.settings))
var libsManager *librariesmanager.LibrariesManager
if pme.GetProfile() != nil {
......@@ -227,9 +231,9 @@ func (s *arduinoCoreServerImpl) Compile(req *rpc.CompileRequest, stream rpc.Ardu
coreBuildCachePath,
int(req.GetJobs()),
req.GetBuildProperties(),
configuration.HardwareDirectories(configuration.Settings),
configuration.HardwareDirectories(s.settings),
otherLibrariesDirs,
configuration.IDEBuiltinLibrariesDir(configuration.Settings),
configuration.IDEBuiltinLibrariesDir(s.settings),
fqbn,
req.GetClean(),
req.GetSourceOverride(),
......@@ -394,9 +398,7 @@ func (s *arduinoCoreServerImpl) Compile(req *rpc.CompileRequest, stream rpc.Ardu
}
// maybePurgeBuildCache runs the build files cache purge if the policy conditions are met.
func maybePurgeBuildCache() {
compilationsBeforePurge := configuration.Settings.GetUint("build_cache.compilations_before_purge")
func maybePurgeBuildCache(compilationsBeforePurge uint, cacheTTL time.Duration) {
// 0 means never purge
if compilationsBeforePurge == 0 {
return
......@@ -409,7 +411,6 @@ func maybePurgeBuildCache() {
return
}
inventory.Store.Set("build_cache.compilation_count_since_last_purge", 0)
cacheTTL := configuration.Settings.GetDuration("build_cache.ttl").Abs()
buildcache.New(paths.TempDir().Join("arduino", "cores")).Purge(cacheTTL)
buildcache.New(paths.TempDir().Join("arduino", "sketches")).Purge(cacheTTL)
}
......
......@@ -27,6 +27,7 @@ import (
"github.com/arduino/go-properties-orderedmap"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.bug.st/downloader/v2"
)
func TestGetCommandLine(t *testing.T) {
......@@ -36,7 +37,7 @@ func TestGetCommandLine(t *testing.T) {
sketchPath := paths.New("testdata", "debug", sketch)
require.NoError(t, sketchPath.ToAbs())
pmb := packagemanager.NewBuilder(nil, nil, nil, nil, "test")
pmb := packagemanager.NewBuilder(nil, nil, nil, nil, "test", downloader.GetDefaultConfig())
pmb.LoadHardwareFromDirectory(customHardware)
pmb.LoadHardwareFromDirectory(dataDir)
......
......@@ -20,8 +20,8 @@ import (
"github.com/arduino/arduino-cli/commands/cmderrors"
"github.com/arduino/arduino-cli/commands/internal/instances"
"github.com/arduino/arduino-cli/internal/arduino/httpclient"
"github.com/arduino/arduino-cli/internal/arduino/libraries/librariesindex"
"github.com/arduino/arduino-cli/internal/cli/configuration"
rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
"github.com/arduino/go-paths-helper"
)
......@@ -66,7 +66,7 @@ func (s *arduinoCoreServerImpl) LibraryDownload(req *rpc.LibraryDownloadRequest,
return err
}
if err := downloadLibrary(ctx, downloadsDir, lib, downloadCB, func(*rpc.TaskProgress) {}, "download"); err != nil {
if err := downloadLibrary(ctx, downloadsDir, lib, downloadCB, func(*rpc.TaskProgress) {}, "download", s.settings); err != nil {
return err
}
......@@ -74,10 +74,10 @@ func (s *arduinoCoreServerImpl) LibraryDownload(req *rpc.LibraryDownloadRequest,
}
func downloadLibrary(_ context.Context, downloadsDir *paths.Path, libRelease *librariesindex.Release,
downloadCB rpc.DownloadProgressCB, taskCB rpc.TaskProgressCB, queryParameter string) error {
downloadCB rpc.DownloadProgressCB, taskCB rpc.TaskProgressCB, queryParameter string, settings *configuration.Settings) error {
taskCB(&rpc.TaskProgress{Name: tr("Downloading %s", libRelease)})
config, err := httpclient.GetDownloaderConfig()
config, err := settings.DownloaderConfig()
if err != nil {
return &cmderrors.FailedDownloadError{Message: tr("Can't download library"), Cause: err}
}
......
......@@ -147,7 +147,7 @@ func (s *arduinoCoreServerImpl) LibraryInstall(req *rpc.LibraryInstallRequest, s
downloadReason += "-builtin"
}
}
if err := downloadLibrary(ctx, downloadsDir, libRelease, downloadCB, taskCB, downloadReason); err != nil {
if err := downloadLibrary(ctx, downloadsDir, libRelease, downloadCB, taskCB, downloadReason, s.settings); err != nil {
return err
}
if err := installLibrary(lmi, downloadsDir, libRelease, installTask, taskCB); err != nil {
......
......@@ -67,13 +67,13 @@ func (s *arduinoCoreServerImpl) PlatformDownload(req *rpc.PlatformDownloadReques
// TODO: pass context
// ctx := stream.Context()
if err := pme.DownloadPlatformRelease(platform, nil, downloadCB); err != nil {
if err := pme.DownloadPlatformRelease(platform, downloadCB); err != nil {
return err
}
for _, tool := range tools {
// TODO: pass context
if err := pme.DownloadToolRelease(tool, nil, downloadCB); err != nil {
if err := pme.DownloadToolRelease(tool, downloadCB); err != nil {
return err
}
}
......
......@@ -36,9 +36,8 @@ func TestPlatformSearch(t *testing.T) {
err := paths.New("testdata", "platform", "package_index.json").CopyTo(dataDir.Join("package_index.json"))
require.Nil(t, err)
configuration.Settings = configuration.Init(paths.TempDir().Join("test", "arduino-cli.yaml").String())
srv := NewArduinoCoreServer("")
settings := configuration.Init(paths.TempDir().Join("test", "arduino-cli.yaml").String())
srv := NewArduinoCoreServer("", settings)
ctx := context.Background()
createResp, err := srv.Create(ctx, &rpc.CreateRequest{})
require.NoError(t, err)
......@@ -338,9 +337,8 @@ func TestPlatformSearchSorting(t *testing.T) {
err := paths.New("testdata", "platform", "package_index.json").CopyTo(dataDir.Join("package_index.json"))
require.Nil(t, err)
configuration.Settings = configuration.Init(paths.TempDir().Join("test", "arduino-cli.yaml").String())
srv := NewArduinoCoreServer("")
settings := configuration.Init(paths.TempDir().Join("test", "arduino-cli.yaml").String())
srv := NewArduinoCoreServer("", settings)
ctx := context.Background()
createResp, err := srv.Create(ctx, &rpc.CreateRequest{})
......
......@@ -29,7 +29,7 @@ import (
// SettingsGetAll returns a message with a string field containing all the settings
// currently in use, marshalled in JSON format.
func (s *arduinoCoreServerImpl) SettingsGetAll(ctx context.Context, req *rpc.SettingsGetAllRequest) (*rpc.SettingsGetAllResponse, error) {
b, err := json.Marshal(configuration.Settings.AllSettings())
b, err := json.Marshal(s.settings.AllSettings())
if err == nil {
return &rpc.SettingsGetAllResponse{
JsonData: string(b),
......@@ -83,9 +83,9 @@ func (s *arduinoCoreServerImpl) SettingsMerge(ctx context.Context, req *rpc.Sett
for k, v := range mapped {
updatedSettings.Set(k, v)
}
configPath := configuration.Settings.ConfigFileUsed()
configPath := s.settings.ConfigFileUsed()
updatedSettings.SetConfigFile(configPath)
configuration.Settings = updatedSettings
s.settings = updatedSettings
return &rpc.SettingsMergeResponse{}, nil
}
......@@ -100,7 +100,7 @@ func (s *arduinoCoreServerImpl) SettingsGetValue(ctx context.Context, req *rpc.S
// since that doesn't check for keys formatted like daemon.port or those set
// with Viper.Set(). This way we check for all existing settings for sure.
keyExists := false
for _, k := range configuration.Settings.AllKeys() {
for _, k := range s.settings.AllKeys() {
if k == key || strings.HasPrefix(k, key) {
keyExists = true
break
......@@ -110,7 +110,7 @@ func (s *arduinoCoreServerImpl) SettingsGetValue(ctx context.Context, req *rpc.S
return nil, errors.New(tr("key not found in settings"))
}
b, err := json.Marshal(configuration.Settings.Get(key))
b, err := json.Marshal(s.settings.Get(key))
value := &rpc.SettingsGetValueResponse{}
if err == nil {
value.Key = key
......@@ -127,7 +127,7 @@ func (s *arduinoCoreServerImpl) SettingsSetValue(ctx context.Context, val *rpc.S
err := json.Unmarshal([]byte(val.GetJsonData()), &value)
if err == nil {
configuration.Settings.Set(key, value)
s.settings.Set(key, value)
}
return &rpc.SettingsSetValueResponse{}, err
......@@ -138,7 +138,7 @@ func (s *arduinoCoreServerImpl) SettingsSetValue(ctx context.Context, val *rpc.S
// and that's picked up when the CLI is run as daemon, either using the default path or a custom one
// set with the --config-file flag.
func (s *arduinoCoreServerImpl) SettingsWrite(ctx context.Context, req *rpc.SettingsWriteRequest) (*rpc.SettingsWriteResponse, error) {
if err := configuration.Settings.WriteConfigAs(req.GetFilePath()); err != nil {
if err := s.settings.WriteConfigAs(req.GetFilePath()); err != nil {
return nil, err
}
return &rpc.SettingsWriteResponse{}, nil
......@@ -153,7 +153,7 @@ func (s *arduinoCoreServerImpl) SettingsDelete(ctx context.Context, req *rpc.Set
// with Viper.Set(). This way we check for all existing settings for sure.
keyExists := false
keys := []string{}
for _, k := range configuration.Settings.AllKeys() {
for _, k := range s.settings.AllKeys() {
if !strings.HasPrefix(k, toDelete) {
keys = append(keys, k)
continue
......@@ -168,11 +168,11 @@ func (s *arduinoCoreServerImpl) SettingsDelete(ctx context.Context, req *rpc.Set
// Override current settings to delete the key
updatedSettings := configuration.Init("")
for _, k := range keys {
updatedSettings.Set(k, configuration.Settings.Get(k))
updatedSettings.Set(k, s.settings.Get(k))
}
configPath := configuration.Settings.ConfigFileUsed()
configPath := s.settings.ConfigFileUsed()
updatedSettings.SetConfigFile(configPath)
configuration.Settings = updatedSettings
s.settings = updatedSettings
return &rpc.SettingsDeleteResponse{}, nil
}
......@@ -27,71 +27,68 @@ import (
"github.com/stretchr/testify/require"
)
var svc = NewArduinoCoreServer("")
func init() {
configuration.Settings = configuration.Init(filepath.Join("testdata", "arduino-cli.yaml"))
}
func reset() {
configuration.Settings = configuration.Init(filepath.Join("testdata", "arduino-cli.yaml"))
}
func TestGetAll(t *testing.T) {
settings := configuration.Init(filepath.Join("testdata", "arduino-cli.yaml"))
svc := NewArduinoCoreServer("", settings)
resp, err := svc.SettingsGetAll(context.Background(), &rpc.SettingsGetAllRequest{})
require.Nil(t, err)
content, err := json.Marshal(configuration.Settings.AllSettings())
content, err := json.Marshal(settings.AllSettings())
require.Nil(t, err)
require.Equal(t, string(content), resp.GetJsonData())
}
func TestMerge(t *testing.T) {
initialSettings := configuration.Init(filepath.Join("testdata", "arduino-cli.yaml"))
svc := NewArduinoCoreServer("", initialSettings).(*arduinoCoreServerImpl)
ctx := context.Background()
// Verify defaults
require.Equal(t, "50051", configuration.Settings.GetString("daemon.port"))
require.Equal(t, "", configuration.Settings.GetString("foo"))
require.Equal(t, false, configuration.Settings.GetBool("sketch.always_export_binaries"))
require.Equal(t, "50051", svc.settings.GetString("daemon.port"))
require.Equal(t, "", svc.settings.GetString("foo"))
require.Equal(t, false, svc.settings.GetBool("sketch.always_export_binaries"))
bulkSettings := `{"foo": "bar", "daemon":{"port":"420"}, "sketch": {"always_export_binaries": "true"}}`
res, err := svc.SettingsMerge(context.Background(), &rpc.SettingsMergeRequest{JsonData: bulkSettings})
res, err := svc.SettingsMerge(ctx, &rpc.SettingsMergeRequest{JsonData: bulkSettings})
require.NotNil(t, res)
require.NoError(t, err)
require.Equal(t, "420", configuration.Settings.GetString("daemon.port"))
require.Equal(t, "bar", configuration.Settings.GetString("foo"))
require.Equal(t, true, configuration.Settings.GetBool("sketch.always_export_binaries"))
require.Equal(t, "420", svc.settings.GetString("daemon.port"))
require.Equal(t, "bar", svc.settings.GetString("foo"))
require.Equal(t, true, svc.settings.GetBool("sketch.always_export_binaries"))
bulkSettings = `{"foo":"", "daemon": {}, "sketch": {"always_export_binaries": "false"}}`
res, err = svc.SettingsMerge(context.Background(), &rpc.SettingsMergeRequest{JsonData: bulkSettings})
res, err = svc.SettingsMerge(ctx, &rpc.SettingsMergeRequest{JsonData: bulkSettings})
require.NotNil(t, res)
require.NoError(t, err)
require.Equal(t, "50051", configuration.Settings.GetString("daemon.port"))
require.Equal(t, "", configuration.Settings.GetString("foo"))
require.Equal(t, false, configuration.Settings.GetBool("sketch.always_export_binaries"))
require.Equal(t, "50051", svc.settings.GetString("daemon.port"))
require.Equal(t, "", svc.settings.GetString("foo"))
require.Equal(t, false, svc.settings.GetBool("sketch.always_export_binaries"))
bulkSettings = `{"daemon": {"port":""}}`
res, err = svc.SettingsMerge(context.Background(), &rpc.SettingsMergeRequest{JsonData: bulkSettings})
res, err = svc.SettingsMerge(ctx, &rpc.SettingsMergeRequest{JsonData: bulkSettings})
require.NotNil(t, res)
require.NoError(t, err)
require.Equal(t, "", configuration.Settings.GetString("daemon.port"))
require.Equal(t, "", svc.settings.GetString("daemon.port"))
// Verifies other values are not changed
require.Equal(t, "", configuration.Settings.GetString("foo"))
require.Equal(t, false, configuration.Settings.GetBool("sketch.always_export_binaries"))
require.Equal(t, "", svc.settings.GetString("foo"))
require.Equal(t, false, svc.settings.GetBool("sketch.always_export_binaries"))
bulkSettings = `{"network": {}}`
res, err = svc.SettingsMerge(context.Background(), &rpc.SettingsMergeRequest{JsonData: bulkSettings})
res, err = svc.SettingsMerge(ctx, &rpc.SettingsMergeRequest{JsonData: bulkSettings})
require.NotNil(t, res)
require.NoError(t, err)
require.Equal(t, "", configuration.Settings.GetString("proxy"))
reset()
require.Equal(t, "", svc.settings.GetString("proxy"))
}
func TestGetValue(t *testing.T) {
settings := configuration.Init(filepath.Join("testdata", "arduino-cli.yaml"))
svc := NewArduinoCoreServer("", settings)
key := &rpc.SettingsGetValueRequest{Key: "daemon"}
resp, err := svc.SettingsGetValue(context.Background(), key)
require.NoError(t, err)
......@@ -104,6 +101,9 @@ func TestGetValue(t *testing.T) {
}
func TestGetMergedValue(t *testing.T) {
settings := configuration.Init(filepath.Join("testdata", "arduino-cli.yaml"))
svc := NewArduinoCoreServer("", settings)
// Verifies value is not set
key := &rpc.SettingsGetValueRequest{Key: "foo"}
res, err := svc.SettingsGetValue(context.Background(), key)
......@@ -120,27 +120,34 @@ func TestGetMergedValue(t *testing.T) {
res, err = svc.SettingsGetValue(context.Background(), key)
require.NoError(t, err)
require.Equal(t, `"bar"`, res.GetJsonData())
reset()
}
func TestGetValueNotFound(t *testing.T) {
settings := configuration.Init(filepath.Join("testdata", "arduino-cli.yaml"))
svc := NewArduinoCoreServer("", settings)
key := &rpc.SettingsGetValueRequest{Key: "DOESNTEXIST"}
_, err := svc.SettingsGetValue(context.Background(), key)
require.Error(t, err)
}
func TestSetValue(t *testing.T) {
settings := configuration.Init(filepath.Join("testdata", "arduino-cli.yaml"))
svc := NewArduinoCoreServer("", settings)
val := &rpc.SettingsSetValueRequest{
Key: "foo",
JsonData: `"bar"`,
}
_, err := svc.SettingsSetValue(context.Background(), val)
require.Nil(t, err)
require.Equal(t, "bar", configuration.Settings.GetString("foo"))
require.Equal(t, "bar", settings.GetString("foo"))
}
func TestWrite(t *testing.T) {
settings := configuration.Init(filepath.Join("testdata", "arduino-cli.yaml"))
svc := NewArduinoCoreServer("", settings)
// Writes some settings
val := &rpc.SettingsSetValueRequest{
Key: "foo",
......@@ -169,6 +176,9 @@ func TestWrite(t *testing.T) {
}
func TestDelete(t *testing.T) {
settings := configuration.Init(filepath.Join("testdata", "arduino-cli.yaml"))
svc := NewArduinoCoreServer("", settings)
_, err := svc.SettingsDelete(context.Background(), &rpc.SettingsDeleteRequest{
Key: "doesnotexist",
})
......
......@@ -19,12 +19,13 @@ import (
"context"
"testing"
"github.com/arduino/arduino-cli/internal/cli/configuration"
"github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
"github.com/stretchr/testify/require"
)
func TestLoadSketchProfiles(t *testing.T) {
srv := NewArduinoCoreServer("")
srv := NewArduinoCoreServer("", configuration.Init(""))
loadResp, err := srv.LoadSketch(context.Background(), &commands.LoadSketchRequest{
SketchPath: "./testdata/sketch_with_profile",
})
......
......@@ -22,7 +22,6 @@ import (
"github.com/arduino/arduino-cli/commands/cmderrors"
"github.com/arduino/arduino-cli/internal/arduino/globals"
"github.com/arduino/arduino-cli/internal/cli/configuration"
rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
paths "github.com/arduino/go-paths-helper"
)
......@@ -48,7 +47,7 @@ func (s *arduinoCoreServerImpl) NewSketch(ctx context.Context, req *rpc.NewSketc
if len(req.GetSketchDir()) > 0 {
sketchesDir = req.GetSketchDir()
} else {
sketchesDir = configuration.Settings.GetString("directories.User")
sketchesDir = s.settings.GetString("directories.User")
}
if err := validateSketchName(req.GetSketchName()); err != nil {
......
......@@ -20,6 +20,7 @@ import (
"fmt"
"testing"
"github.com/arduino/arduino-cli/internal/cli/configuration"
rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
"github.com/stretchr/testify/require"
)
......@@ -35,7 +36,7 @@ func Test_SketchNameWrongPattern(t *testing.T) {
",`hack[}attempt{];",
}
srv := NewArduinoCoreServer("")
srv := NewArduinoCoreServer("", configuration.Init(""))
for _, name := range invalidNames {
_, err := srv.NewSketch(context.Background(), &rpc.NewSketchRequest{
SketchName: name,
......@@ -48,7 +49,7 @@ func Test_SketchNameWrongPattern(t *testing.T) {
}
func Test_SketchNameEmpty(t *testing.T) {
srv := NewArduinoCoreServer("")
srv := NewArduinoCoreServer("", configuration.Init(""))
_, err := srv.NewSketch(context.Background(), &rpc.NewSketchRequest{
SketchName: "",
SketchDir: t.TempDir(),
......@@ -62,7 +63,7 @@ func Test_SketchNameTooLong(t *testing.T) {
for i := range tooLongName {
tooLongName[i] = 'a'
}
srv := NewArduinoCoreServer("")
srv := NewArduinoCoreServer("", configuration.Init(""))
_, err := srv.NewSketch(context.Background(), &rpc.NewSketchRequest{
SketchName: string(tooLongName),
SketchDir: t.TempDir(),
......@@ -86,7 +87,7 @@ func Test_SketchNameOk(t *testing.T) {
"_hello_world",
string(lengthLimitName),
}
srv := NewArduinoCoreServer("")
srv := NewArduinoCoreServer("", configuration.Init(""))
for _, name := range validNames {
_, err := srv.NewSketch(context.Background(), &rpc.NewSketchRequest{
SketchName: name,
......@@ -99,7 +100,7 @@ func Test_SketchNameOk(t *testing.T) {
func Test_SketchNameReserved(t *testing.T) {
invalidNames := []string{"CON", "PRN", "AUX", "NUL", "COM0", "COM1", "COM2", "COM3", "COM4", "COM5",
"COM6", "COM7", "COM8", "COM9", "LPT0", "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9"}
srv := NewArduinoCoreServer("")
srv := NewArduinoCoreServer("", configuration.Init(""))
for _, name := range invalidNames {
_, err := srv.NewSketch(context.Background(), &rpc.NewSketchRequest{
SketchName: name,
......
......@@ -29,6 +29,7 @@ import (
properties "github.com/arduino/go-properties-orderedmap"
"github.com/sirupsen/logrus"
"github.com/stretchr/testify/require"
"go.bug.st/downloader/v2"
)
func TestDetectSketchNameFromBuildPath(t *testing.T) {
......@@ -127,7 +128,7 @@ func TestDetermineBuildPathAndSketchName(t *testing.T) {
}
func TestUploadPropertiesComposition(t *testing.T) {
pmb := packagemanager.NewBuilder(nil, nil, nil, nil, "test")
pmb := packagemanager.NewBuilder(nil, nil, nil, nil, "test", downloader.GetDefaultConfig())
errs := pmb.LoadHardwareFromDirectory(paths.New("testdata", "upload", "hardware"))
require.Len(t, errs, 0)
buildPath1 := paths.New("testdata", "upload", "build_path_1")
......
......@@ -22,7 +22,6 @@ import (
"github.com/arduino/arduino-cli/commands/cmderrors"
"github.com/arduino/arduino-cli/internal/arduino/cores"
rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
"go.bug.st/downloader/v2"
semver "go.bug.st/relaxed-semver"
)
......@@ -120,21 +119,21 @@ func (pme *Explorer) FindPlatformReleaseDependencies(item *PlatformReference) (*
// DownloadToolRelease downloads a ToolRelease. If the tool is already downloaded a nil Downloader
// is returned. Uses the given downloader configuration for download, or the default config if nil.
func (pme *Explorer) DownloadToolRelease(tool *cores.ToolRelease, config *downloader.Config, progressCB rpc.DownloadProgressCB) error {
func (pme *Explorer) DownloadToolRelease(tool *cores.ToolRelease, progressCB rpc.DownloadProgressCB) error {
resource := tool.GetCompatibleFlavour()
if resource == nil {
return &cmderrors.FailedDownloadError{
Message: tr("Error downloading tool %s", tool),
Cause: errors.New(tr("no versions available for the current OS, try contacting %s", tool.Tool.Package.Email))}
}
return resource.Download(pme.DownloadDir, config, tool.String(), progressCB, "")
return resource.Download(pme.DownloadDir, pme.downloaderConfig, tool.String(), progressCB, "")
}
// DownloadPlatformRelease downloads a PlatformRelease. If the platform is already downloaded a
// nil Downloader is returned.
func (pme *Explorer) DownloadPlatformRelease(platform *cores.PlatformRelease, config *downloader.Config, progressCB rpc.DownloadProgressCB) error {
func (pme *Explorer) DownloadPlatformRelease(platform *cores.PlatformRelease, progressCB rpc.DownloadProgressCB) error {
if platform.Resource == nil {
return &cmderrors.PlatformNotFoundError{Platform: platform.String()}
}
return platform.Resource.Download(pme.DownloadDir, config, platform.String(), progressCB, "")
return platform.Resource.Download(pme.DownloadDir, pme.downloaderConfig, platform.String(), progressCB, "")
}
......@@ -92,11 +92,11 @@ func (pme *Explorer) DownloadAndInstallPlatformAndTools(
// Package download
taskCB(&rpc.TaskProgress{Name: tr("Downloading packages")})
for _, tool := range toolsToInstall {
if err := pme.DownloadToolRelease(tool, nil, downloadCB); err != nil {
if err := pme.DownloadToolRelease(tool, downloadCB); err != nil {
return err
}
}
if err := pme.DownloadPlatformRelease(platformRelease, nil, downloadCB); err != nil {
if err := pme.DownloadPlatformRelease(platformRelease, downloadCB); err != nil {
return err
}
taskCB(&rpc.TaskProgress{Completed: true})
......
......@@ -31,8 +31,8 @@ import (
)
// LoadHardware read all plaforms from the configured paths
func (pm *Builder) LoadHardware() []error {
hardwareDirs := configuration.HardwareDirectories(configuration.Settings)
func (pm *Builder) LoadHardware(settings *configuration.Settings) []error {
hardwareDirs := configuration.HardwareDirectories(settings)
return pm.LoadHardwareFromDirectories(hardwareDirs)
}
......
......@@ -21,6 +21,7 @@ import (
"github.com/arduino/go-paths-helper"
"github.com/arduino/go-properties-orderedmap"
"github.com/stretchr/testify/require"
"go.bug.st/downloader/v2"
semver "go.bug.st/relaxed-semver"
)
......@@ -174,7 +175,7 @@ func TestLoadDiscoveries(t *testing.T) {
defer fakePath.RemoveAll()
createTestPackageManager := func() *PackageManager {
pmb := NewBuilder(fakePath, fakePath, fakePath, fakePath, "test")
pmb := NewBuilder(fakePath, fakePath, fakePath, fakePath, "test", downloader.GetDefaultConfig())
pack := pmb.packages.GetOrCreatePackage("arduino")
// ble-discovery tool
tool := pack.GetOrCreateTool("ble-discovery")
......
......@@ -33,12 +33,12 @@ import (
"github.com/arduino/arduino-cli/internal/arduino/cores/packageindex"
"github.com/arduino/arduino-cli/internal/arduino/discovery/discoverymanager"
"github.com/arduino/arduino-cli/internal/arduino/sketch"
"github.com/arduino/arduino-cli/internal/cli/configuration"
"github.com/arduino/arduino-cli/internal/i18n"
paths "github.com/arduino/go-paths-helper"
properties "github.com/arduino/go-properties-orderedmap"
"github.com/arduino/go-timeutils"
"github.com/sirupsen/logrus"
"go.bug.st/downloader/v2"
semver "go.bug.st/relaxed-semver"
)
......@@ -60,6 +60,7 @@ type PackageManager struct {
profile *sketch.Profile
discoveryManager *discoverymanager.DiscoveryManager
userAgent string
downloaderConfig downloader.Config
}
// Builder is used to create a new PackageManager. The builder
......@@ -75,7 +76,7 @@ type Explorer PackageManager
var tr = i18n.Tr
// NewBuilder returns a new Builder
func NewBuilder(indexDir, packagesDir, downloadDir, tempDir *paths.Path, userAgent string) *Builder {
func NewBuilder(indexDir, packagesDir, downloadDir, tempDir *paths.Path, userAgent string, downloaderConfig downloader.Config) *Builder {
return &Builder{
log: logrus.StandardLogger(),
packages: cores.NewPackages(),
......@@ -84,8 +85,9 @@ func NewBuilder(indexDir, packagesDir, downloadDir, tempDir *paths.Path, userAge
DownloadDir: downloadDir,
tempDir: tempDir,
packagesCustomGlobalProperties: properties.NewMap(),
discoveryManager: discoverymanager.New(configuration.UserAgent(configuration.Settings)),
discoveryManager: discoverymanager.New(userAgent),
userAgent: userAgent,
downloaderConfig: downloaderConfig,
}
}
......@@ -164,7 +166,7 @@ func (pmb *Builder) calculateCompatibleReleases() {
// this function will make the builder write the new configuration into this
// PackageManager.
func (pm *PackageManager) NewBuilder() (builder *Builder, commit func()) {
pmb := NewBuilder(pm.IndexDir, pm.PackagesDir, pm.DownloadDir, pm.tempDir, pm.userAgent)
pmb := NewBuilder(pm.IndexDir, pm.PackagesDir, pm.DownloadDir, pm.tempDir, pm.userAgent, pm.downloaderConfig)
return pmb, func() {
pmb.calculateCompatibleReleases()
pmb.BuildIntoExistingPackageManager(pm)
......@@ -188,6 +190,7 @@ func (pm *PackageManager) NewExplorer() (explorer *Explorer, release func()) {
profile: pm.profile,
discoveryManager: pm.discoveryManager,
userAgent: pm.userAgent,
downloaderConfig: pm.downloaderConfig,
}, pm.packagesLock.RUnlock
}
......
......@@ -28,6 +28,7 @@ import (
"github.com/arduino/go-paths-helper"
"github.com/arduino/go-properties-orderedmap"
"github.com/stretchr/testify/require"
"go.bug.st/downloader/v2"
semver "go.bug.st/relaxed-semver"
)
......@@ -38,7 +39,7 @@ var dataDir1 = paths.New("testdata", "data_dir_1")
var extraHardware = paths.New("testdata", "extra_hardware")
func TestFindBoardWithFQBN(t *testing.T) {
pmb := NewBuilder(customHardware, customHardware, customHardware, customHardware, "test")
pmb := NewBuilder(customHardware, customHardware, customHardware, customHardware, "test", downloader.GetDefaultConfig())
pmb.LoadHardwareFromDirectory(customHardware)
pm := pmb.Build()
pme, release := pm.NewExplorer()
......@@ -56,7 +57,7 @@ func TestFindBoardWithFQBN(t *testing.T) {
func TestResolveFQBN(t *testing.T) {
// Pass nil, since these paths are only used for installing
pmb := NewBuilder(nil, nil, nil, nil, "test")
pmb := NewBuilder(nil, nil, nil, nil, "test", downloader.GetDefaultConfig())
// Hardware from main packages directory
pmb.LoadHardwareFromDirectory(dataDir1.Join("packages"))
// This contains the arduino:avr core
......@@ -341,7 +342,7 @@ func TestResolveFQBN(t *testing.T) {
}
func TestBoardOptionsFunctions(t *testing.T) {
pmb := NewBuilder(customHardware, customHardware, customHardware, customHardware, "test")
pmb := NewBuilder(customHardware, customHardware, customHardware, customHardware, "test", downloader.GetDefaultConfig())
pmb.LoadHardwareFromDirectory(customHardware)
pm := pmb.Build()
pme, release := pm.NewExplorer()
......@@ -381,7 +382,7 @@ func TestBoardOptionsFunctions(t *testing.T) {
}
func TestBoardOrdering(t *testing.T) {
pmb := NewBuilder(dataDir1, dataDir1.Join("packages"), nil, nil, "")
pmb := NewBuilder(dataDir1, dataDir1.Join("packages"), nil, nil, "", downloader.GetDefaultConfig())
_ = pmb.LoadHardwareFromDirectories(paths.NewPathList(dataDir1.Join("packages").String()))
pm := pmb.Build()
pme, release := pm.NewExplorer()
......@@ -432,13 +433,14 @@ func TestBoardOrdering(t *testing.T) {
func TestFindToolsRequiredForBoard(t *testing.T) {
t.Setenv("ARDUINO_DATA_DIR", dataDir1.String())
configuration.Settings = configuration.Init("")
settings := configuration.Init("")
pmb := NewBuilder(
dataDir1,
configuration.PackagesDir(configuration.Settings),
configuration.DownloadsDir(configuration.Settings),
configuration.PackagesDir(settings),
configuration.DownloadsDir(settings),
dataDir1,
"test",
downloader.GetDefaultConfig(),
)
loadIndex := func(addr string) {
......@@ -454,7 +456,7 @@ func TestFindToolsRequiredForBoard(t *testing.T) {
// We ignore the errors returned since they might not be necessarily blocking
// but just warnings for the user, like in the case a board is not loaded
// because of malformed menus
pmb.LoadHardware()
pmb.LoadHardware(settings)
pm := pmb.Build()
pme, release := pm.NewExplorer()
defer release()
......@@ -567,7 +569,7 @@ func TestFindToolsRequiredForBoard(t *testing.T) {
}
func TestIdentifyBoard(t *testing.T) {
pmb := NewBuilder(customHardware, customHardware, customHardware, customHardware, "test")
pmb := NewBuilder(customHardware, customHardware, customHardware, customHardware, "test", downloader.GetDefaultConfig())
pmb.LoadHardwareFromDirectory(customHardware)
pm := pmb.Build()
pme, release := pm.NewExplorer()
......@@ -594,12 +596,12 @@ func TestIdentifyBoard(t *testing.T) {
func TestPackageManagerClear(t *testing.T) {
// Create a PackageManager and load the harware
pmb := NewBuilder(customHardware, customHardware, customHardware, customHardware, "test")
pmb := NewBuilder(customHardware, customHardware, customHardware, customHardware, "test", downloader.GetDefaultConfig())
pmb.LoadHardwareFromDirectory(customHardware)
pm := pmb.Build()
// Creates another PackageManager but don't load the hardware
emptyPmb := NewBuilder(customHardware, customHardware, customHardware, customHardware, "test")
emptyPmb := NewBuilder(customHardware, customHardware, customHardware, customHardware, "test", downloader.GetDefaultConfig())
emptyPm := emptyPmb.Build()
// Verifies they're not equal
......@@ -621,7 +623,7 @@ func TestFindToolsRequiredFromPlatformRelease(t *testing.T) {
require.NoError(t, err)
defer fakePath.RemoveAll()
pmb := NewBuilder(fakePath, fakePath, fakePath, fakePath, "test")
pmb := NewBuilder(fakePath, fakePath, fakePath, fakePath, "test", downloader.GetDefaultConfig())
pack := pmb.GetOrCreatePackage("arduino")
{
......@@ -742,7 +744,7 @@ func TestFindToolsRequiredFromPlatformRelease(t *testing.T) {
}
func TestFindPlatformReleaseDependencies(t *testing.T) {
pmb := NewBuilder(nil, nil, nil, nil, "test")
pmb := NewBuilder(nil, nil, nil, nil, "test", downloader.GetDefaultConfig())
pmb.LoadPackageIndexFromFile(paths.New("testdata", "package_tooltest_index.json"))
pmb.calculateCompatibleReleases()
pm := pmb.Build()
......@@ -758,7 +760,7 @@ func TestFindPlatformReleaseDependencies(t *testing.T) {
func TestLegacyPackageConversionToPluggableDiscovery(t *testing.T) {
// Pass nil, since these paths are only used for installing
pmb := NewBuilder(nil, nil, nil, nil, "test")
pmb := NewBuilder(nil, nil, nil, nil, "test", downloader.GetDefaultConfig())
// Hardware from main packages directory
pmb.LoadHardwareFromDirectory(dataDir1.Join("packages"))
pm := pmb.Build()
......@@ -828,7 +830,7 @@ func TestLegacyPackageConversionToPluggableDiscovery(t *testing.T) {
func TestVariantAndCoreSelection(t *testing.T) {
// Pass nil, since these paths are only used for installing
pmb := NewBuilder(nil, nil, nil, nil, "test")
pmb := NewBuilder(nil, nil, nil, nil, "test", downloader.GetDefaultConfig())
// Hardware from main packages directory
pmb.LoadHardwareFromDirectory(dataDir1.Join("packages"))
pm := pmb.Build()
......@@ -923,7 +925,7 @@ func TestVariantAndCoreSelection(t *testing.T) {
}
func TestRunScript(t *testing.T) {
pmb := NewBuilder(nil, nil, nil, nil, "test")
pmb := NewBuilder(nil, nil, nil, nil, "test", downloader.GetDefaultConfig())
pm := pmb.Build()
pme, release := pm.NewExplorer()
defer release()
......
......@@ -32,7 +32,7 @@ import (
// LoadHardwareForProfile load the hardware platforms for the given profile.
// If installMissing is true then possibly missing tools and platforms will be downloaded and installed.
func (pmb *Builder) LoadHardwareForProfile(p *sketch.Profile, installMissing bool, downloadCB rpc.DownloadProgressCB, taskCB rpc.TaskProgressCB) []error {
func (pmb *Builder) LoadHardwareForProfile(p *sketch.Profile, installMissing bool, downloadCB rpc.DownloadProgressCB, taskCB rpc.TaskProgressCB, settings *configuration.Settings) []error {
pmb.profile = p
// Load required platforms
......@@ -40,7 +40,7 @@ func (pmb *Builder) LoadHardwareForProfile(p *sketch.Profile, installMissing boo
var platformReleases []*cores.PlatformRelease
indexURLs := map[string]*url.URL{}
for _, platformRef := range p.Platforms {
if platformRelease, err := pmb.loadProfilePlatform(platformRef, installMissing, downloadCB, taskCB); err != nil {
if platformRelease, err := pmb.loadProfilePlatform(platformRef, installMissing, downloadCB, taskCB, settings); err != nil {
merr = append(merr, fmt.Errorf("%s: %w", tr("loading required platform %s", platformRef), err))
logrus.WithField("platform", platformRef).WithError(err).Debugf("Error loading platform for profile")
} else {
......@@ -56,7 +56,7 @@ func (pmb *Builder) LoadHardwareForProfile(p *sketch.Profile, installMissing boo
for _, toolDep := range platformRelease.ToolDependencies {
indexURL := indexURLs[toolDep.ToolPackager]
if err := pmb.loadProfileTool(toolDep, indexURL, installMissing, downloadCB, taskCB); err != nil {
if err := pmb.loadProfileTool(toolDep, indexURL, installMissing, downloadCB, taskCB, settings); err != nil {
merr = append(merr, fmt.Errorf("%s: %w", tr("loading required tool %s", toolDep), err))
logrus.WithField("tool", toolDep).WithField("index_url", indexURL).WithError(err).Debugf("Error loading tool for profile")
} else {
......@@ -68,13 +68,13 @@ func (pmb *Builder) LoadHardwareForProfile(p *sketch.Profile, installMissing boo
return merr
}
func (pmb *Builder) loadProfilePlatform(platformRef *sketch.ProfilePlatformReference, installMissing bool, downloadCB rpc.DownloadProgressCB, taskCB rpc.TaskProgressCB) (*cores.PlatformRelease, error) {
func (pmb *Builder) loadProfilePlatform(platformRef *sketch.ProfilePlatformReference, installMissing bool, downloadCB rpc.DownloadProgressCB, taskCB rpc.TaskProgressCB, settings *configuration.Settings) (*cores.PlatformRelease, error) {
targetPackage := pmb.packages.GetOrCreatePackage(platformRef.Packager)
platform := targetPackage.GetOrCreatePlatform(platformRef.Architecture)
release := platform.GetOrCreateRelease(platformRef.Version)
uid := platformRef.InternalUniqueIdentifier()
destDir := configuration.ProfilesCacheDir(configuration.Settings).Join(uid)
destDir := configuration.ProfilesCacheDir(settings).Join(uid)
if !destDir.IsDir() && installMissing {
// Try installing the missing platform
if err := pmb.installMissingProfilePlatform(platformRef, destDir, downloadCB, taskCB); err != nil {
......@@ -91,7 +91,7 @@ func (pmb *Builder) installMissingProfilePlatform(platformRef *sketch.ProfilePla
if err != nil {
return fmt.Errorf("installing missing platform: could not create temp dir %s", err)
}
tmpPmb := NewBuilder(tmp, tmp, pmb.DownloadDir, tmp, pmb.userAgent)
tmpPmb := NewBuilder(tmp, tmp, pmb.DownloadDir, tmp, pmb.userAgent, pmb.downloaderConfig)
defer tmp.RemoveAll()
// Download the main index and parse it
......@@ -103,7 +103,7 @@ func (pmb *Builder) installMissingProfilePlatform(platformRef *sketch.ProfilePla
}
for _, indexURL := range indexesToDownload {
indexResource := resources.IndexResource{URL: indexURL}
if err := indexResource.Download(tmpPmb.IndexDir, downloadCB); err != nil {
if err := indexResource.Download(tmpPmb.IndexDir, downloadCB, pmb.downloaderConfig); err != nil {
taskCB(&rpc.TaskProgress{Name: tr("Error downloading %s", indexURL)})
return &cmderrors.FailedDownloadError{Message: tr("Error downloading %s", indexURL), Cause: err}
}
......@@ -121,7 +121,7 @@ func (pmb *Builder) installMissingProfilePlatform(platformRef *sketch.ProfilePla
tmpPme, tmpRelease := tmpPm.NewExplorer()
defer tmpRelease()
if err := tmpPme.DownloadPlatformRelease(tmpPlatformRelease, nil, downloadCB); err != nil {
if err := tmpPme.DownloadPlatformRelease(tmpPlatformRelease, downloadCB); err != nil {
taskCB(&rpc.TaskProgress{Name: tr("Error downloading platform %s", tmpPlatformRelease)})
return &cmderrors.FailedInstallError{Message: tr("Error downloading platform %s", tmpPlatformRelease), Cause: err}
}
......@@ -137,12 +137,12 @@ func (pmb *Builder) installMissingProfilePlatform(platformRef *sketch.ProfilePla
return nil
}
func (pmb *Builder) loadProfileTool(toolRef *cores.ToolDependency, indexURL *url.URL, installMissing bool, downloadCB rpc.DownloadProgressCB, taskCB rpc.TaskProgressCB) error {
func (pmb *Builder) loadProfileTool(toolRef *cores.ToolDependency, indexURL *url.URL, installMissing bool, downloadCB rpc.DownloadProgressCB, taskCB rpc.TaskProgressCB, settings *configuration.Settings) error {
targetPackage := pmb.packages.GetOrCreatePackage(toolRef.ToolPackager)
tool := targetPackage.GetOrCreateTool(toolRef.ToolName)
uid := toolRef.InternalUniqueIdentifier(indexURL)
destDir := configuration.ProfilesCacheDir(configuration.Settings).Join(uid)
destDir := configuration.ProfilesCacheDir(settings).Join(uid)
if !destDir.IsDir() && installMissing {
// Try installing the missing tool
......@@ -172,7 +172,7 @@ func (pmb *Builder) installMissingProfileTool(toolRelease *cores.ToolRelease, de
return &cmderrors.InvalidVersionError{Cause: fmt.Errorf(tr("version %s not available for this operating system", toolRelease))}
}
taskCB(&rpc.TaskProgress{Name: tr("Downloading tool %s", toolRelease)})
if err := toolResource.Download(pmb.DownloadDir, nil, toolRelease.String(), downloadCB, ""); err != nil {
if err := toolResource.Download(pmb.DownloadDir, pmb.downloaderConfig, toolRelease.String(), downloadCB, ""); err != nil {
taskCB(&rpc.TaskProgress{Name: tr("Error downloading tool %s", toolRelease)})
return &cmderrors.FailedInstallError{Message: tr("Error installing tool %s", toolRelease), Cause: err}
}
......
......@@ -16,12 +16,9 @@
package httpclient
import (
"net/http"
"net/url"
"time"
"github.com/arduino/arduino-cli/commands/cmderrors"
"github.com/arduino/arduino-cli/internal/cli/configuration"
"github.com/arduino/arduino-cli/internal/i18n"
rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
"github.com/arduino/go-paths-helper"
......@@ -34,7 +31,7 @@ var tr = i18n.Tr
// DownloadFile downloads a file from a URL into the specified path. An optional config and options may be passed (or nil to use the defaults).
// A DownloadProgressCB callback function must be passed to monitor download progress.
// If a not empty queryParameter is passed, it is appended to the URL for analysis purposes.
func DownloadFile(path *paths.Path, URL string, queryParameter string, label string, downloadCB rpc.DownloadProgressCB, config *downloader.Config, options ...downloader.DownloadOptions) (returnedError error) {
func DownloadFile(path *paths.Path, URL string, queryParameter string, label string, downloadCB rpc.DownloadProgressCB, config downloader.Config, options ...downloader.DownloadOptions) (returnedError error) {
if queryParameter != "" {
URL = URL + "?query=" + queryParameter
}
......@@ -48,15 +45,7 @@ func DownloadFile(path *paths.Path, URL string, queryParameter string, label str
}
}()
if config == nil {
c, err := GetDownloaderConfig()
if err != nil {
return err
}
config = c
}
d, err := downloader.DownloadWithConfig(path.String(), URL, *config, options...)
d, err := downloader.DownloadWithConfig(path.String(), URL, config, options...)
if err != nil {
return err
}
......@@ -76,52 +65,3 @@ func DownloadFile(path *paths.Path, URL string, queryParameter string, label str
return nil
}
// Config is the configuration of the http client
type Config struct {
UserAgent string
Proxy *url.URL
}
// New returns a default http client for use in the arduino-cli
func New() (*http.Client, error) {
userAgent := configuration.UserAgent(configuration.Settings)
proxy, err := configuration.NetworkProxy(configuration.Settings)
if err != nil {
return nil, err
}
return NewWithConfig(&Config{UserAgent: userAgent, Proxy: proxy}), nil
}
// NewWithConfig creates a http client for use in the arduino-cli, with a given configuration
func NewWithConfig(config *Config) *http.Client {
return &http.Client{
Transport: &httpClientRoundTripper{
transport: &http.Transport{
Proxy: http.ProxyURL(config.Proxy),
},
userAgent: config.UserAgent,
},
}
}
// GetDownloaderConfig returns the downloader configuration based on current settings.
func GetDownloaderConfig() (*downloader.Config, error) {
httpClient, err := New()
if err != nil {
return nil, &cmderrors.InvalidArgumentError{Message: tr("Could not connect via HTTP"), Cause: err}
}
return &downloader.Config{
HttpClient: *httpClient,
}, nil
}
type httpClientRoundTripper struct {
transport http.RoundTripper
userAgent string
}
func (h *httpClientRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
req.Header.Add("User-Agent", h.userAgent)
return h.transport.RoundTrip(req)
}
......@@ -28,7 +28,7 @@ import (
// Download performs a download loop using the provided downloader.Config.
// Messages are passed back to the DownloadProgressCB using label as text for the File field.
// queryParameter is passed for analysis purposes.
func (r *DownloadResource) Download(downloadDir *paths.Path, config *downloader.Config, label string, downloadCB rpc.DownloadProgressCB, queryParameter string) error {
func (r *DownloadResource) Download(downloadDir *paths.Path, config downloader.Config, label string, downloadCB rpc.DownloadProgressCB, queryParameter string) error {
path, err := r.ArchivePath(downloadDir)
if err != nil {
return fmt.Errorf(tr("getting archive path: %s"), err)
......
......@@ -22,11 +22,10 @@ import (
"strings"
"testing"
"github.com/arduino/arduino-cli/internal/arduino/httpclient"
"github.com/arduino/arduino-cli/internal/cli/configuration"
rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
"github.com/arduino/go-paths-helper"
"github.com/stretchr/testify/require"
"go.bug.st/downloader/v2"
)
type EchoHandler struct{}
......@@ -37,8 +36,7 @@ func (h *EchoHandler) ServeHTTP(writer http.ResponseWriter, request *http.Reques
}
func TestDownloadApplyUserAgentHeaderUsingConfig(t *testing.T) {
goldUserAgentValue := "arduino-cli/0.0.0-test.preview (amd64; linux; go1.12.4) Commit:deadbeef/Build:2019-06-12 11:11:11.111"
goldUserAgentString := "User-Agent: " + goldUserAgentValue
goldUserAgentValue := "arduino-cli/0.0.0-test.preview"
tmp, err := paths.MkTempDir("", "")
require.NoError(t, err)
......@@ -54,9 +52,11 @@ func TestDownloadApplyUserAgentHeaderUsingConfig(t *testing.T) {
URL: srv.URL,
}
httpClient := httpclient.NewWithConfig(&httpclient.Config{UserAgent: goldUserAgentValue})
err = r.Download(tmp, &downloader.Config{HttpClient: *httpClient}, "", func(progress *rpc.DownloadProgress) {}, "")
settings := configuration.Init("")
settings.Set("network.user_agent_ext", goldUserAgentValue)
config, err := settings.DownloaderConfig()
require.NoError(t, err)
err = r.Download(tmp, config, "", func(progress *rpc.DownloadProgress) {}, "")
require.NoError(t, err)
// leverage the download helper to download the echo for the request made by the downloader itself
......@@ -71,12 +71,11 @@ func TestDownloadApplyUserAgentHeaderUsingConfig(t *testing.T) {
require.NoError(t, err)
requestLines := strings.Split(string(b), "\r\n")
userAgentHeaderString := ""
userAgentHeader := ""
for _, line := range requestLines {
if strings.Contains(line, "User-Agent: ") {
userAgentHeaderString = line
userAgentHeader = line
}
}
require.Equal(t, goldUserAgentString, userAgentHeaderString)
require.Contains(t, userAgentHeader, goldUserAgentValue)
}
......@@ -58,7 +58,7 @@ func (res *IndexResource) IndexFileName() (string, error) {
// Download will download the index and possibly check the signature using the Arduino's public key.
// If the file is in .gz format it will be unpacked first.
func (res *IndexResource) Download(destDir *paths.Path, downloadCB rpc.DownloadProgressCB) error {
func (res *IndexResource) Download(destDir *paths.Path, downloadCB rpc.DownloadProgressCB, config downloader.Config) error {
// Create destination directory
if err := destDir.MkdirAll(); err != nil {
return &cmderrors.PermissionDeniedError{Message: tr("Can't create data directory %s", destDir), Cause: err}
......@@ -78,7 +78,7 @@ func (res *IndexResource) Download(destDir *paths.Path, downloadCB rpc.DownloadP
return err
}
tmpIndexPath := tmp.Join(downloadFileName)
if err := httpclient.DownloadFile(tmpIndexPath, res.URL.String(), "", tr("Downloading index: %s", downloadFileName), downloadCB, nil, downloader.NoResume); err != nil {
if err := httpclient.DownloadFile(tmpIndexPath, res.URL.String(), "", tr("Downloading index: %s", downloadFileName), downloadCB, config, downloader.NoResume); err != nil {
return &cmderrors.FailedDownloadError{Message: tr("Error downloading index '%s'", res.URL), Cause: err}
}
......@@ -133,7 +133,7 @@ func (res *IndexResource) Download(destDir *paths.Path, downloadCB rpc.DownloadP
// Download signature
signaturePath = destDir.Join(signatureFileName)
tmpSignaturePath = tmp.Join(signatureFileName)
if err := httpclient.DownloadFile(tmpSignaturePath, res.SignatureURL.String(), "", tr("Downloading index signature: %s", signatureFileName), downloadCB, nil, downloader.NoResume); err != nil {
if err := httpclient.DownloadFile(tmpSignaturePath, res.SignatureURL.String(), "", tr("Downloading index signature: %s", signatureFileName), downloadCB, config, downloader.NoResume); err != nil {
return &cmderrors.FailedDownloadError{Message: tr("Error downloading index signature '%s'", res.SignatureURL), Cause: err}
}
......
......@@ -49,7 +49,7 @@ func TestDownloadAndChecksums(t *testing.T) {
require.NoError(t, err)
downloadAndTestChecksum := func() {
err := r.Download(tmp, &downloader.Config{}, "", func(*rpc.DownloadProgress) {}, "")
err := r.Download(tmp, downloader.Config{}, "", func(*rpc.DownloadProgress) {}, "")
require.NoError(t, err)
data, err := testFile.ReadFile()
......@@ -63,7 +63,7 @@ func TestDownloadAndChecksums(t *testing.T) {
downloadAndTestChecksum()
// Download with cached file
err = r.Download(tmp, &downloader.Config{}, "", func(*rpc.DownloadProgress) {}, "")
err = r.Download(tmp, downloader.Config{}, "", func(*rpc.DownloadProgress) {}, "")
require.NoError(t, err)
// Download if cached file has data in excess (redownload)
......@@ -132,7 +132,7 @@ func TestIndexDownloadAndSignatureWithinArchive(t *testing.T) {
destDir, err := paths.MkTempDir("", "")
require.NoError(t, err)
defer destDir.RemoveAll()
err = idxResource.Download(destDir, func(curr *rpc.DownloadProgress) {})
err = idxResource.Download(destDir, func(curr *rpc.DownloadProgress) {}, downloader.GetDefaultConfig())
require.NoError(t, err)
require.True(t, destDir.Join("package_index.json").Exist())
require.True(t, destDir.Join("package_index.json.sig").Exist())
......@@ -143,7 +143,7 @@ func TestIndexDownloadAndSignatureWithinArchive(t *testing.T) {
invDestDir, err := paths.MkTempDir("", "")
require.NoError(t, err)
defer invDestDir.RemoveAll()
err = invIdxResource.Download(invDestDir, func(curr *rpc.DownloadProgress) {})
err = invIdxResource.Download(invDestDir, func(curr *rpc.DownloadProgress) {}, downloader.GetDefaultConfig())
require.Error(t, err)
require.Contains(t, err.Error(), "invalid signature")
require.False(t, invDestDir.Join("package_index.json").Exist())
......
......@@ -48,10 +48,6 @@ var badCores = []struct {
{"", nil},
}
func init() {
configuration.Settings = configuration.Init("")
}
func TestArgsStringify(t *testing.T) {
for _, core := range goodCores {
require.Equal(t, core.in, core.expected.String())
......@@ -59,7 +55,7 @@ func TestArgsStringify(t *testing.T) {
}
func TestParseReferenceCores(t *testing.T) {
srv := commands.NewArduinoCoreServer("")
srv := commands.NewArduinoCoreServer("", configuration.Init(""))
ctx := context.Background()
for _, tt := range goodCores {
actual, err := arguments.ParseReference(ctx, srv, tt.in)
......@@ -80,7 +76,7 @@ func TestParseArgs(t *testing.T) {
input = append(input, tt.in)
}
srv := commands.NewArduinoCoreServer("")
srv := commands.NewArduinoCoreServer("", configuration.Init(""))
refs, err := arguments.ParseReferences(context.Background(), srv, input)
assert.Nil(t, err)
assert.Equal(t, len(goodCores), len(refs))
......
......@@ -63,7 +63,7 @@ var (
)
// NewCommand creates a new ArduinoCli command root
func NewCommand(srv rpc.ArduinoCoreServiceServer) *cobra.Command {
func NewCommand(srv rpc.ArduinoCoreServiceServer, defaultSettings *configuration.Settings) *cobra.Command {
cobra.AddTemplateFunc("tr", i18n.Tr)
var updaterMessageChan chan *semver.Version
......@@ -79,7 +79,7 @@ func NewCommand(srv rpc.ArduinoCoreServiceServer) *cobra.Command {
outputFormat = "json"
}
preRun(cmd, args)
preRun(cmd, defaultSettings)
if cmd.Name() != "version" {
updaterMessageChan = make(chan *semver.Version)
......@@ -115,13 +115,13 @@ func NewCommand(srv rpc.ArduinoCoreServiceServer) *cobra.Command {
cmd.AddCommand(board.NewCommand(srv))
cmd.AddCommand(cache.NewCommand(srv))
cmd.AddCommand(compile.NewCommand(srv))
cmd.AddCommand(compile.NewCommand(srv, defaultSettings))
cmd.AddCommand(completion.NewCommand())
cmd.AddCommand(config.NewCommand())
cmd.AddCommand(config.NewCommand(srv, defaultSettings))
cmd.AddCommand(core.NewCommand(srv))
cmd.AddCommand(daemon.NewCommand())
cmd.AddCommand(daemon.NewCommand(srv, defaultSettings))
cmd.AddCommand(generatedocs.NewCommand())
cmd.AddCommand(lib.NewCommand(srv))
cmd.AddCommand(lib.NewCommand(srv, defaultSettings))
cmd.AddCommand(monitor.NewCommand(srv))
cmd.AddCommand(outdated.NewCommand(srv))
cmd.AddCommand(sketch.NewCommand(srv))
......@@ -156,7 +156,7 @@ func NewCommand(srv rpc.ArduinoCoreServiceServer) *cobra.Command {
cmd.PersistentFlags().StringVar(&configFile, "config-file", "", tr("The custom config file (if not specified the default will be used)."))
cmd.PersistentFlags().StringSlice("additional-urls", []string{}, tr("Comma-separated list of additional URLs for the Boards Manager."))
cmd.PersistentFlags().Bool("no-color", false, "Disable colored output.")
configuration.BindFlags(cmd, configuration.Settings)
configuration.BindFlags(cmd, defaultSettings)
return cmd
}
......@@ -177,17 +177,17 @@ func toLogLevel(s string) (t logrus.Level, found bool) {
return
}
func preRun(cmd *cobra.Command, args []string) {
configFile := configuration.Settings.ConfigFileUsed()
func preRun(cmd *cobra.Command, defaultSettings *configuration.Settings) {
configFile := defaultSettings.ConfigFileUsed()
// initialize inventory
err := inventory.Init(configuration.DataDir(configuration.Settings).String())
err := inventory.Init(configuration.DataDir(defaultSettings).String())
if err != nil {
feedback.Fatal(fmt.Sprintf("Error: %v", err), feedback.ErrInitializingInventory)
}
// https://no-color.org/
color.NoColor = configuration.Settings.GetBool("output.no_color") || os.Getenv("NO_COLOR") != ""
color.NoColor = defaultSettings.GetBool("output.no_color") || os.Getenv("NO_COLOR") != ""
// Set default feedback output to colorable
feedback.SetOut(colorable.NewColorableStdout())
......@@ -210,13 +210,13 @@ func preRun(cmd *cobra.Command, args []string) {
}
// set the Logger format
logFormat := strings.ToLower(configuration.Settings.GetString("logging.format"))
logFormat := strings.ToLower(defaultSettings.GetString("logging.format"))
if logFormat == "json" {
logrus.SetFormatter(&logrus.JSONFormatter{})
}
// should we log to file?
logFile := configuration.Settings.GetString("logging.file")
logFile := defaultSettings.GetString("logging.file")
if logFile != "" {
file, err := os.OpenFile(logFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
if err != nil {
......@@ -232,8 +232,8 @@ func preRun(cmd *cobra.Command, args []string) {
}
// configure logging filter
if lvl, found := toLogLevel(configuration.Settings.GetString("logging.level")); !found {
feedback.Fatal(tr("Invalid option for --log-level: %s", configuration.Settings.GetString("logging.level")), feedback.ErrBadArgument)
if lvl, found := toLogLevel(defaultSettings.GetString("logging.level")); !found {
feedback.Fatal(tr("Invalid option for --log-level: %s", defaultSettings.GetString("logging.level")), feedback.ErrBadArgument)
} else {
logrus.SetLevel(lvl)
}
......
......@@ -77,7 +77,7 @@ var (
)
// NewCommand created a new `compile` command
func NewCommand(srv rpc.ArduinoCoreServiceServer) *cobra.Command {
func NewCommand(srv rpc.ArduinoCoreServiceServer, defaultSettings *configuration.Settings) *cobra.Command {
compileCommand := &cobra.Command{
Use: "compile",
Short: tr("Compiles Arduino sketches."),
......@@ -133,7 +133,7 @@ func NewCommand(srv rpc.ArduinoCoreServiceServer) *cobra.Command {
compileCommand.Flags().BoolVar(&skipLibrariesDiscovery, "skip-libraries-discovery", false, "Skip libraries discovery. This flag is provided only for use in language server and other, very specific, use cases. Do not use for normal compiles")
compileCommand.Flag("skip-libraries-discovery").Hidden = true
compileCommand.Flags().Int32VarP(&jobs, "jobs", "j", 0, tr("Max number of parallel compiles. If set to 0 the number of available CPUs cores will be used."))
configuration.Settings.BindPFlag("sketch.always_export_binaries", compileCommand.Flags().Lookup("export-binaries"))
defaultSettings.BindPFlag("sketch.always_export_binaries", compileCommand.Flags().Lookup("export-binaries"))
compileCommand.Flags().MarkDeprecated("build-properties", tr("please use --build-property instead."))
......
......@@ -44,7 +44,7 @@ func uniquify[T comparable](s []T) []T {
return result
}
func initAddCommand() *cobra.Command {
func initAddCommand(defaultSettings *configuration.Settings) *cobra.Command {
addCommand := &cobra.Command{
Use: "add",
Short: tr("Adds one or more values to a setting."),
......@@ -53,15 +53,17 @@ func initAddCommand() *cobra.Command {
" " + os.Args[0] + " config add board_manager.additional_urls https://example.com/package_example_index.json\n" +
" " + os.Args[0] + " config add board_manager.additional_urls https://example.com/package_example_index.json https://another-url.com/package_another_index.json\n",
Args: cobra.MinimumNArgs(2),
Run: runAddCommand,
Run: func(cmd *cobra.Command, args []string) {
runAddCommand(args, defaultSettings)
},
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return GetConfigurationKeys(), cobra.ShellCompDirectiveDefault
return GetSlicesConfigurationKeys(defaultSettings), cobra.ShellCompDirectiveDefault
},
}
return addCommand
}
func runAddCommand(cmd *cobra.Command, args []string) {
func runAddCommand(args []string, defaultSettings *configuration.Settings) {
logrus.Info("Executing `arduino-cli config add`")
key := args[0]
kind := validateKey(key)
......@@ -71,12 +73,12 @@ func runAddCommand(cmd *cobra.Command, args []string) {
feedback.Fatal(msg, feedback.ErrGeneric)
}
v := configuration.Settings.GetStringSlice(key)
v := defaultSettings.GetStringSlice(key)
v = append(v, args[1:]...)
v = uniquify(v)
configuration.Settings.Set(key, v)
defaultSettings.Set(key, v)
if err := configuration.Settings.WriteConfig(); err != nil {
if err := defaultSettings.WriteConfig(); err != nil {
feedback.Fatal(tr("Can't write config file: %v", err), feedback.ErrGeneric)
}
}
......@@ -21,35 +21,36 @@ import (
"github.com/arduino/arduino-cli/internal/cli/configuration"
"github.com/arduino/arduino-cli/internal/i18n"
rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
"github.com/spf13/cobra"
)
var tr = i18n.Tr
// NewCommand created a new `config` command
func NewCommand() *cobra.Command {
func NewCommand(srv rpc.ArduinoCoreServiceServer, defaultSettings *configuration.Settings) *cobra.Command {
configCommand := &cobra.Command{
Use: "config",
Short: tr("Arduino configuration commands."),
Example: " " + os.Args[0] + " config init",
}
configCommand.AddCommand(initAddCommand())
configCommand.AddCommand(initDeleteCommand())
configCommand.AddCommand(initDumpCommand())
configCommand.AddCommand(initGetCommand())
configCommand.AddCommand(initInitCommand())
configCommand.AddCommand(initRemoveCommand())
configCommand.AddCommand(initSetCommand())
configCommand.AddCommand(initAddCommand(defaultSettings))
configCommand.AddCommand(initDeleteCommand(srv, defaultSettings))
configCommand.AddCommand(initDumpCommand(defaultSettings))
configCommand.AddCommand(initGetCommand(srv, defaultSettings))
configCommand.AddCommand(initInitCommand(defaultSettings))
configCommand.AddCommand(initRemoveCommand(defaultSettings))
configCommand.AddCommand(initSetCommand(defaultSettings))
return configCommand
}
// GetConfigurationKeys is an helper function useful to autocomplete.
// GetSlicesConfigurationKeys is an helper function useful to autocomplete.
// It returns a list of configuration keys which can be changed
func GetConfigurationKeys() []string {
func GetSlicesConfigurationKeys(settings *configuration.Settings) []string {
var res []string
keys := configuration.Settings.AllKeys()
keys := settings.AllKeys()
for _, key := range keys {
kind, _ := typeOf(key)
if kind == reflect.Slice {
......
......@@ -16,9 +16,9 @@
package config
import (
"context"
"os"
"github.com/arduino/arduino-cli/commands"
"github.com/arduino/arduino-cli/internal/cli/configuration"
"github.com/arduino/arduino-cli/internal/cli/feedback"
rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
......@@ -26,7 +26,8 @@ import (
"github.com/spf13/cobra"
)
func initDeleteCommand() *cobra.Command {
func initDeleteCommand(srv rpc.ArduinoCoreServiceServer, defaultSettings *configuration.Settings) *cobra.Command {
configFile := defaultSettings.ConfigFileUsed()
deleteCommand := &cobra.Command{
Use: "delete",
Short: tr("Deletes a settings key and all its sub keys."),
......@@ -35,25 +36,27 @@ func initDeleteCommand() *cobra.Command {
" " + os.Args[0] + " config delete board_manager\n" +
" " + os.Args[0] + " config delete board_manager.additional_urls",
Args: cobra.ExactArgs(1),
Run: runDeleteCommand,
Run: func(cmd *cobra.Command, args []string) {
runDeleteCommand(srv, args, configFile)
},
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return configuration.Settings.AllKeys(), cobra.ShellCompDirectiveDefault
return defaultSettings.AllKeys(), cobra.ShellCompDirectiveDefault
},
}
return deleteCommand
}
func runDeleteCommand(cmd *cobra.Command, args []string) {
func runDeleteCommand(srv rpc.ArduinoCoreServiceServer, args []string, configFile string) {
logrus.Info("Executing `arduino-cli config delete`")
toDelete := args[0]
ctx := context.Background()
svc := commands.NewArduinoCoreServer("")
_, err := svc.SettingsDelete(cmd.Context(), &rpc.SettingsDeleteRequest{Key: toDelete})
toDelete := args[0]
_, err := srv.SettingsDelete(ctx, &rpc.SettingsDeleteRequest{Key: toDelete})
if err != nil {
feedback.Fatal(tr("Cannot delete the key %[1]s: %[2]v", toDelete, err), feedback.ErrGeneric)
}
_, err = svc.SettingsWrite(cmd.Context(), &rpc.SettingsWriteRequest{FilePath: configuration.Settings.ConfigFileUsed()})
_, err = srv.SettingsWrite(ctx, &rpc.SettingsWriteRequest{FilePath: configFile})
if err != nil {
feedback.Fatal(tr("Cannot write the file %[1]s: %[2]v", configuration.Settings.ConfigFileUsed(), err), feedback.ErrGeneric)
feedback.Fatal(tr("Cannot write the file %[1]s: %[2]v", configFile, err), feedback.ErrGeneric)
}
}
......@@ -25,23 +25,21 @@ import (
"gopkg.in/yaml.v3"
)
func initDumpCommand() *cobra.Command {
func initDumpCommand(defaultSettings *configuration.Settings) *cobra.Command {
var dumpCommand = &cobra.Command{
Use: "dump",
Short: tr("Prints the current configuration"),
Long: tr("Prints the current configuration."),
Example: " " + os.Args[0] + " config dump",
Args: cobra.NoArgs,
Run: runDumpCommand,
Run: func(cmd *cobra.Command, args []string) {
logrus.Info("Executing `arduino-cli config dump`")
feedback.PrintResult(dumpResult{defaultSettings.AllSettings()})
},
}
return dumpCommand
}
func runDumpCommand(cmd *cobra.Command, args []string) {
logrus.Info("Executing `arduino-cli config dump`")
feedback.PrintResult(dumpResult{configuration.Settings.AllSettings()})
}
// output from this command requires special formatting, let's create a dedicated
// feedback.Result implementation
type dumpResult struct {
......
......@@ -16,11 +16,11 @@
package config
import (
"context"
"encoding/json"
"fmt"
"os"
"github.com/arduino/arduino-cli/commands"
"github.com/arduino/arduino-cli/internal/cli/configuration"
"github.com/arduino/arduino-cli/internal/cli/feedback"
rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
......@@ -29,7 +29,7 @@ import (
"gopkg.in/yaml.v3"
)
func initGetCommand() *cobra.Command {
func initGetCommand(srv rpc.ArduinoCoreServiceServer, defaultSettings *configuration.Settings) *cobra.Command {
getCommand := &cobra.Command{
Use: "get",
Short: tr("Gets a settings key value."),
......@@ -39,20 +39,22 @@ func initGetCommand() *cobra.Command {
" " + os.Args[0] + " config get daemon.port\n" +
" " + os.Args[0] + " config get board_manager.additional_urls",
Args: cobra.MinimumNArgs(1),
Run: runGetCommand,
Run: func(cmd *cobra.Command, args []string) {
runGetCommand(srv, args)
},
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return configuration.Settings.AllKeys(), cobra.ShellCompDirectiveDefault
return defaultSettings.AllKeys(), cobra.ShellCompDirectiveDefault
},
}
return getCommand
}
func runGetCommand(cmd *cobra.Command, args []string) {
func runGetCommand(srv rpc.ArduinoCoreServiceServer, args []string) {
logrus.Info("Executing `arduino-cli config get`")
ctx := context.Background()
svc := commands.NewArduinoCoreServer("")
for _, toGet := range args {
resp, err := svc.SettingsGetValue(cmd.Context(), &rpc.SettingsGetValueRequest{Key: toGet})
resp, err := srv.SettingsGetValue(ctx, &rpc.SettingsGetValueRequest{Key: toGet})
if err != nil {
feedback.Fatal(tr("Cannot get the configuration key %[1]s: %[2]v", toGet, err), feedback.ErrGeneric)
}
......
......@@ -25,7 +25,6 @@ import (
"github.com/arduino/go-paths-helper"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
var (
......@@ -36,7 +35,7 @@ var (
const defaultFileName = "arduino-cli.yaml"
func initInitCommand() *cobra.Command {
func initInitCommand(defaultSettings *configuration.Settings) *cobra.Command {
initCommand := &cobra.Command{
Use: "init",
Short: tr("Writes current configuration to a configuration file."),
......@@ -50,7 +49,9 @@ func initInitCommand() *cobra.Command {
PreRun: func(cmd *cobra.Command, args []string) {
arguments.CheckFlagsConflicts(cmd, "dest-file", "dest-dir")
},
Run: runInitCommand,
Run: func(cmd *cobra.Command, args []string) {
runInitCommand(cmd, defaultSettings)
},
}
initCommand.Flags().StringVar(&destDir, "dest-dir", "", tr("Sets where to save the configuration file."))
initCommand.Flags().StringVar(&destFile, "dest-file", "", tr("Sets where to save the configuration file."))
......@@ -58,11 +59,11 @@ func initInitCommand() *cobra.Command {
return initCommand
}
func runInitCommand(cmd *cobra.Command, args []string) {
func runInitCommand(cmd *cobra.Command, defaultSettings *configuration.Settings) {
logrus.Info("Executing `arduino-cli config init`")
var configFileAbsPath *paths.Path
var absPath *paths.Path
var configFileDir *paths.Path
var err error
switch {
......@@ -72,30 +73,29 @@ func runInitCommand(cmd *cobra.Command, args []string) {
feedback.Fatal(tr("Cannot find absolute path: %v", err), feedback.ErrGeneric)
}
absPath = configFileAbsPath.Parent()
configFileDir = configFileAbsPath.Parent()
case destDir == "":
destDir = configuration.Settings.GetString("directories.Data")
destDir = defaultSettings.GetString("directories.Data")
fallthrough
default:
absPath, err = paths.New(destDir).Abs()
configFileDir, err = paths.New(destDir).Abs()
if err != nil {
feedback.Fatal(tr("Cannot find absolute path: %v", err), feedback.ErrGeneric)
}
configFileAbsPath = absPath.Join(defaultFileName)
configFileAbsPath = configFileDir.Join(defaultFileName)
}
if !overwrite && configFileAbsPath.Exist() {
feedback.Fatal(tr("Config file already exists, use --overwrite to discard the existing one."), feedback.ErrGeneric)
}
logrus.Infof("Writing config file to: %s", absPath)
logrus.Infof("Writing config file to: %s", configFileDir)
if err := absPath.MkdirAll(); err != nil {
if err := configFileDir.MkdirAll(); err != nil {
feedback.Fatal(tr("Cannot create config file directory: %v", err), feedback.ErrGeneric)
}
newSettings := viper.New()
configuration.SetDefaults(newSettings)
newSettings := configuration.NewSettings()
configuration.BindFlags(cmd, newSettings)
for _, url := range newSettings.GetStringSlice("board_manager.additional_urls") {
......
......@@ -25,7 +25,7 @@ import (
"github.com/spf13/cobra"
)
func initRemoveCommand() *cobra.Command {
func initRemoveCommand(defaultSettings *configuration.Settings) *cobra.Command {
removeCommand := &cobra.Command{
Use: "remove",
Short: tr("Removes one or more values from a setting."),
......@@ -34,15 +34,17 @@ func initRemoveCommand() *cobra.Command {
" " + os.Args[0] + " config remove board_manager.additional_urls https://example.com/package_example_index.json\n" +
" " + os.Args[0] + " config remove board_manager.additional_urls https://example.com/package_example_index.json https://another-url.com/package_another_index.json\n",
Args: cobra.MinimumNArgs(2),
Run: runRemoveCommand,
Run: func(cmd *cobra.Command, args []string) {
runRemoveCommand(args, defaultSettings)
},
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return GetConfigurationKeys(), cobra.ShellCompDirectiveDefault
return GetSlicesConfigurationKeys(defaultSettings), cobra.ShellCompDirectiveDefault
},
}
return removeCommand
}
func runRemoveCommand(cmd *cobra.Command, args []string) {
func runRemoveCommand(args []string, defaultSettings *configuration.Settings) {
logrus.Info("Executing `arduino-cli config remove`")
key := args[0]
kind := validateKey(key)
......@@ -53,7 +55,7 @@ func runRemoveCommand(cmd *cobra.Command, args []string) {
}
mappedValues := map[string]bool{}
for _, v := range configuration.Settings.GetStringSlice(key) {
for _, v := range defaultSettings.GetStringSlice(key) {
mappedValues[v] = true
}
for _, arg := range args[1:] {
......@@ -63,9 +65,9 @@ func runRemoveCommand(cmd *cobra.Command, args []string) {
for k := range mappedValues {
values = append(values, k)
}
configuration.Settings.Set(key, values)
defaultSettings.Set(key, values)
if err := configuration.Settings.WriteConfig(); err != nil {
if err := defaultSettings.WriteConfig(); err != nil {
feedback.Fatal(tr("Can't write config file: %v", err), feedback.ErrGeneric)
}
}
......@@ -26,7 +26,7 @@ import (
"github.com/spf13/cobra"
)
func initSetCommand() *cobra.Command {
func initSetCommand(defaultSettings *configuration.Settings) *cobra.Command {
setCommand := &cobra.Command{
Use: "set",
Short: tr("Sets a setting value."),
......@@ -37,15 +37,17 @@ func initSetCommand() *cobra.Command {
" " + os.Args[0] + " config set sketch.always_export_binaries true\n" +
" " + os.Args[0] + " config set board_manager.additional_urls https://example.com/package_example_index.json https://another-url.com/package_another_index.json",
Args: cobra.MinimumNArgs(2),
Run: runSetCommand,
Run: func(cmd *cobra.Command, args []string) {
runSetCommand(defaultSettings, args)
},
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return configuration.Settings.AllKeys(), cobra.ShellCompDirectiveDefault
return defaultSettings.AllKeys(), cobra.ShellCompDirectiveDefault
},
}
return setCommand
}
func runSetCommand(cmd *cobra.Command, args []string) {
func runSetCommand(defaultSettings *configuration.Settings, args []string) {
logrus.Info("Executing `arduino-cli config set`")
key := args[0]
kind := validateKey(key)
......@@ -68,9 +70,9 @@ func runSetCommand(cmd *cobra.Command, args []string) {
}
}
configuration.Settings.Set(key, value)
defaultSettings.Set(key, value)
if err := configuration.Settings.WriteConfig(); err != nil {
if err := defaultSettings.WriteConfig(); err != nil {
feedback.Fatal(tr("Writing config file: %v", err), feedback.ErrGeneric)
}
}
......@@ -29,18 +29,26 @@ import (
"github.com/spf13/viper"
)
// Settings is a global instance of viper holding configurations for the CLI and the gRPC consumers
var Settings *viper.Viper
var tr = i18n.Tr
// Settings contains the configuration of the Arduino CLI core service
type Settings struct {
*viper.Viper
}
// NewSettings creates a new instance of Settings with the default values set
func NewSettings() *Settings {
res := &Settings{viper.New()}
SetDefaults(res)
return res
}
// Init initialize defaults and read the configuration file.
// Please note the logging system hasn't been configured yet,
// so logging shouldn't be used here.
func Init(configFile string) *viper.Viper {
func Init(configFile string) *Settings {
// Create a new viper instance with default values for all the settings
settings := viper.New()
SetDefaults(settings)
settings := NewSettings()
// Set config name and config path
if configFilePath := paths.New(configFile); configFilePath != nil {
......@@ -70,7 +78,7 @@ func Init(configFile string) *viper.Viper {
}
// BindFlags creates all the flags binding between the cobra Command and the instance of viper
func BindFlags(cmd *cobra.Command, settings *viper.Viper) {
func BindFlags(cmd *cobra.Command, settings *Settings) {
settings.BindPFlag("logging.level", cmd.Flag("log-level"))
settings.BindPFlag("logging.file", cmd.Flag("log-file"))
settings.BindPFlag("logging.format", cmd.Flag("log-format"))
......
......@@ -19,12 +19,10 @@ import (
"path/filepath"
"strings"
"time"
"github.com/spf13/viper"
)
// SetDefaults sets the default values for certain keys
func SetDefaults(settings *viper.Viper) {
func SetDefaults(settings *Settings) {
// logging
settings.SetDefault("logging.level", "info")
settings.SetDefault("logging.format", "text")
......
......@@ -17,15 +17,14 @@ package configuration
import (
"github.com/arduino/go-paths-helper"
"github.com/spf13/viper"
)
// HardwareDirectories returns all paths that may contains hardware packages.
func HardwareDirectories(settings *viper.Viper) paths.PathList {
func HardwareDirectories(settings *Settings) paths.PathList {
res := paths.PathList{}
if settings.IsSet("directories.Data") {
packagesDir := PackagesDir(Settings)
packagesDir := PackagesDir(settings)
if packagesDir.IsDir() {
res.Add(packagesDir)
}
......@@ -44,34 +43,34 @@ func HardwareDirectories(settings *viper.Viper) paths.PathList {
// IDEBuiltinLibrariesDir returns the IDE-bundled libraries path. Usually
// this directory is present in the Arduino IDE.
func IDEBuiltinLibrariesDir(settings *viper.Viper) *paths.Path {
return paths.New(Settings.GetString("directories.builtin.Libraries"))
func IDEBuiltinLibrariesDir(settings *Settings) *paths.Path {
return paths.New(settings.GetString("directories.builtin.Libraries"))
}
// LibrariesDir returns the full path to the user directory containing
// custom libraries
func LibrariesDir(settings *viper.Viper) *paths.Path {
func LibrariesDir(settings *Settings) *paths.Path {
return paths.New(settings.GetString("directories.User")).Join("libraries")
}
// PackagesDir returns the full path to the packages folder
func PackagesDir(settings *viper.Viper) *paths.Path {
func PackagesDir(settings *Settings) *paths.Path {
return DataDir(settings).Join("packages")
}
// ProfilesCacheDir returns the full path to the profiles cache directory
// (it contains all the platforms and libraries used to compile a sketch
// using profiles)
func ProfilesCacheDir(settings *viper.Viper) *paths.Path {
func ProfilesCacheDir(settings *Settings) *paths.Path {
return DataDir(settings).Join("internal")
}
// DataDir returns the full path to the data directory
func DataDir(settings *viper.Viper) *paths.Path {
func DataDir(settings *Settings) *paths.Path {
return paths.New(settings.GetString("directories.Data"))
}
// DownloadsDir returns the full path to the download cache directory
func DownloadsDir(settings *viper.Viper) *paths.Path {
func DownloadsDir(settings *Settings) *paths.Path {
return paths.New(settings.GetString("directories.Downloads"))
}
......@@ -17,16 +17,18 @@ package configuration
import (
"fmt"
"net/http"
"net/url"
"os"
"runtime"
"github.com/arduino/arduino-cli/commands/cmderrors"
"github.com/arduino/arduino-cli/version"
"github.com/spf13/viper"
"go.bug.st/downloader/v2"
)
// UserAgent returns the user agent (mainly used by HTTP clients)
func UserAgent(settings *viper.Viper) string {
func UserAgent(settings *Settings) string {
subComponent := ""
if settings != nil {
subComponent = settings.GetString("network.user_agent_ext")
......@@ -50,7 +52,7 @@ func UserAgent(settings *viper.Viper) string {
}
// NetworkProxy returns the proxy configuration (mainly used by HTTP clients)
func NetworkProxy(settings *viper.Viper) (*url.URL, error) {
func NetworkProxy(settings *Settings) (*url.URL, error) {
if settings == nil || !settings.IsSet("network.proxy") {
return nil, nil
}
......@@ -65,3 +67,42 @@ func NetworkProxy(settings *viper.Viper) (*url.URL, error) {
return proxy, nil
}
}
// NewHttpClient returns a new http client for use in the arduino-cli
func (settings *Settings) NewHttpClient() (*http.Client, error) {
proxy, err := NetworkProxy(settings)
if err != nil {
return nil, err
}
return &http.Client{
Transport: &httpClientRoundTripper{
transport: &http.Transport{
Proxy: http.ProxyURL(proxy),
},
userAgent: UserAgent(settings),
},
}, nil
}
type httpClientRoundTripper struct {
transport http.RoundTripper
userAgent string
}
func (h *httpClientRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
req.Header.Add("User-Agent", h.userAgent)
return h.transport.RoundTrip(req)
}
// DownloaderConfig returns the downloader configuration based on current settings.
func (settings *Settings) DownloaderConfig() (downloader.Config, error) {
httpClient, err := settings.NewHttpClient()
if err != nil {
return downloader.Config{}, &cmderrors.InvalidArgumentError{
Message: tr("Could not connect via HTTP"),
Cause: err}
}
return downloader.Config{
HttpClient: *httpClient,
}, nil
}
......@@ -13,16 +13,16 @@
// Arduino software without disclosing the source code of your own applications.
// To purchase a commercial license, send an email to license@arduino.cc.
package httpclient
package configuration_test
import (
"fmt"
"io"
"net/http"
"net/http/httptest"
"net/url"
"testing"
"github.com/arduino/arduino-cli/internal/cli/configuration"
"github.com/stretchr/testify/require"
)
......@@ -32,9 +32,10 @@ func TestUserAgentHeader(t *testing.T) {
}))
defer ts.Close()
client := NewWithConfig(&Config{
UserAgent: "test-user-agent",
})
settings := configuration.Init("")
settings.Set("network.user_agent_ext", "test-user-agent")
client, err := settings.NewHttpClient()
require.NoError(t, err)
request, err := http.NewRequest("GET", ts.URL, nil)
require.NoError(t, err)
......@@ -45,7 +46,7 @@ func TestUserAgentHeader(t *testing.T) {
b, err := io.ReadAll(response.Body)
require.NoError(t, err)
require.Equal(t, "test-user-agent", string(b))
require.Contains(t, string(b), "test-user-agent")
}
func TestProxy(t *testing.T) {
......@@ -54,13 +55,11 @@ func TestProxy(t *testing.T) {
}))
defer ts.Close()
proxyURL, err := url.Parse(ts.URL)
settings := configuration.Init("")
settings.Set("network.proxy", ts.URL)
client, err := settings.NewHttpClient()
require.NoError(t, err)
client := NewWithConfig(&Config{
Proxy: proxyURL,
})
request, err := http.NewRequest("GET", "http://arduino.cc", nil)
require.NoError(t, err)
......
......@@ -24,12 +24,10 @@ import (
"strings"
"syscall"
"github.com/arduino/arduino-cli/commands"
"github.com/arduino/arduino-cli/internal/cli/configuration"
"github.com/arduino/arduino-cli/internal/cli/feedback"
"github.com/arduino/arduino-cli/internal/i18n"
srv_commands "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
"github.com/arduino/arduino-cli/version"
rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
"github.com/arduino/go-paths-helper"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
......@@ -45,17 +43,24 @@ var (
)
// NewCommand created a new `daemon` command
func NewCommand() *cobra.Command {
func NewCommand(srv rpc.ArduinoCoreServiceServer, defaultSettings *configuration.Settings) *cobra.Command {
var daemonPort string
daemonCommand := &cobra.Command{
Use: "daemon",
Short: tr("Run as a daemon on port: %s", configuration.Settings.GetString("daemon.port")),
Long: tr("Running as a daemon the initialization of cores and libraries is done only once."),
Short: tr("Run the Arduino CLI as a gRPC daemon."),
Example: " " + os.Args[0] + " daemon",
Args: cobra.NoArgs,
Run: runDaemonCommand,
PreRun: func(cmd *cobra.Command, args []string) {
// Bundled libraries support is enabled by default when running as a daemon
defaultSettings.SetDefault(
"directories.builtin.Libraries",
configuration.GetDefaultBuiltinLibrariesDir())
},
Run: func(cmd *cobra.Command, args []string) {
runDaemonCommand(srv, daemonPort)
},
}
daemonCommand.PersistentFlags().String("port", "", tr("The TCP port the daemon will listen to"))
configuration.Settings.BindPFlag("daemon.port", daemonCommand.PersistentFlags().Lookup("port"))
daemonCommand.Flags().StringVar(&daemonPort, "port", defaultSettings.GetString("daemon.port"), tr("The TCP port the daemon will listen to"))
daemonCommand.Flags().BoolVar(&daemonize, "daemonize", false, tr("Do not terminate daemon process if the parent process dies"))
daemonCommand.Flags().BoolVar(&debug, "debug", false, tr("Enable debug logging of gRPC calls"))
daemonCommand.Flags().StringVar(&debugFile, "debug-file", "", tr("Append debug logging to the specified file"))
......@@ -63,13 +68,9 @@ func NewCommand() *cobra.Command {
return daemonCommand
}
func runDaemonCommand(cmd *cobra.Command, args []string) {
func runDaemonCommand(srv rpc.ArduinoCoreServiceServer, daemonPort string) {
logrus.Info("Executing `arduino-cli daemon`")
// Bundled libraries support is enabled by default when running as a daemon
configuration.Settings.SetDefault("directories.builtin.Libraries", configuration.GetDefaultBuiltinLibrariesDir())
port := configuration.Settings.GetString("daemon.port")
gRPCOptions := []grpc.ServerOption{}
if debugFile != "" {
if !debug {
......@@ -98,43 +99,40 @@ func runDaemonCommand(cmd *cobra.Command, args []string) {
)
}
s := grpc.NewServer(gRPCOptions...)
// Set specific user-agent for the daemon
configuration.Settings.Set("network.user_agent_ext", "daemon")
// register the commands service
srv_commands.RegisterArduinoCoreServiceServer(s,
commands.NewArduinoCoreServer(version.VersionInfo.VersionString))
rpc.RegisterArduinoCoreServiceServer(s, srv)
if !daemonize {
// When parent process ends terminate also the daemon
go feedback.ExitWhenParentProcessEnds()
}
ip := "127.0.0.1"
lis, err := net.Listen("tcp", fmt.Sprintf("%s:%s", ip, port))
daemonIP := "127.0.0.1"
lis, err := net.Listen("tcp", fmt.Sprintf("%s:%s", daemonIP, daemonPort))
if err != nil {
// Invalid port, such as "Foo"
var dnsError *net.DNSError
if errors.As(err, &dnsError) {
feedback.Fatal(tr("Failed to listen on TCP port: %[1]s. %[2]s is unknown name.", port, dnsError.Name), feedback.ErrBadTCPPortArgument)
feedback.Fatal(tr("Failed to listen on TCP port: %[1]s. %[2]s is unknown name.", daemonPort, dnsError.Name), feedback.ErrBadTCPPortArgument)
}
// Invalid port number, such as -1
var addrError *net.AddrError
if errors.As(err, &addrError) {
feedback.Fatal(tr("Failed to listen on TCP port: %[1]s. %[2]s is an invalid port.", port, addrError.Addr), feedback.ErrBadTCPPortArgument)
feedback.Fatal(tr("Failed to listen on TCP port: %[1]s. %[2]s is an invalid port.", daemonPort, addrError.Addr), feedback.ErrBadTCPPortArgument)
}
// Port is already in use
var syscallErr *os.SyscallError
if errors.As(err, &syscallErr) && errors.Is(syscallErr.Err, syscall.EADDRINUSE) {
feedback.Fatal(tr("Failed to listen on TCP port: %s. Address already in use.", port), feedback.ErrFailedToListenToTCPPort)
feedback.Fatal(tr("Failed to listen on TCP port: %s. Address already in use.", daemonPort), feedback.ErrFailedToListenToTCPPort)
}
feedback.Fatal(tr("Failed to listen on TCP port: %[1]s. Unexpected error: %[2]v", port, err), feedback.ErrFailedToListenToTCPPort)
feedback.Fatal(tr("Failed to listen on TCP port: %[1]s. Unexpected error: %[2]v", daemonPort, err), feedback.ErrFailedToListenToTCPPort)
}
// We need to retrieve the port used only if the user did not specify it
// and let the OS choose it randomly, in all other cases we already know
// which port is used.
if port == "0" {
if daemonPort == "0" {
address := lis.Addr()
split := strings.Split(address.String(), ":")
......@@ -142,12 +140,12 @@ func runDaemonCommand(cmd *cobra.Command, args []string) {
feedback.Fatal(tr("Invalid TCP address: port is missing"), feedback.ErrBadTCPPortArgument)
}
port = split[1]
daemonPort = split[1]
}
feedback.PrintResult(daemonResult{
IP: ip,
Port: port,
IP: daemonIP,
Port: daemonPort,
})
if err := s.Serve(lis); err != nil {
......
......@@ -34,12 +34,13 @@ import (
semver "go.bug.st/relaxed-semver"
)
func initInstallCommand(srv rpc.ArduinoCoreServiceServer) *cobra.Command {
func initInstallCommand(srv rpc.ArduinoCoreServiceServer, defaultSettings *configuration.Settings) *cobra.Command {
var noDeps bool
var noOverwrite bool
var gitURL bool
var zipPath bool
var useBuiltinLibrariesDir bool
enableUnsafeInstall := defaultSettings.GetBool("library.enable_unsafe_install")
installCommand := &cobra.Command{
Use: fmt.Sprintf("install %s[@%s]...", tr("LIBRARY"), tr("VERSION_NUMBER")),
Short: tr("Installs one or more specified libraries into the system."),
......@@ -52,7 +53,7 @@ func initInstallCommand(srv rpc.ArduinoCoreServiceServer) *cobra.Command {
" " + os.Args[0] + " lib install --zip-path /path/to/WiFi101.zip /path/to/ArduinoBLE.zip\n",
Args: cobra.MinimumNArgs(1),
Run: func(cmd *cobra.Command, args []string) {
runInstallCommand(srv, args, noDeps, noOverwrite, gitURL, zipPath, useBuiltinLibrariesDir)
runInstallCommand(srv, args, noDeps, noOverwrite, gitURL, zipPath, useBuiltinLibrariesDir, enableUnsafeInstall)
},
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return arguments.GetInstallableLibs(context.Background(), srv), cobra.ShellCompDirectiveDefault
......@@ -66,13 +67,13 @@ func initInstallCommand(srv rpc.ArduinoCoreServiceServer) *cobra.Command {
return installCommand
}
func runInstallCommand(srv rpc.ArduinoCoreServiceServer, args []string, noDeps bool, noOverwrite bool, gitURL bool, zipPath bool, useBuiltinLibrariesDir bool) {
func runInstallCommand(srv rpc.ArduinoCoreServiceServer, args []string, noDeps bool, noOverwrite bool, gitURL bool, zipPath bool, useBuiltinLibrariesDir bool, enableUnsafeInstall bool) {
ctx := context.Background()
instance := instance.CreateAndInit(ctx, srv)
logrus.Info("Executing `arduino-cli lib install`")
if zipPath || gitURL {
if !configuration.Settings.GetBool("library.enable_unsafe_install") {
if !enableUnsafeInstall {
documentationURL := "https://arduino.github.io/arduino-cli/latest/configuration/#configuration-keys"
_, err := semver.Parse(version.VersionInfo.VersionString)
if err == nil {
......
......@@ -18,6 +18,7 @@ package lib
import (
"os"
"github.com/arduino/arduino-cli/internal/cli/configuration"
"github.com/arduino/arduino-cli/internal/i18n"
rpc "github.com/arduino/arduino-cli/rpc/cc/arduino/cli/commands/v1"
"github.com/spf13/cobra"
......@@ -26,7 +27,7 @@ import (
var tr = i18n.Tr
// NewCommand created a new `lib` command
func NewCommand(srv rpc.ArduinoCoreServiceServer) *cobra.Command {
func NewCommand(srv rpc.ArduinoCoreServiceServer, defaultSettings *configuration.Settings) *cobra.Command {
libCommand := &cobra.Command{
Use: "lib",
Short: tr("Arduino commands about libraries."),
......@@ -37,7 +38,7 @@ func NewCommand(srv rpc.ArduinoCoreServiceServer) *cobra.Command {
}
libCommand.AddCommand(initDownloadCommand(srv))
libCommand.AddCommand(initInstallCommand(srv))
libCommand.AddCommand(initInstallCommand(srv, defaultSettings))
libCommand.AddCommand(initListCommand(srv))
libCommand.AddCommand(initExamplesCommand(srv))
libCommand.AddCommand(initSearchCommand(srv))
......
......@@ -31,8 +31,8 @@ func main() {
os.MkdirAll(os.Args[1], 0755) // Create the output folder if it doesn't already exist
configuration.Settings = configuration.Init(configuration.FindConfigFileInArgsFallbackOnEnv(os.Args))
cli := cli.NewCommand(nil)
settings := configuration.Init(configuration.FindConfigFileInArgsFallbackOnEnv(os.Args))
cli := cli.NewCommand(nil, settings)
cli.DisableAutoGenTag = true // Disable addition of auto-generated date stamp
err := doc.GenMarkdownTree(cli, os.Args[1])
if err != nil {
......
......@@ -27,12 +27,12 @@ import (
)
func main() {
configuration.Settings = configuration.Init(configuration.FindConfigFileInArgsFallbackOnEnv(os.Args))
i18n.Init(configuration.Settings.GetString("locale"))
defaultSettings := configuration.Init(configuration.FindConfigFileInArgsFallbackOnEnv(os.Args))
i18n.Init(defaultSettings.GetString("locale"))
srv := commands.NewArduinoCoreServer(version.VersionInfo.VersionString)
srv := commands.NewArduinoCoreServer(version.VersionInfo.VersionString, defaultSettings)
arduinoCmd := cli.NewCommand(srv)
arduinoCmd := cli.NewCommand(srv, defaultSettings)
if err := arduinoCmd.Execute(); err != nil {
feedback.FatalError(err, feedback.ErrGeneric)
}
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment