diff --git a/commands/instances.go b/commands/instances.go index 50aa45c1ee5..743bae6b931 100644 --- a/commands/instances.go +++ b/commands/instances.go @@ -47,8 +47,9 @@ import ( func installTool(pm *packagemanager.PackageManager, tool *cores.ToolRelease, downloadCB rpc.DownloadProgressCB, taskCB rpc.TaskProgressCB) error { pme, release := pm.NewExplorer() defer release() + taskCB(&rpc.TaskProgress{Name: tr("Downloading missing tool %s", tool)}) - if err := pme.DownloadToolRelease(tool, nil, downloadCB); err != nil { + if err := pme.DownloadToolRelease(tool, downloadCB); err != nil { return fmt.Errorf(tr("downloading %[1]s tool: %[2]s"), tool, err) } taskCB(&rpc.TaskProgress{Completed: true}) @@ -60,16 +61,16 @@ func installTool(pm *packagemanager.PackageManager, tool *cores.ToolRelease, dow // Create a new Instance ready to be initialized, supporting directories are also created. func (s *arduinoCoreServerImpl) Create(ctx context.Context, req *rpc.CreateRequest) (*rpc.CreateResponse, error) { - var userAgent []string + var userAgent string if md, ok := metadata.FromIncomingContext(ctx); ok { - userAgent = md.Get("user-agent") + userAgent = strings.Join(md.Get("user-agent"), " ") } - if len(userAgent) == 0 { - userAgent = []string{"gRPCClientUnknown/0.0.0"} + if userAgent == "" { + userAgent = "gRPCClientUnknown/0.0.0" } // Setup downloads directory - downloadsDir := configuration.DownloadsDir(configuration.Settings) + downloadsDir := configuration.DownloadsDir(s.settings) if downloadsDir.NotExist() { err := downloadsDir.MkdirAll() if err != nil { @@ -78,8 +79,8 @@ func (s *arduinoCoreServerImpl) Create(ctx context.Context, req *rpc.CreateReque } // Setup data directory - dataDir := configuration.DataDir(configuration.Settings) - packagesDir := configuration.PackagesDir(configuration.Settings) + dataDir := configuration.DataDir(s.settings) + packagesDir := configuration.PackagesDir(s.settings) if packagesDir.NotExist() { err := packagesDir.MkdirAll() if err != nil { @@ -87,7 +88,11 @@ func (s *arduinoCoreServerImpl) Create(ctx context.Context, req *rpc.CreateReque } } - inst, err := instances.Create(dataDir, packagesDir, downloadsDir, userAgent...) + config, err := s.settings.DownloaderConfig() + if err != nil { + return nil, err + } + inst, err := instances.Create(dataDir, packagesDir, downloadsDir, userAgent, config) if err != nil { return nil, err } @@ -106,6 +111,8 @@ func InitStreamResponseToCallbackFunction(ctx context.Context, cb func(r *rpc.In // Failures don't stop the loading process, in case of loading failure the Platform or library // is simply skipped and an error gRPC status is sent to responseCallback. func (s *arduinoCoreServerImpl) Init(req *rpc.InitRequest, stream rpc.ArduinoCoreService_InitServer) error { + ctx := stream.Context() + instance := req.GetInstance() if !instances.IsValid(instance) { return &cmderrors.InvalidInstanceError{} @@ -168,7 +175,7 @@ func (s *arduinoCoreServerImpl) Init(req *rpc.InitRequest, stream rpc.ArduinoCor defaultIndexURL, _ := utils.URLParse(globals.DefaultIndexURL) allPackageIndexUrls := []*url.URL{defaultIndexURL} if profile == nil { - for _, u := range configuration.Settings.GetStringSlice("board_manager.additional_urls") { + for _, u := range s.settings.GetStringSlice("board_manager.additional_urls") { URL, err := utils.URLParse(u) if err != nil { e := &cmderrors.InitFailedError{ @@ -183,7 +190,7 @@ func (s *arduinoCoreServerImpl) Init(req *rpc.InitRequest, stream rpc.ArduinoCor } } - if err := firstUpdate(context.Background(), s, req.GetInstance(), downloadCallback, allPackageIndexUrls); err != nil { + if err := firstUpdate(ctx, s, req.GetInstance(), configuration.DataDir(s.settings), downloadCallback, allPackageIndexUrls); err != nil { e := &cmderrors.InitFailedError{ Code: codes.InvalidArgument, Cause: err, @@ -236,15 +243,13 @@ func (s *arduinoCoreServerImpl) Init(req *rpc.InitRequest, stream rpc.ArduinoCor // Load Platforms if profile == nil { - for _, err := range pmb.LoadHardware() { + for _, err := range pmb.LoadHardware(s.settings) { s := &cmderrors.PlatformLoadingError{Cause: err} responseError(s.GRPCStatus()) } } else { // Load platforms from profile - errs := pmb.LoadHardwareForProfile( - profile, true, downloadCallback, taskCallback, - ) + errs := pmb.LoadHardwareForProfile(profile, true, downloadCallback, taskCallback, s.settings) for _, err := range errs { s := &cmderrors.PlatformLoadingError{Cause: err} responseError(s.GRPCStatus()) @@ -342,7 +347,7 @@ func (s *arduinoCoreServerImpl) Init(req *rpc.InitRequest, stream rpc.ArduinoCor if profile == nil { // Add directories of libraries bundled with IDE - if bundledLibsDir := configuration.IDEBuiltinLibrariesDir(configuration.Settings); bundledLibsDir != nil { + if bundledLibsDir := configuration.IDEBuiltinLibrariesDir(s.settings); bundledLibsDir != nil { lmb.AddLibrariesDir(librariesmanager.LibrariesDir{ Path: bundledLibsDir, Location: libraries.IDEBuiltIn, @@ -351,14 +356,14 @@ func (s *arduinoCoreServerImpl) Init(req *rpc.InitRequest, stream rpc.ArduinoCor // Add libraries directory from config file lmb.AddLibrariesDir(librariesmanager.LibrariesDir{ - Path: configuration.LibrariesDir(configuration.Settings), + Path: configuration.LibrariesDir(s.settings), Location: libraries.User, }) } else { // Load libraries required for profile for _, libraryRef := range profile.Libraries { uid := libraryRef.InternalUniqueIdentifier() - libRoot := configuration.ProfilesCacheDir(configuration.Settings).Join(uid) + libRoot := configuration.ProfilesCacheDir(s.settings).Join(uid) libDir := libRoot.Join(libraryRef.Library) if !libDir.IsDir() { @@ -371,7 +376,14 @@ func (s *arduinoCoreServerImpl) Init(req *rpc.InitRequest, stream rpc.ArduinoCor responseError(err.GRPCStatus()) continue } - if err := libRelease.Resource.Download(pme.DownloadDir, nil, libRelease.String(), downloadCallback, ""); err != nil { + config, err := s.settings.DownloaderConfig() + if err != nil { + taskCallback(&rpc.TaskProgress{Name: tr("Error downloading library %s", libraryRef)}) + e := &cmderrors.FailedLibraryInstallError{Cause: err} + responseError(e.GRPCStatus()) + continue + } + if err := libRelease.Resource.Download(pme.DownloadDir, config, libRelease.String(), downloadCallback, ""); err != nil { taskCallback(&rpc.TaskProgress{Name: tr("Error downloading library %s", libraryRef)}) e := &cmderrors.FailedLibraryInstallError{Cause: err} responseError(e.GRPCStatus()) @@ -407,7 +419,7 @@ func (s *arduinoCoreServerImpl) Init(req *rpc.InitRequest, stream rpc.ArduinoCor // Refreshes the locale used, this will change the // language of the CLI if the locale is different // after started. - i18n.Init(configuration.Settings.GetString("locale")) + i18n.Init(s.settings.GetString("locale")) return nil } @@ -449,7 +461,11 @@ func (s *arduinoCoreServerImpl) UpdateLibrariesIndex(req *rpc.UpdateLibrariesInd // TODO: pass context // ctx := stream.Context() - if err := globals.LibrariesIndexResource.Download(indexDir, downloadCB); err != nil { + config, err := s.settings.DownloaderConfig() + if err != nil { + return err + } + if err := globals.LibrariesIndexResource.Download(indexDir, downloadCB, config); err != nil { return err } @@ -477,11 +493,11 @@ func (s *arduinoCoreServerImpl) UpdateIndex(req *rpc.UpdateIndexRequest, stream var downloadCB rpc.DownloadProgressCB = func(p *rpc.DownloadProgress) { syncSend.Send(&rpc.UpdateIndexResponse{DownloadProgress: p}) } - indexpath := configuration.DataDir(configuration.Settings) + indexpath := configuration.DataDir(s.settings) urls := []string{globals.DefaultIndexURL} if !req.GetIgnoreCustomPackageIndexes() { - urls = append(urls, configuration.Settings.GetStringSlice("board_manager.additional_urls")...) + urls = append(urls, s.settings.GetStringSlice("board_manager.additional_urls")...) } failed := false @@ -511,12 +527,19 @@ func (s *arduinoCoreServerImpl) UpdateIndex(req *rpc.UpdateIndexRequest, stream continue } + config, err := s.settings.DownloaderConfig() + if err != nil { + downloadCB.Start(u, tr("Downloading index: %s", filepath.Base(URL.Path))) + downloadCB.End(false, tr("Invalid network configuration: %s", err)) + failed = true + } + indexResource := resources.IndexResource{URL: URL} if strings.HasSuffix(URL.Host, "arduino.cc") && strings.HasSuffix(URL.Path, ".json") { indexResource.SignatureURL, _ = url.Parse(u) // should not fail because we already parsed it indexResource.SignatureURL.Path += ".sig" } - if err := indexResource.Download(indexpath, downloadCB); err != nil { + if err := indexResource.Download(indexpath, downloadCB, config); err != nil { failed = true } } @@ -529,10 +552,8 @@ func (s *arduinoCoreServerImpl) UpdateIndex(req *rpc.UpdateIndexRequest, stream // firstUpdate downloads libraries and packages indexes if they don't exist. // This ideally is only executed the first time the CLI is run. -func firstUpdate(ctx context.Context, srv rpc.ArduinoCoreServiceServer, instance *rpc.Instance, downloadCb func(msg *rpc.DownloadProgress), externalPackageIndexes []*url.URL) error { - // Gets the data directory to verify if library_index.json and package_index.json exist - dataDir := configuration.DataDir(configuration.Settings) - libraryIndex := dataDir.Join("library_index.json") +func firstUpdate(ctx context.Context, srv rpc.ArduinoCoreServiceServer, instance *rpc.Instance, indexDir *paths.Path, downloadCb func(msg *rpc.DownloadProgress), externalPackageIndexes []*url.URL) error { + libraryIndex := indexDir.Join("library_index.json") if libraryIndex.NotExist() { // The library_index.json file doesn't exists, that means the CLI is run for the first time @@ -554,7 +575,7 @@ func firstUpdate(ctx context.Context, srv rpc.ArduinoCoreServiceServer, instance Message: tr("Error downloading index '%s'", URL), Cause: &cmderrors.InvalidURLError{}} } - packageIndexFile := dataDir.Join(packageIndexFileName) + packageIndexFile := indexDir.Join(packageIndexFileName) if packageIndexFile.NotExist() { // The index file doesn't exists, that means the CLI is run for the first time, // or the 3rd party package index URL has just been added. Similarly to the diff --git a/commands/internal/instances/instances.go b/commands/internal/instances/instances.go index 019927a4967..b5cd63a3287 100644 --- a/commands/internal/instances/instances.go +++ b/commands/internal/instances/instances.go @@ -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{ diff --git a/commands/service.go b/commands/service.go index e8bf689d59a..03da5135b29 100644 --- a/commands/service.go +++ b/commands/service.go @@ -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 diff --git a/commands/service_board_list.go b/commands/service_board_list.go index ba6cd3c727f..aff1085c9c2 100644 --- a/commands/service_board_list.go +++ b/commands/service_board_list.go @@ -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() } diff --git a/commands/service_board_list_test.go b/commands/service_board_list_test.go index 2db649e4554..e9132a50949 100644 --- a/commands/service_board_list_test.go +++ b/commands/service_board_list_test.go @@ -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) diff --git a/commands/service_compile.go b/commands/service_compile.go index 0acdabd95ab..4745fbc28ec 100644 --- a/commands/service_compile.go +++ b/commands/service_compile.go @@ -22,6 +22,7 @@ import ( "io" "sort" "strings" + "time" "github.com/arduino/arduino-cli/commands/cmderrors" "github.com/arduino/arduino-cli/commands/internal/instances" @@ -69,7 +70,7 @@ func (s *arduinoCoreServerImpl) Compile(req *rpc.CompileRequest, stream rpc.Ardu // There is a binding between the export binaries setting and the CLI flag to explicitly set it, // since we want this binding to work also for the gRPC interface we must read it here in this // package instead of the cli/compile one, otherwise we'd lose the binding. - exportBinaries := configuration.Settings.GetBool("sketch.always_export_binaries") + exportBinaries := s.settings.GetBool("sketch.always_export_binaries") // If we'd just read the binding in any case, even if the request sets the export binaries setting, // the settings value would always overwrite the request one and it wouldn't have any effect // setting it for individual requests. To solve this we use a wrapper.BoolValue to handle @@ -180,7 +181,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() == "" { @@ -202,7 +206,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 { @@ -235,9 +239,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(), @@ -402,9 +406,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 @@ -417,7 +419,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) } diff --git a/commands/service_debug_test.go b/commands/service_debug_test.go index e3bb45b0fd8..8e627c44b76 100644 --- a/commands/service_debug_test.go +++ b/commands/service_debug_test.go @@ -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) diff --git a/commands/service_library_download.go b/commands/service_library_download.go index 37b4ecbb53e..8543e8cdea6 100644 --- a/commands/service_library_download.go +++ b/commands/service_library_download.go @@ -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} } diff --git a/commands/service_library_install.go b/commands/service_library_install.go index d4440d3d2ae..20b98695de2 100644 --- a/commands/service_library_install.go +++ b/commands/service_library_install.go @@ -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 { diff --git a/commands/service_platform_download.go b/commands/service_platform_download.go index 23a8096120d..09f49a2d76b 100644 --- a/commands/service_platform_download.go +++ b/commands/service_platform_download.go @@ -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 } } diff --git a/commands/service_platform_search_test.go b/commands/service_platform_search_test.go index 63dc02c5265..3b7b87cf30d 100644 --- a/commands/service_platform_search_test.go +++ b/commands/service_platform_search_test.go @@ -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{}) diff --git a/commands/service_settings.go b/commands/service_settings.go index f4675e0467e..177d0819178 100644 --- a/commands/service_settings.go +++ b/commands/service_settings.go @@ -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 } diff --git a/commands/service_settings_test.go b/commands/service_settings_test.go index 226a2663990..e1d67fb0fad 100644 --- a/commands/service_settings_test.go +++ b/commands/service_settings_test.go @@ -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", }) diff --git a/commands/service_sketch_load_test.go b/commands/service_sketch_load_test.go index 36bb58ea351..822dfea7c5d 100644 --- a/commands/service_sketch_load_test.go +++ b/commands/service_sketch_load_test.go @@ -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", }) diff --git a/commands/service_sketch_new.go b/commands/service_sketch_new.go index a8eb518a815..b0c9c079f73 100644 --- a/commands/service_sketch_new.go +++ b/commands/service_sketch_new.go @@ -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 { diff --git a/commands/service_sketch_new_test.go b/commands/service_sketch_new_test.go index 0d0b3e05aa6..353dec17a05 100644 --- a/commands/service_sketch_new_test.go +++ b/commands/service_sketch_new_test.go @@ -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, diff --git a/commands/service_upload_test.go b/commands/service_upload_test.go index cf2f4ff3306..e8d70e19c4d 100644 --- a/commands/service_upload_test.go +++ b/commands/service_upload_test.go @@ -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") diff --git a/internal/arduino/cores/packagemanager/download.go b/internal/arduino/cores/packagemanager/download.go index c4a7cb62d59..4ac06cf19f8 100644 --- a/internal/arduino/cores/packagemanager/download.go +++ b/internal/arduino/cores/packagemanager/download.go @@ -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, "") } diff --git a/internal/arduino/cores/packagemanager/install_uninstall.go b/internal/arduino/cores/packagemanager/install_uninstall.go index bc29f08fa43..4b8c1cba9b4 100644 --- a/internal/arduino/cores/packagemanager/install_uninstall.go +++ b/internal/arduino/cores/packagemanager/install_uninstall.go @@ -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}) diff --git a/internal/arduino/cores/packagemanager/loader.go b/internal/arduino/cores/packagemanager/loader.go index 68c603044e5..29e8fb511a4 100644 --- a/internal/arduino/cores/packagemanager/loader.go +++ b/internal/arduino/cores/packagemanager/loader.go @@ -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) } diff --git a/internal/arduino/cores/packagemanager/loader_test.go b/internal/arduino/cores/packagemanager/loader_test.go index d0d41992179..a9ee015c86d 100644 --- a/internal/arduino/cores/packagemanager/loader_test.go +++ b/internal/arduino/cores/packagemanager/loader_test.go @@ -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") diff --git a/internal/arduino/cores/packagemanager/package_manager.go b/internal/arduino/cores/packagemanager/package_manager.go index cea5e799d1b..d9a5f79a2ac 100644 --- a/internal/arduino/cores/packagemanager/package_manager.go +++ b/internal/arduino/cores/packagemanager/package_manager.go @@ -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 } diff --git a/internal/arduino/cores/packagemanager/package_manager_test.go b/internal/arduino/cores/packagemanager/package_manager_test.go index 055a6d332d0..fb215816d0c 100644 --- a/internal/arduino/cores/packagemanager/package_manager_test.go +++ b/internal/arduino/cores/packagemanager/package_manager_test.go @@ -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() diff --git a/internal/arduino/cores/packagemanager/profiles.go b/internal/arduino/cores/packagemanager/profiles.go index 2422766e899..6cd6f3578d2 100644 --- a/internal/arduino/cores/packagemanager/profiles.go +++ b/internal/arduino/cores/packagemanager/profiles.go @@ -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} } diff --git a/internal/arduino/httpclient/httpclient.go b/internal/arduino/httpclient/httpclient.go index ec4b4acc4a6..0e904dddf8f 100644 --- a/internal/arduino/httpclient/httpclient.go +++ b/internal/arduino/httpclient/httpclient.go @@ -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) -} diff --git a/internal/arduino/resources/download.go b/internal/arduino/resources/download.go index 4f1df1ad5b3..19b37df48ac 100644 --- a/internal/arduino/resources/download.go +++ b/internal/arduino/resources/download.go @@ -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) diff --git a/internal/arduino/resources/helpers_test.go b/internal/arduino/resources/helpers_test.go index e56747d8b40..72c595f6c89 100644 --- a/internal/arduino/resources/helpers_test.go +++ b/internal/arduino/resources/helpers_test.go @@ -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) } diff --git a/internal/arduino/resources/index.go b/internal/arduino/resources/index.go index 4740c6f12f0..fb05f55d776 100644 --- a/internal/arduino/resources/index.go +++ b/internal/arduino/resources/index.go @@ -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} } diff --git a/internal/arduino/resources/resources_test.go b/internal/arduino/resources/resources_test.go index 9cf8ae54509..ee987ddcea6 100644 --- a/internal/arduino/resources/resources_test.go +++ b/internal/arduino/resources/resources_test.go @@ -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()) diff --git a/internal/cli/arguments/reference_test.go b/internal/cli/arguments/reference_test.go index b4176dc4e5e..f91109f8de6 100644 --- a/internal/cli/arguments/reference_test.go +++ b/internal/cli/arguments/reference_test.go @@ -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)) diff --git a/internal/cli/cache/cache.go b/internal/cli/cache/cache.go index 43c9d775234..09635356b1d 100644 --- a/internal/cli/cache/cache.go +++ b/internal/cli/cache/cache.go @@ -18,6 +18,7 @@ package cache import ( "os" + "github.com/arduino/arduino-cli/internal/cli/configuration" "github.com/arduino/arduino-cli/internal/i18n" "github.com/spf13/cobra" ) @@ -25,7 +26,7 @@ import ( var tr = i18n.Tr // NewCommand created a new `cache` command -func NewCommand() *cobra.Command { +func NewCommand(settings *configuration.Settings) *cobra.Command { cacheCommand := &cobra.Command{ Use: "cache", Short: tr("Arduino cache commands."), @@ -34,7 +35,7 @@ func NewCommand() *cobra.Command { " " + os.Args[0] + " cache clean\n\n", } - cacheCommand.AddCommand(initCleanCommand()) + cacheCommand.AddCommand(initCleanCommand(settings)) return cacheCommand } diff --git a/internal/cli/cache/clean.go b/internal/cli/cache/clean.go index c4c37a1cf1e..9d28e86b82e 100644 --- a/internal/cli/cache/clean.go +++ b/internal/cli/cache/clean.go @@ -24,22 +24,24 @@ import ( "github.com/spf13/cobra" ) -func initCleanCommand() *cobra.Command { +func initCleanCommand(settings *configuration.Settings) *cobra.Command { cleanCommand := &cobra.Command{ Use: "clean", Short: tr("Delete Boards/Library Manager download cache."), Long: tr("Delete contents of the `directories.downloads` folder, where archive files are staged during installation of libraries and boards platforms."), Example: " " + os.Args[0] + " cache clean", Args: cobra.NoArgs, - Run: runCleanCommand, + Run: func(cmd *cobra.Command, args []string) { + runCleanCommand(settings) + }, } return cleanCommand } -func runCleanCommand(cmd *cobra.Command, args []string) { +func runCleanCommand(settings *configuration.Settings) { logrus.Info("Executing `arduino-cli cache clean`") - cachePath := configuration.DownloadsDir(configuration.Settings) + cachePath := configuration.DownloadsDir(settings) err := cachePath.RemoveAll() if err != nil { feedback.Fatal(tr("Error cleaning caches: %v", err), feedback.ErrGeneric) diff --git a/internal/cli/cli.go b/internal/cli/cli.go index 8779a87ded0..3f4b994d541 100644 --- a/internal/cli/cli.go +++ b/internal/cli/cli.go @@ -62,7 +62,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) // ArduinoCli is the root command @@ -71,21 +71,21 @@ func NewCommand(srv rpc.ArduinoCoreServiceServer) *cobra.Command { Short: tr("Arduino CLI."), Long: tr("Arduino Command Line Interface (arduino-cli)."), Example: fmt.Sprintf(" %s <%s> [%s...]", os.Args[0], tr("command"), tr("flags")), - PersistentPreRun: preRun, + PersistentPreRun: func(cmd *cobra.Command, args []string) { preRun(cmd, defaultSettings) }, PersistentPostRun: postRun, } cmd.SetUsageTemplate(getUsageTemplate()) cmd.AddCommand(board.NewCommand(srv)) - cmd.AddCommand(cache.NewCommand()) - cmd.AddCommand(compile.NewCommand(srv)) + cmd.AddCommand(cache.NewCommand(defaultSettings)) + cmd.AddCommand(compile.NewCommand(srv, defaultSettings)) cmd.AddCommand(completion.NewCommand()) - cmd.AddCommand(config.NewCommand()) - cmd.AddCommand(core.NewCommand(srv)) - cmd.AddCommand(daemon.NewCommand()) + cmd.AddCommand(config.NewCommand(srv, defaultSettings)) + cmd.AddCommand(core.NewCommand(srv, defaultSettings)) + 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)) @@ -94,7 +94,7 @@ func NewCommand(srv rpc.ArduinoCoreServiceServer) *cobra.Command { cmd.AddCommand(upload.NewCommand(srv)) cmd.AddCommand(debug.NewCommand(srv)) cmd.AddCommand(burnbootloader.NewCommand(srv)) - cmd.AddCommand(version.NewCommand()) + cmd.AddCommand(version.NewCommand(defaultSettings)) cmd.AddCommand(feedback.NewCommand()) cmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, tr("Print the logs on the standard output.")) cmd.Flag("verbose").Hidden = true @@ -118,7 +118,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 } @@ -139,17 +139,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()) @@ -166,7 +166,7 @@ func preRun(cmd *cobra.Command, args []string) { if err != nil { updaterMessageChan <- nil } - updaterMessageChan <- updater.CheckForUpdate(currentVersion) + updaterMessageChan <- updater.CheckForUpdate(currentVersion, defaultSettings) }() // @@ -186,13 +186,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 { @@ -208,8 +208,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) } diff --git a/internal/cli/compile/compile.go b/internal/cli/compile/compile.go index 389065db755..8d9cd0df4f0 100644 --- a/internal/cli/compile/compile.go +++ b/internal/cli/compile/compile.go @@ -76,7 +76,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."), @@ -135,7 +135,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.")) diff --git a/internal/cli/config/add.go b/internal/cli/config/add.go index 5018b0440de..5678e52e470 100644 --- a/internal/cli/config/add.go +++ b/internal/cli/config/add.go @@ -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) } } diff --git a/internal/cli/config/config.go b/internal/cli/config/config.go index c59714478d3..d8d4cbf5e46 100644 --- a/internal/cli/config/config.go +++ b/internal/cli/config/config.go @@ -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 { diff --git a/internal/cli/config/delete.go b/internal/cli/config/delete.go index e5889a12952..75b81563aed 100644 --- a/internal/cli/config/delete.go +++ b/internal/cli/config/delete.go @@ -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) } } diff --git a/internal/cli/config/dump.go b/internal/cli/config/dump.go index 9e10bf34f9b..695e5b11ce8 100644 --- a/internal/cli/config/dump.go +++ b/internal/cli/config/dump.go @@ -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 { diff --git a/internal/cli/config/get.go b/internal/cli/config/get.go index 68b8afd0d4a..dec61be03a7 100644 --- a/internal/cli/config/get.go +++ b/internal/cli/config/get.go @@ -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) } diff --git a/internal/cli/config/init.go b/internal/cli/config/init.go index afb05f74279..56c60e8adf7 100644 --- a/internal/cli/config/init.go +++ b/internal/cli/config/init.go @@ -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.Init("") configuration.BindFlags(cmd, newSettings) for _, url := range newSettings.GetStringSlice("board_manager.additional_urls") { diff --git a/internal/cli/config/remove.go b/internal/cli/config/remove.go index d7870172b0c..efbd416913a 100644 --- a/internal/cli/config/remove.go +++ b/internal/cli/config/remove.go @@ -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) } } diff --git a/internal/cli/config/set.go b/internal/cli/config/set.go index 8fd002277da..10fb9e6f239 100644 --- a/internal/cli/config/set.go +++ b/internal/cli/config/set.go @@ -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) } } diff --git a/internal/cli/configuration/configuration.go b/internal/cli/configuration/configuration.go index e251b80c514..d3645c62c09 100644 --- a/internal/cli/configuration/configuration.go +++ b/internal/cli/configuration/configuration.go @@ -29,17 +29,19 @@ 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 +} + // 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() + settings := &Settings{viper.New()} SetDefaults(settings) // Set config name and config path @@ -70,7 +72,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")) diff --git a/internal/cli/configuration/defaults.go b/internal/cli/configuration/defaults.go index be1a0088a62..c983310a746 100644 --- a/internal/cli/configuration/defaults.go +++ b/internal/cli/configuration/defaults.go @@ -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") diff --git a/internal/cli/configuration/directories.go b/internal/cli/configuration/directories.go index 27669e9017c..0fb9a9d19e5 100644 --- a/internal/cli/configuration/directories.go +++ b/internal/cli/configuration/directories.go @@ -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")) } diff --git a/internal/cli/configuration/network.go b/internal/cli/configuration/network.go index 793c67de155..c88b974c10d 100644 --- a/internal/cli/configuration/network.go +++ b/internal/cli/configuration/network.go @@ -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 +} diff --git a/internal/arduino/httpclient/httpclient_test.go b/internal/cli/configuration/network_test.go similarity index 79% rename from internal/arduino/httpclient/httpclient_test.go rename to internal/cli/configuration/network_test.go index a2dbc61c291..6d6218c8f5d 100644 --- a/internal/arduino/httpclient/httpclient_test.go +++ b/internal/cli/configuration/network_test.go @@ -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) diff --git a/internal/cli/core/core.go b/internal/cli/core/core.go index ac337df6c9e..e8e3c60220b 100644 --- a/internal/cli/core/core.go +++ b/internal/cli/core/core.go @@ -18,6 +18,7 @@ package core 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 `core` command -func NewCommand(srv rpc.ArduinoCoreServiceServer) *cobra.Command { +func NewCommand(srv rpc.ArduinoCoreServiceServer, defaultSettings *configuration.Settings) *cobra.Command { coreCommand := &cobra.Command{ Use: "core", Short: tr("Arduino core operations."), @@ -40,7 +41,7 @@ func NewCommand(srv rpc.ArduinoCoreServiceServer) *cobra.Command { coreCommand.AddCommand(initUpdateIndexCommand(srv)) coreCommand.AddCommand(initUpgradeCommand(srv)) coreCommand.AddCommand(initUninstallCommand(srv)) - coreCommand.AddCommand(initSearchCommand(srv)) + coreCommand.AddCommand(initSearchCommand(srv, defaultSettings)) return coreCommand } diff --git a/internal/cli/core/search.go b/internal/cli/core/search.go index c2d2690bcd4..164fcd4d4f2 100644 --- a/internal/cli/core/search.go +++ b/internal/cli/core/search.go @@ -32,12 +32,15 @@ import ( "github.com/arduino/arduino-cli/internal/cli/feedback/table" "github.com/arduino/arduino-cli/internal/cli/instance" 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" ) -func initSearchCommand(srv rpc.ArduinoCoreServiceServer) *cobra.Command { +func initSearchCommand(srv rpc.ArduinoCoreServiceServer, defaultSettings *configuration.Settings) *cobra.Command { var allVersions bool + indexDir := configuration.DataDir(defaultSettings) + additionalURLs := defaultSettings.GetStringSlice("board_manager.additional_urls") searchCommand := &cobra.Command{ Use: fmt.Sprintf("search <%s...>", tr("keywords")), Short: tr("Search for a core in Boards Manager."), @@ -45,7 +48,7 @@ func initSearchCommand(srv rpc.ArduinoCoreServiceServer) *cobra.Command { Example: " " + os.Args[0] + " core search MKRZero -a -v", Args: cobra.ArbitraryArgs, Run: func(cmd *cobra.Command, args []string) { - runSearchCommand(srv, args, allVersions) + runSearchCommand(srv, args, allVersions, indexDir, additionalURLs) }, } searchCommand.Flags().BoolVarP(&allVersions, "all", "a", false, tr("Show all available core versions.")) @@ -56,11 +59,11 @@ func initSearchCommand(srv rpc.ArduinoCoreServiceServer) *cobra.Command { // indexUpdateInterval specifies the time threshold over which indexes are updated const indexUpdateInterval = "24h" -func runSearchCommand(srv rpc.ArduinoCoreServiceServer, args []string, allVersions bool) { +func runSearchCommand(srv rpc.ArduinoCoreServiceServer, args []string, allVersions bool, indexDir *paths.Path, additionalURLs []string) { ctx := context.Background() inst := instance.CreateAndInit(ctx, srv) - if indexesNeedUpdating(indexUpdateInterval) { + if indexesNeedUpdating(indexDir, additionalURLs, indexUpdateInterval) { stream := commands.UpdateIndexStreamResponseToCallbackFunction(ctx, feedback.ProgressBar()) err := srv.UpdateIndex(&rpc.UpdateIndexRequest{Instance: inst}, stream) if err != nil { @@ -141,9 +144,7 @@ func (sr searchResults) String() string { // used to update the indexes, if the duration is not valid a default // of 24 hours is used. // Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h". -func indexesNeedUpdating(duration string) bool { - indexpath := configuration.DataDir(configuration.Settings) - +func indexesNeedUpdating(indexDir *paths.Path, additionalURLs []string, duration string) bool { now := time.Now() modTimeThreshold, err := time.ParseDuration(duration) if err != nil { @@ -151,7 +152,7 @@ func indexesNeedUpdating(duration string) bool { } urls := []string{globals.DefaultIndexURL} - urls = append(urls, configuration.Settings.GetStringSlice("board_manager.additional_urls")...) + urls = append(urls, additionalURLs...) for _, u := range urls { URL, err := utils.URLParse(u) if err != nil { @@ -174,7 +175,7 @@ func indexesNeedUpdating(duration string) bool { indexFileName = strings.TrimSuffix(indexFileName, ".sig") indexFileName = strings.TrimSuffix(indexFileName, ".json") // and obtain package_index.json as result - coreIndexPath := indexpath.Join(indexFileName + ".json") + coreIndexPath := indexDir.Join(indexFileName + ".json") if coreIndexPath.NotExist() { return true } diff --git a/internal/cli/daemon/daemon.go b/internal/cli/daemon/daemon.go index 2422a03c2c2..8e719400eaf 100644 --- a/internal/cli/daemon/daemon.go +++ b/internal/cli/daemon/daemon.go @@ -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 { diff --git a/internal/cli/lib/install.go b/internal/cli/lib/install.go index dce521eb6d3..d8aafc8bd1a 100644 --- a/internal/cli/lib/install.go +++ b/internal/cli/lib/install.go @@ -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 { diff --git a/internal/cli/lib/lib.go b/internal/cli/lib/lib.go index deab795b9f5..35cd5286139 100644 --- a/internal/cli/lib/lib.go +++ b/internal/cli/lib/lib.go @@ -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,10 +38,10 @@ 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)) + libCommand.AddCommand(initSearchCommand(srv, defaultSettings)) libCommand.AddCommand(initUninstallCommand(srv)) libCommand.AddCommand(initUpgradeCommand(srv)) libCommand.AddCommand(initUpdateIndexCommand(srv)) diff --git a/internal/cli/lib/search.go b/internal/cli/lib/search.go index 9bb14709df9..31f46ed58cc 100644 --- a/internal/cli/lib/search.go +++ b/internal/cli/lib/search.go @@ -33,7 +33,7 @@ import ( "github.com/spf13/cobra" ) -func initSearchCommand(srv rpc.ArduinoCoreServiceServer) *cobra.Command { +func initSearchCommand(srv rpc.ArduinoCoreServiceServer, defaultSettings *configuration.Settings) *cobra.Command { var namesOnly bool var omitReleasesDetails bool searchCommand := &cobra.Command{ @@ -92,7 +92,8 @@ In addition to the fields listed above, QV terms can use these qualifiers: " " + os.Args[0] + " lib search dependencies=IRremote # " + tr("libraries that depend only on \"IRremote\"") + "\n", Args: cobra.ArbitraryArgs, Run: func(cmd *cobra.Command, args []string) { - runSearchCommand(srv, args, namesOnly, omitReleasesDetails) + indexDir := paths.New(defaultSettings.GetString("directories.Data")) + runSearchCommand(srv, args, namesOnly, omitReleasesDetails, indexDir) }, } searchCommand.Flags().BoolVar(&namesOnly, "names", false, tr("Show library names only.")) @@ -103,13 +104,13 @@ In addition to the fields listed above, QV terms can use these qualifiers: // indexUpdateInterval specifies the time threshold over which indexes are updated const indexUpdateInterval = 60 * time.Minute -func runSearchCommand(srv rpc.ArduinoCoreServiceServer, args []string, namesOnly bool, omitReleasesDetails bool) { +func runSearchCommand(srv rpc.ArduinoCoreServiceServer, args []string, namesOnly bool, omitReleasesDetails bool, indexDir *paths.Path) { ctx := context.Background() inst := instance.CreateAndInit(ctx, srv) logrus.Info("Executing `arduino-cli lib search`") - if indexNeedsUpdating(indexUpdateInterval) { + if indexNeedsUpdating(indexUpdateInterval, indexDir) { req := &rpc.UpdateLibrariesIndexRequest{Instance: inst} stream := commands.UpdateLibrariesIndexStreamResponseToCallbackFunction(ctx, feedback.ProgressBar()) if err := srv.UpdateLibrariesIndex(req, stream); err != nil { @@ -221,11 +222,11 @@ func (res librarySearchResult) String() string { } // indexNeedsUpdating returns whether library_index.json needs updating -func indexNeedsUpdating(timeout time.Duration) bool { +// TODO: Make this a gRPC call +func indexNeedsUpdating(timeout time.Duration, indexDir *paths.Path) bool { // Library index path is constant (relative to the data directory). // It does not depend on board manager URLs or any other configuration. - dataDir := configuration.Settings.GetString("directories.Data") - indexPath := paths.New(dataDir).Join("library_index.json") + indexPath := indexDir.Join("library_index.json") // Verify the index file exists and we can read its fstat attrs. if indexPath.NotExist() { return true diff --git a/internal/cli/updater/updater.go b/internal/cli/updater/updater.go index 8a980fb3ad9..2c5c2c0f34b 100644 --- a/internal/cli/updater/updater.go +++ b/internal/cli/updater/updater.go @@ -20,7 +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/i18n" @@ -34,24 +33,27 @@ var tr = i18n.Tr // CheckForUpdate returns the latest available version if greater than // the one running and it makes sense to check for an update, nil in all other cases -func CheckForUpdate(currentVersion *semver.Version) *semver.Version { - if !shouldCheckForUpdate(currentVersion) { +func CheckForUpdate(currentVersion *semver.Version, settings *configuration.Settings) *semver.Version { + if strings.Contains(currentVersion.String(), "git-snapshot") || strings.Contains(currentVersion.String(), "nightly") { + return nil + } + if !settings.GetBool("updater.enable_notification") { + return nil + } + if inventory.Store.IsSet("updater.last_check_time") && time.Since(inventory.Store.GetTime("updater.last_check_time")).Hours() < 24 { + return nil + } + if feedback.IsCI() || !feedback.IsInteractive() || !feedback.HasConsole() { return nil } - return ForceCheckForUpdate(currentVersion) -} - -// ForceCheckForUpdate always returns the latest available version if greater than -// the one running, nil in all other cases -func ForceCheckForUpdate(currentVersion *semver.Version) *semver.Version { defer func() { // Always save the last time we checked for updates at the end inventory.Store.Set("updater.last_check_time", time.Now()) inventory.WriteStore() }() - latestVersion, err := semver.Parse(getLatestRelease()) + latestVersion, err := semver.Parse(getLatestRelease(settings)) if err != nil { return nil } @@ -74,32 +76,10 @@ func NotifyNewVersionIsAvailable(latestVersion string) { feedback.Warning(msg) } -// shouldCheckForUpdate return true if it actually makes sense to check for new updates, -// false in all other cases. -func 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") { - // Don't check if the user disabled the notification - return false - } - - if inventory.Store.IsSet("updater.last_check_time") && time.Since(inventory.Store.GetTime("updater.last_check_time")).Hours() < 24 { - // Checked less than 24 hours ago, let's wait - return false - } - - // Don't check when running on CI or on non interactive consoles - return !feedback.IsCI() && feedback.IsInteractive() && feedback.HasConsole() -} - // 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 getLatestRelease(settings *configuration.Settings) string { + client, err := settings.NewHttpClient() if err != nil { return "" } diff --git a/internal/cli/version/version.go b/internal/cli/version/version.go index 990a4cbd236..1688a79fec6 100644 --- a/internal/cli/version/version.go +++ b/internal/cli/version/version.go @@ -20,6 +20,7 @@ import ( "os" "strings" + "github.com/arduino/arduino-cli/internal/cli/configuration" "github.com/arduino/arduino-cli/internal/cli/feedback" "github.com/arduino/arduino-cli/internal/cli/updater" "github.com/arduino/arduino-cli/internal/i18n" @@ -32,19 +33,21 @@ import ( var tr = i18n.Tr // NewCommand created a new `version` command -func NewCommand() *cobra.Command { +func NewCommand(defaultSettings *configuration.Settings) *cobra.Command { versionCommand := &cobra.Command{ Use: "version", Short: tr("Shows version number of Arduino CLI."), Long: tr("Shows the version number of Arduino CLI which is installed on your system."), Example: " " + os.Args[0] + " version", Args: cobra.NoArgs, - Run: runVersionCommand, + Run: func(cmd *cobra.Command, args []string) { + runVersionCommand(defaultSettings) + }, } return versionCommand } -func runVersionCommand(cmd *cobra.Command, args []string) { +func runVersionCommand(settings *configuration.Settings) { logrus.Info("Executing `arduino-cli version`") info := version.VersionInfo @@ -59,7 +62,7 @@ func runVersionCommand(cmd *cobra.Command, args []string) { if err != nil { feedback.Fatal(fmt.Sprintf("Error parsing current version: %s", err), feedback.ErrGeneric) } - latestVersion := updater.CheckForUpdate(currentVersion) + latestVersion := updater.CheckForUpdate(currentVersion, settings) if feedback.GetFormat() != feedback.Text && latestVersion != nil { // Set this only we managed to get the latest version diff --git a/internal/docsgen/main.go b/internal/docsgen/main.go index eb8903507e1..69ac4983de9 100644 --- a/internal/docsgen/main.go +++ b/internal/docsgen/main.go @@ -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 { diff --git a/main.go b/main.go index 8587037fbf1..2255ae626b4 100644 --- a/main.go +++ b/main.go @@ -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) }