diff --git a/arduino/cores/packagemanager/loader.go b/arduino/cores/packagemanager/loader.go index 6d97f8ef66c..7ef76ca943a 100644 --- a/arduino/cores/packagemanager/loader.go +++ b/arduino/cores/packagemanager/loader.go @@ -30,12 +30,12 @@ import ( // LoadHardware read all plaforms from the configured paths func (pm *PackageManager) LoadHardware() error { - dirs := configuration.HardwareDirectories() + dirs := configuration.HardwareDirectories(configuration.Settings) if err := pm.LoadHardwareFromDirectories(dirs); err != nil { return err } - dirs = configuration.BundleToolsDirectories() + dirs = configuration.BundleToolsDirectories(configuration.Settings) return pm.LoadToolsFromBundleDirectories(dirs) } diff --git a/arduino/cores/packagemanager/package_manager_test.go b/arduino/cores/packagemanager/package_manager_test.go index b104e65d74f..c4cb37db891 100644 --- a/arduino/cores/packagemanager/package_manager_test.go +++ b/arduino/cores/packagemanager/package_manager_test.go @@ -26,7 +26,6 @@ import ( "github.com/arduino/arduino-cli/configuration" "github.com/arduino/go-paths-helper" "github.com/arduino/go-properties-orderedmap" - "github.com/spf13/viper" "github.com/stretchr/testify/require" semver "go.bug.st/relaxed-semver" ) @@ -213,11 +212,11 @@ func TestBoardOptionsFunctions(t *testing.T) { func TestFindToolsRequiredForBoard(t *testing.T) { os.Setenv("ARDUINO_DATA_DIR", dataDir1.String()) - configuration.Init("") + configuration.Settings = configuration.Init("") pm := packagemanager.NewPackageManager( dataDir1, - configuration.PackagesDir(), - paths.New(viper.GetString("directories.Downloads")), + configuration.PackagesDir(configuration.Settings), + paths.New(configuration.Settings.GetString("directories.Downloads")), dataDir1, ) diff --git a/cli/cache/clean.go b/cli/cache/clean.go index e8d676f7de9..ecac2048dba 100644 --- a/cli/cache/clean.go +++ b/cli/cache/clean.go @@ -20,9 +20,9 @@ import ( "github.com/arduino/arduino-cli/cli/errorcodes" "github.com/arduino/arduino-cli/cli/feedback" + "github.com/arduino/arduino-cli/configuration" "github.com/sirupsen/logrus" "github.com/spf13/cobra" - "github.com/spf13/viper" ) func initCleanCommand() *cobra.Command { @@ -40,7 +40,7 @@ func initCleanCommand() *cobra.Command { func runCleanCommand(cmd *cobra.Command, args []string) { logrus.Info("Executing `arduino cache clean`") - cachePath := viper.GetString("directories.Downloads") + cachePath := configuration.Settings.GetString("directories.Downloads") err := os.RemoveAll(cachePath) if err != nil { feedback.Errorf("Error cleaning caches: %v", err) diff --git a/cli/cli.go b/cli/cli.go index 7573d97b8fc..ca711c159fc 100644 --- a/cli/cli.go +++ b/cli/cli.go @@ -42,13 +42,13 @@ import ( "github.com/arduino/arduino-cli/cli/upgrade" "github.com/arduino/arduino-cli/cli/upload" "github.com/arduino/arduino-cli/cli/version" + "github.com/arduino/arduino-cli/configuration" "github.com/arduino/arduino-cli/i18n" "github.com/arduino/arduino-cli/inventory" "github.com/mattn/go-colorable" "github.com/rifflock/lfshook" "github.com/sirupsen/logrus" "github.com/spf13/cobra" - "github.com/spf13/viper" ) var ( @@ -99,15 +99,12 @@ func createCliCommandTree(cmd *cobra.Command) { cmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "Print the logs on the standard output.") cmd.PersistentFlags().String("log-level", "", "Messages with this level and above will be logged. Valid levels are: trace, debug, info, warn, error, fatal, panic") - viper.BindPFlag("logging.level", cmd.PersistentFlags().Lookup("log-level")) cmd.PersistentFlags().String("log-file", "", "Path to the file where logs will be written.") - viper.BindPFlag("logging.file", cmd.PersistentFlags().Lookup("log-file")) cmd.PersistentFlags().String("log-format", "", "The output format for the logs, can be {text|json}.") - viper.BindPFlag("logging.format", cmd.PersistentFlags().Lookup("log-format")) cmd.PersistentFlags().StringVar(&outputFormat, "format", "text", "The output format, can be {text|json}.") cmd.PersistentFlags().StringVar(&configFile, "config-file", "", "The custom config file (if not specified the default will be used).") cmd.PersistentFlags().StringSlice("additional-urls", []string{}, "Comma-separated list of additional URLs for the Boards Manager.") - viper.BindPFlag("board_manager.additional_urls", cmd.PersistentFlags().Lookup("additional-urls")) + configuration.BindFlags(cmd, configuration.Settings) } // convert the string passed to the `--log-level` option to the corresponding @@ -136,10 +133,10 @@ func parseFormatString(arg string) (feedback.OutputFormat, bool) { } func preRun(cmd *cobra.Command, args []string) { - configFile := viper.ConfigFileUsed() + configFile := configuration.Settings.ConfigFileUsed() // initialize inventory - inventory.Init(viper.GetString("directories.Data")) + inventory.Init(configuration.Settings.GetString("directories.Data")) // // Prepare logging @@ -157,13 +154,13 @@ func preRun(cmd *cobra.Command, args []string) { } // set the Logger format - logFormat := strings.ToLower(viper.GetString("logging.format")) + logFormat := strings.ToLower(configuration.Settings.GetString("logging.format")) if logFormat == "json" { logrus.SetFormatter(&logrus.JSONFormatter{}) } // should we log to file? - logFile := viper.GetString("logging.file") + logFile := configuration.Settings.GetString("logging.file") if logFile != "" { file, err := os.OpenFile(logFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666) if err != nil { @@ -180,8 +177,8 @@ func preRun(cmd *cobra.Command, args []string) { } // configure logging filter - if lvl, found := toLogLevel(viper.GetString("logging.level")); !found { - feedback.Errorf("Invalid option for --log-level: %s", viper.GetString("logging.level")) + if lvl, found := toLogLevel(configuration.Settings.GetString("logging.level")); !found { + feedback.Errorf("Invalid option for --log-level: %s", configuration.Settings.GetString("logging.level")) os.Exit(errorcodes.ErrBadArgument) } else { logrus.SetLevel(lvl) diff --git a/cli/compile/compile.go b/cli/compile/compile.go index a5c8a78acd5..5e85e4638c1 100644 --- a/cli/compile/compile.go +++ b/cli/compile/compile.go @@ -20,6 +20,7 @@ import ( "os" "github.com/arduino/arduino-cli/cli/feedback" + "github.com/arduino/arduino-cli/configuration" "github.com/arduino/arduino-cli/cli/errorcodes" "github.com/arduino/arduino-cli/cli/instance" @@ -29,7 +30,6 @@ import ( "github.com/arduino/go-paths-helper" "github.com/sirupsen/logrus" "github.com/spf13/cobra" - "github.com/spf13/viper" ) var ( @@ -124,7 +124,7 @@ func run(cmd *cobra.Command, args []string) { Libraries: libraries, OptimizeForDebug: optimizeForDebug, Clean: clean, - }, os.Stdout, os.Stderr, viper.GetString("logging.level") == "debug") + }, os.Stdout, os.Stderr, configuration.Settings.GetString("logging.level") == "debug") if err != nil { feedback.Errorf("Error during build: %v", err) diff --git a/cli/config/dump.go b/cli/config/dump.go index c384a8d48a7..90b20bcaedb 100644 --- a/cli/config/dump.go +++ b/cli/config/dump.go @@ -19,9 +19,9 @@ import ( "os" "github.com/arduino/arduino-cli/cli/feedback" + "github.com/arduino/arduino-cli/configuration" "github.com/sirupsen/logrus" "github.com/spf13/cobra" - "github.com/spf13/viper" "gopkg.in/yaml.v2" ) @@ -59,5 +59,5 @@ func (dr dumpResult) String() string { func runDumpCommand(cmd *cobra.Command, args []string) { logrus.Info("Executing `arduino config dump`") - feedback.PrintResult(dumpResult{viper.AllSettings()}) + feedback.PrintResult(dumpResult{configuration.Settings.AllSettings()}) } diff --git a/cli/config/init.go b/cli/config/init.go index 8d0cbc66559..e9cdf28cb95 100644 --- a/cli/config/init.go +++ b/cli/config/init.go @@ -17,16 +17,21 @@ package config import ( "os" - "path/filepath" "github.com/arduino/arduino-cli/cli/errorcodes" "github.com/arduino/arduino-cli/cli/feedback" + "github.com/arduino/arduino-cli/configuration" + "github.com/arduino/go-paths-helper" "github.com/sirupsen/logrus" "github.com/spf13/cobra" "github.com/spf13/viper" ) -var destDir string +var ( + destDir string + destFile string + overwrite bool +) const defaultFileName = "arduino-cli.yaml" @@ -37,39 +42,71 @@ func initInitCommand() *cobra.Command { Long: "Creates or updates the configuration file in the data directory or custom directory with the current configuration settings.", Example: "" + " # Writes current configuration to the configuration file in the data directory.\n" + - " " + os.Args[0] + " config init", + " " + os.Args[0] + " config init" + + " " + os.Args[0] + " config init --dest-dir /home/user/MyDirectory" + + " " + os.Args[0] + " config init --dest-file /home/user/MyDirectory/my_settings.yaml", Args: cobra.NoArgs, Run: runInitCommand, } initCommand.Flags().StringVar(&destDir, "dest-dir", "", "Sets where to save the configuration file.") + initCommand.Flags().StringVar(&destFile, "dest-file", "", "Sets where to save the configuration file.") + initCommand.Flags().BoolVar(&overwrite, "overwrite", false, "Overwrite existing config file.") return initCommand } func runInitCommand(cmd *cobra.Command, args []string) { - if destDir == "" { - destDir = viper.GetString("directories.Data") + if destFile != "" && destDir != "" { + feedback.Errorf("Can't use both --dest-file and --dest-dir flags at the same time.") + os.Exit(errorcodes.ErrGeneric) } - absPath, err := filepath.Abs(destDir) - if err != nil { - feedback.Errorf("Cannot find absolute path: %v", err) + var configFileAbsPath *paths.Path + var absPath *paths.Path + var err error + + switch { + case destFile != "": + configFileAbsPath, err = paths.New(destFile).Abs() + if err != nil { + feedback.Errorf("Cannot find absolute path: %v", err) + os.Exit(errorcodes.ErrGeneric) + } + + absPath = configFileAbsPath.Parent() + case destDir == "": + destDir = configuration.Settings.GetString("directories.Data") + fallthrough + default: + absPath, err = paths.New(destDir).Abs() + if err != nil { + feedback.Errorf("Cannot find absolute path: %v", err) + os.Exit(errorcodes.ErrGeneric) + } + configFileAbsPath = absPath.Join(defaultFileName) + } + + if !overwrite && configFileAbsPath.Exist() { + feedback.Error("Config file already exists, use --overwrite to discard the existing one.") os.Exit(errorcodes.ErrGeneric) } - configFileAbsPath := filepath.Join(absPath, defaultFileName) logrus.Infof("Writing config file to: %s", absPath) - if err := os.MkdirAll(absPath, os.FileMode(0755)); err != nil { + if err := absPath.MkdirAll(); err != nil { feedback.Errorf("Cannot create config file directory: %v", err) os.Exit(errorcodes.ErrGeneric) } - if err := viper.WriteConfigAs(configFileAbsPath); err != nil { + newSettings := viper.New() + configuration.SetDefaults(newSettings) + configuration.BindFlags(cmd, newSettings) + + if err := newSettings.WriteConfigAs(configFileAbsPath.String()); err != nil { feedback.Errorf("Cannot create config file: %v", err) os.Exit(errorcodes.ErrGeneric) } - msg := "Config file written to: " + configFileAbsPath + msg := "Config file written to: " + configFileAbsPath.String() logrus.Info(msg) feedback.Print(msg) } diff --git a/cli/daemon/daemon.go b/cli/daemon/daemon.go index 1dbd6a70c66..52cea847c23 100644 --- a/cli/daemon/daemon.go +++ b/cli/daemon/daemon.go @@ -28,6 +28,7 @@ import ( "github.com/arduino/arduino-cli/cli/feedback" "github.com/arduino/arduino-cli/cli/globals" "github.com/arduino/arduino-cli/commands/daemon" + "github.com/arduino/arduino-cli/configuration" srv_commands "github.com/arduino/arduino-cli/rpc/commands" srv_debug "github.com/arduino/arduino-cli/rpc/debug" srv_monitor "github.com/arduino/arduino-cli/rpc/monitor" @@ -36,7 +37,6 @@ import ( "github.com/segmentio/stats/v4" "github.com/sirupsen/logrus" "github.com/spf13/cobra" - "github.com/spf13/viper" "google.golang.org/grpc" ) @@ -44,14 +44,14 @@ import ( func NewCommand() *cobra.Command { cmd := &cobra.Command{ Use: "daemon", - Short: fmt.Sprintf("Run as a daemon on port %s", viper.GetString("daemon.port")), + Short: fmt.Sprintf("Run as a daemon on port %s", configuration.Settings.GetString("daemon.port")), Long: "Running as a daemon the initialization of cores and libraries is done only once.", Example: " " + os.Args[0] + " daemon", Args: cobra.NoArgs, Run: runDaemonCommand, } cmd.PersistentFlags().String("port", "", "The TCP port the daemon will listen to") - viper.BindPFlag("daemon.port", cmd.PersistentFlags().Lookup("port")) + configuration.Settings.BindPFlag("daemon.port", cmd.PersistentFlags().Lookup("port")) cmd.Flags().BoolVar(&daemonize, "daemonize", false, "Do not terminate daemon process if the parent process dies") return cmd } @@ -60,16 +60,16 @@ var daemonize bool func runDaemonCommand(cmd *cobra.Command, args []string) { - if viper.GetBool("telemetry.enabled") { + if configuration.Settings.GetBool("telemetry.enabled") { telemetry.Activate("daemon") stats.Incr("daemon", stats.T("success", "true")) defer stats.Flush() } - port := viper.GetString("daemon.port") + port := configuration.Settings.GetString("daemon.port") s := grpc.NewServer() // Set specific user-agent for the daemon - viper.Set("network.user_agent_ext", "daemon") + configuration.Settings.Set("network.user_agent_ext", "daemon") // register the commands service srv_commands.RegisterArduinoCoreServer(s, &daemon.ArduinoCoreServerImpl{ diff --git a/commands/board/list_test.go b/commands/board/list_test.go index 5b8347c8df8..94384419ffa 100644 --- a/commands/board/list_test.go +++ b/commands/board/list_test.go @@ -22,10 +22,15 @@ import ( "testing" "github.com/arduino/arduino-cli/commands" + "github.com/arduino/arduino-cli/configuration" "github.com/arduino/go-properties-orderedmap" "github.com/stretchr/testify/require" ) +func init() { + configuration.Settings = configuration.Init("") +} + func TestGetByVidPid(t *testing.T) { ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, ` diff --git a/commands/compile/compile.go b/commands/compile/compile.go index 128aa9412ec..efc164d97c6 100644 --- a/commands/compile/compile.go +++ b/commands/compile/compile.go @@ -39,7 +39,6 @@ import ( "github.com/pkg/errors" "github.com/segmentio/stats/v4" "github.com/sirupsen/logrus" - "github.com/spf13/viper" ) // Compile FIXMEDOC @@ -122,11 +121,11 @@ func Compile(ctx context.Context, req *rpc.CompileReq, outStream, errStream io.W builderCtx.SketchLocation = sketch.FullPath // FIXME: This will be redundant when arduino-builder will be part of the cli - builderCtx.HardwareDirs = configuration.HardwareDirectories() - builderCtx.BuiltInToolsDirs = configuration.BundleToolsDirectories() + builderCtx.HardwareDirs = configuration.HardwareDirectories(configuration.Settings) + builderCtx.BuiltInToolsDirs = configuration.BundleToolsDirectories(configuration.Settings) builderCtx.OtherLibrariesDirs = paths.NewPathList(req.GetLibraries()...) - builderCtx.OtherLibrariesDirs.Add(configuration.LibrariesDir()) + builderCtx.OtherLibrariesDirs.Add(configuration.LibrariesDir(configuration.Settings)) if req.GetBuildPath() != "" { builderCtx.BuildPath = paths.New(req.GetBuildPath()) @@ -168,7 +167,7 @@ func Compile(ctx context.Context, req *rpc.CompileReq, outStream, errStream io.W builderCtx.ArduinoAPIVersion = "10607" // Check if Arduino IDE is installed and get it's libraries location. - dataDir := paths.New(viper.GetString("directories.Data")) + dataDir := paths.New(configuration.Settings.GetString("directories.Data")) preferencesTxt := dataDir.Join("preferences.txt") ideProperties, err := properties.LoadFromPath(preferencesTxt) if err == nil { diff --git a/commands/core/search_test.go b/commands/core/search_test.go index e890e65860f..af17ede9e79 100644 --- a/commands/core/search_test.go +++ b/commands/core/search_test.go @@ -46,7 +46,7 @@ func TestPlatformSearch(t *testing.T) { err := paths.New("testdata").Join("package_index.json").CopyTo(dataDir.Join("package_index.json")) require.Nil(t, err) - configuration.Init(paths.TempDir().Join("test", "arduino-cli.yaml").String()) + configuration.Settings = configuration.Init(paths.TempDir().Join("test", "arduino-cli.yaml").String()) inst, err := instance.CreateInstance() require.Nil(t, err) diff --git a/commands/daemon/settings.go b/commands/daemon/settings.go index 8813802efdc..f88826c323d 100644 --- a/commands/daemon/settings.go +++ b/commands/daemon/settings.go @@ -20,8 +20,8 @@ import ( "encoding/json" "errors" + "github.com/arduino/arduino-cli/configuration" rpc "github.com/arduino/arduino-cli/rpc/settings" - "github.com/spf13/viper" ) // SettingsService implements the `Settings` service @@ -30,7 +30,7 @@ type SettingsService struct{} // GetAll returns a message with a string field containing all the settings // currently in use, marshalled in JSON format. func (s *SettingsService) GetAll(ctx context.Context, req *rpc.GetAllRequest) (*rpc.RawData, error) { - b, err := json.Marshal(viper.AllSettings()) + b, err := json.Marshal(configuration.Settings.AllSettings()) if err == nil { return &rpc.RawData{ JsonData: string(b), @@ -47,7 +47,7 @@ func (s *SettingsService) Merge(ctx context.Context, req *rpc.RawData) (*rpc.Mer return nil, err } - if err := viper.MergeConfigMap(toMerge); err != nil { + if err := configuration.Settings.MergeConfigMap(toMerge); err != nil { return nil, err } @@ -61,11 +61,11 @@ func (s *SettingsService) GetValue(ctx context.Context, req *rpc.GetValueRequest key := req.GetKey() value := &rpc.Value{} - if !viper.InConfig(key) { + if !configuration.Settings.InConfig(key) { return nil, errors.New("key not found in settings") } - b, err := json.Marshal(viper.Get(key)) + b, err := json.Marshal(configuration.Settings.Get(key)) if err == nil { value.Key = key value.JsonData = string(b) @@ -81,7 +81,7 @@ func (s *SettingsService) SetValue(ctx context.Context, val *rpc.Value) (*rpc.Se err := json.Unmarshal([]byte(val.GetJsonData()), &value) if err == nil { - viper.Set(key, value) + configuration.Settings.Set(key, value) } return &rpc.SetValueResponse{}, err diff --git a/commands/daemon/settings_test.go b/commands/daemon/settings_test.go index 4bc7a11f86f..b64d2b713a4 100644 --- a/commands/daemon/settings_test.go +++ b/commands/daemon/settings_test.go @@ -18,10 +18,9 @@ package daemon import ( "context" "encoding/json" + "path/filepath" "testing" - "github.com/spf13/viper" - "github.com/arduino/arduino-cli/configuration" rpc "github.com/arduino/arduino-cli/rpc/settings" "github.com/stretchr/testify/require" @@ -30,19 +29,18 @@ import ( var svc = SettingsService{} func init() { - configuration.Init("testdata") + configuration.Settings = configuration.Init(filepath.Join("testdata", "arduino-cli.yaml")) } func reset() { - viper.Reset() - configuration.Init("testdata") + configuration.Settings = configuration.Init(filepath.Join("testdata", "arduino-cli.yaml")) } func TestGetAll(t *testing.T) { resp, err := svc.GetAll(context.Background(), &rpc.GetAllRequest{}) require.Nil(t, err) - content, err := json.Marshal(viper.AllSettings()) + content, err := json.Marshal(configuration.Settings.AllSettings()) require.Nil(t, err) require.Equal(t, string(content), resp.GetJsonData()) @@ -53,8 +51,8 @@ func TestMerge(t *testing.T) { _, err := svc.Merge(context.Background(), &rpc.RawData{JsonData: bulkSettings}) require.Nil(t, err) - require.Equal(t, "420", viper.GetString("daemon.port")) - require.Equal(t, "bar", viper.GetString("foo")) + require.Equal(t, "420", configuration.Settings.GetString("daemon.port")) + require.Equal(t, "bar", configuration.Settings.GetString("foo")) reset() } @@ -80,5 +78,5 @@ func TestSetValue(t *testing.T) { } _, err := svc.SetValue(context.Background(), val) require.Nil(t, err) - require.Equal(t, "bar", viper.GetString("foo")) + require.Equal(t, "bar", configuration.Settings.GetString("foo")) } diff --git a/commands/instances.go b/commands/instances.go index f9f61747ef2..1dc9f7bb193 100644 --- a/commands/instances.go +++ b/commands/instances.go @@ -36,7 +36,6 @@ import ( rpc "github.com/arduino/arduino-cli/rpc/commands" paths "github.com/arduino/go-paths-helper" "github.com/sirupsen/logrus" - "github.com/spf13/viper" "go.bug.st/downloader/v2" ) @@ -200,9 +199,9 @@ func UpdateIndex(ctx context.Context, req *rpc.UpdateIndexReq, downloadCB Downlo return nil, fmt.Errorf("invalid handle") } - indexpath := paths.New(viper.GetString("directories.Data")) + indexpath := paths.New(configuration.Settings.GetString("directories.Data")) urls := []string{globals.DefaultIndexURL} - urls = append(urls, viper.GetStringSlice("board_manager.additional_urls")...) + urls = append(urls, configuration.Settings.GetStringSlice("board_manager.additional_urls")...) for _, u := range urls { URL, err := url.Parse(u) if err != nil { @@ -614,7 +613,7 @@ func createInstance(ctx context.Context, getLibOnly bool) (*createInstanceResult res := &createInstanceResult{} // setup downloads directory - downloadsDir := paths.New(viper.GetString("directories.Downloads")) + downloadsDir := paths.New(configuration.Settings.GetString("directories.Downloads")) if downloadsDir.NotExist() { err := downloadsDir.MkdirAll() if err != nil { @@ -623,8 +622,8 @@ func createInstance(ctx context.Context, getLibOnly bool) (*createInstanceResult } // setup data directory - dataDir := paths.New(viper.GetString("directories.Data")) - packagesDir := configuration.PackagesDir() + dataDir := paths.New(configuration.Settings.GetString("directories.Data")) + packagesDir := configuration.PackagesDir(configuration.Settings) if packagesDir.NotExist() { err := packagesDir.MkdirAll() if err != nil { @@ -633,11 +632,11 @@ func createInstance(ctx context.Context, getLibOnly bool) (*createInstanceResult } if !getLibOnly { - res.Pm = packagemanager.NewPackageManager(dataDir, configuration.PackagesDir(), + res.Pm = packagemanager.NewPackageManager(dataDir, configuration.PackagesDir(configuration.Settings), downloadsDir, dataDir.Join("tmp")) urls := []string{globals.DefaultIndexURL} - urls = append(urls, viper.GetStringSlice("board_manager.additional_urls")...) + urls = append(urls, configuration.Settings.GetStringSlice("board_manager.additional_urls")...) for _, u := range urls { URL, err := url.Parse(u) if err != nil { @@ -664,12 +663,12 @@ func createInstance(ctx context.Context, getLibOnly bool) (*createInstanceResult res.Lm = librariesmanager.NewLibraryManager(dataDir, downloadsDir) // Add IDE builtin libraries dir - if bundledLibsDir := configuration.IDEBundledLibrariesDir(); bundledLibsDir != nil { + if bundledLibsDir := configuration.IDEBundledLibrariesDir(configuration.Settings); bundledLibsDir != nil { res.Lm.AddLibrariesDir(bundledLibsDir, libraries.IDEBuiltIn) } // Add user libraries dir - libDir := configuration.LibrariesDir() + libDir := configuration.LibrariesDir(configuration.Settings) res.Lm.AddLibrariesDir(libDir, libraries.User) // Add libraries dirs from installed platforms diff --git a/configuration/configuration.go b/configuration/configuration.go index 02924d3de81..01f424c586d 100644 --- a/configuration/configuration.go +++ b/configuration/configuration.go @@ -25,58 +25,57 @@ import ( paths "github.com/arduino/go-paths-helper" "github.com/arduino/go-win32-utils" "github.com/sirupsen/logrus" + "github.com/spf13/cobra" jww "github.com/spf13/jwalterweatherman" "github.com/spf13/viper" ) +// Settings is a global instance of viper holding configurations for the CLI and the gRPC consumers +var Settings *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(configPath string) { - // Config file metadata +func Init(configFile string) *viper.Viper { jww.SetStdoutThreshold(jww.LevelFatal) - viper.SetConfigName("arduino-cli") - - // Get default data path if none was provided - if configPath == "" { - configPath = getDefaultArduinoDataDir() - } - - // Add paths where to search for a config file - viper.AddConfigPath(configPath) - // Bind env vars - viper.SetEnvPrefix("ARDUINO") - viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) - viper.AutomaticEnv() - - // Bind env aliases to keep backward compatibility - viper.BindEnv("directories.User", "ARDUINO_SKETCHBOOK_DIR") - viper.BindEnv("directories.Downloads", "ARDUINO_DOWNLOADS_DIR") - viper.BindEnv("directories.Data", "ARDUINO_DATA_DIR") + // Create a new viper instance with default values for all the settings + settings := viper.New() + SetDefaults(settings) + + // Set config name and config path + if configFilePath := paths.New(configFile); configFilePath != nil { + settings.SetConfigName(strings.TrimSuffix(configFilePath.Base(), configFilePath.Ext())) + settings.AddConfigPath(configFilePath.Parent().String()) + } else { + configDir := settings.GetString("directories.Data") + // Get default data path if none was provided + if configDir == "" { + configDir = getDefaultArduinoDataDir() + } - // Early access directories.Data and directories.User in case - // those were set through env vars or cli flags - dataDir := viper.GetString("directories.Data") - if dataDir == "" { - dataDir = getDefaultArduinoDataDir() - } - userDir := viper.GetString("directories.User") - if userDir == "" { - userDir = getDefaultUserDir() + settings.SetConfigName("arduino-cli") + settings.AddConfigPath(configDir) } - // Set default values for all the settings - setDefaults(dataDir, userDir) - // Attempt to read config file - if err := viper.ReadInConfig(); err != nil { + if err := settings.ReadInConfig(); err != nil { // ConfigFileNotFoundError is acceptable, anything else // should be reported to the user if _, ok := err.(viper.ConfigFileNotFoundError); !ok { feedback.Errorf("Error reading config file: %v", err) } } + + return settings +} + +// BindFlags creates all the flags binding between the cobra Command and the instance of viper +func BindFlags(cmd *cobra.Command, settings *viper.Viper) { + settings.BindPFlag("logging.level", cmd.Flag("log-level")) + settings.BindPFlag("logging.file", cmd.Flag("log-file")) + settings.BindPFlag("logging.format", cmd.Flag("log-format")) + settings.BindPFlag("board_manager.additional_urls", cmd.Flag("additional-urls")) } // getDefaultArduinoDataDir returns the full path to the default arduino folder @@ -130,14 +129,14 @@ func getDefaultUserDir() string { } // IsBundledInDesktopIDE returns true if the CLI is bundled with the Arduino IDE. -func IsBundledInDesktopIDE() bool { +func IsBundledInDesktopIDE(settings *viper.Viper) bool { // value is cached the first time we run the check - if viper.IsSet("IDE.Bundled") { - return viper.GetBool("IDE.Bundled") + if settings.IsSet("IDE.Bundled") { + return settings.GetBool("IDE.Bundled") } - viper.Set("IDE.Bundled", false) - viper.Set("IDE.Portable", false) + settings.Set("IDE.Bundled", false) + settings.Set("IDE.Portable", false) logrus.Info("Checking if CLI is Bundled into the IDE") executable, err := os.Executable() @@ -172,71 +171,46 @@ func IsBundledInDesktopIDE() bool { logrus.Info("The CLI is bundled in the Arduino IDE") // Persist IDE-related config settings - viper.Set("IDE.Bundled", true) - viper.Set("IDE.Directory", ideDir) + settings.Set("IDE.Bundled", true) + settings.Set("IDE.Directory", ideDir) // Check whether this is a portable install if ideDir.Join("portable").Exist() { logrus.Info("The IDE installation is 'portable'") - viper.Set("IDE.Portable", true) + settings.Set("IDE.Portable", true) } return true } -// FindConfigFile returns the config file path using the argument '--config-file' if specified or via the current working dir -func FindConfigFile() string { - - configFile := "" - for i, arg := range os.Args { - // 0 --config-file ss +// FindConfigFileInArgsOrWorkingDirectory returns the config file path using the +// argument '--config-file' (if specified) or looking in the current working dir +func FindConfigFileInArgsOrWorkingDirectory(args []string) string { + // Look for '--config-file' argument + for i, arg := range args { if arg == "--config-file" { - if len(os.Args) > i+1 { - configFile = os.Args[i+1] + if len(args) > i+1 { + return args[i+1] } } } - if configFile != "" { - if fi, err := os.Stat(configFile); err == nil { - if fi.IsDir() { - return configFile - } - return filepath.Dir(configFile) - } + // Look into current working directory + if cwd, err := paths.Getwd(); err != nil { + return "" + } else if configFile := searchConfigTree(cwd); configFile != nil { + return configFile.Join("arduino-cli.yaml").String() } - - return searchCwdForConfig() + return "" } -func searchConfigTree(cwd string) string { - +func searchConfigTree(cwd *paths.Path) *paths.Path { // go back up to root and search for the config file - for { - if _, err := os.Stat(filepath.Join(cwd, "arduino-cli.yaml")); err == nil { - // config file found - return cwd - } else if os.IsNotExist(err) { - // no config file found - next := filepath.Dir(cwd) - if next == cwd { - return "" - } - cwd = next - } else { - // some error we can't handle happened - return "" + for _, path := range cwd.Parents() { + if path.Join("arduino-cli.yaml").Exist() { + return path } } -} - -func searchCwdForConfig() string { - cwd, err := os.Getwd() - - if err != nil { - return "" - } - - return searchConfigTree(cwd) + return nil } diff --git a/configuration/configuration_test.go b/configuration/configuration_test.go index 820d19c2f50..45ee9261535 100644 --- a/configuration/configuration_test.go +++ b/configuration/configuration_test.go @@ -22,6 +22,7 @@ import ( "path/filepath" "testing" + paths "github.com/arduino/go-paths-helper" "github.com/stretchr/testify/require" ) @@ -30,12 +31,18 @@ func tmpDirOrDie() string { if err != nil { panic(fmt.Sprintf("error creating tmp dir: %v", err)) } + // Symlinks are evaluated becase the temp folder on Mac OS is inside /var, it's not writable + // and is a symlink to /private/var, we want the full path so we do this + dir, err = filepath.EvalSymlinks(dir) + if err != nil { + panic(fmt.Sprintf("error evaluating tmp dir symlink: %v", err)) + } return dir } func TestSearchConfigTreeNotFound(t *testing.T) { tmp := tmpDirOrDie() - require.Empty(t, searchConfigTree(tmp)) + require.Empty(t, searchConfigTree(paths.New(tmp))) } func TestSearchConfigTreeSameFolder(t *testing.T) { @@ -43,7 +50,7 @@ func TestSearchConfigTreeSameFolder(t *testing.T) { defer os.RemoveAll(tmp) _, err := os.Create(filepath.Join(tmp, "arduino-cli.yaml")) require.Nil(t, err) - require.Equal(t, tmp, searchConfigTree(tmp)) + require.Equal(t, tmp, searchConfigTree(paths.New(tmp)).String()) } func TestSearchConfigTreeInParent(t *testing.T) { @@ -54,10 +61,10 @@ func TestSearchConfigTreeInParent(t *testing.T) { require.Nil(t, err) _, err = os.Create(filepath.Join(tmp, "arduino-cli.yaml")) require.Nil(t, err) - require.Equal(t, tmp, searchConfigTree(target)) + require.Equal(t, tmp, searchConfigTree(paths.New(target)).String()) } -var result string +var result *paths.Path func BenchmarkSearchConfigTree(b *testing.B) { tmp := tmpDirOrDie() @@ -65,9 +72,67 @@ func BenchmarkSearchConfigTree(b *testing.B) { target := filepath.Join(tmp, "foo", "bar", "baz") os.MkdirAll(target, os.ModePerm) - var s string + var s *paths.Path for n := 0; n < b.N; n++ { - s = searchConfigTree(target) + s = searchConfigTree(paths.New(target)) } result = s } + +func TestInit(t *testing.T) { + tmp := tmpDirOrDie() + defer os.RemoveAll(tmp) + settings := Init(filepath.Join(tmp, "arduino-cli.yaml")) + require.NotNil(t, settings) + + require.Equal(t, "info", settings.GetString("logging.level")) + require.Equal(t, "text", settings.GetString("logging.format")) + + require.Empty(t, settings.GetStringSlice("board_manager.additional_urls")) + + require.NotEmpty(t, settings.GetString("directories.Data")) + require.NotEmpty(t, settings.GetString("directories.Downloads")) + require.NotEmpty(t, settings.GetString("directories.User")) + + require.Equal(t, "50051", settings.GetString("daemon.port")) + + require.Equal(t, true, settings.GetBool("telemetry.enabled")) + require.Equal(t, ":9090", settings.GetString("telemetry.addr")) +} + +func TestFindConfigFile(t *testing.T) { + configFile := FindConfigFileInArgsOrWorkingDirectory([]string{"--config-file"}) + require.Equal(t, "", configFile) + + configFile = FindConfigFileInArgsOrWorkingDirectory([]string{"--config-file", "some/path/to/config"}) + require.Equal(t, "some/path/to/config", configFile) + + configFile = FindConfigFileInArgsOrWorkingDirectory([]string{"--config-file", "some/path/to/config/arduino-cli.yaml"}) + require.Equal(t, "some/path/to/config/arduino-cli.yaml", configFile) + + configFile = FindConfigFileInArgsOrWorkingDirectory([]string{}) + require.Equal(t, "", configFile) + + // Create temporary directories + tmp := tmpDirOrDie() + defer os.RemoveAll(tmp) + target := filepath.Join(tmp, "foo", "bar", "baz") + os.MkdirAll(target, os.ModePerm) + require.Nil(t, os.Chdir(target)) + + // Create a config file + f, err := os.Create(filepath.Join(target, "..", "..", "arduino-cli.yaml")) + require.Nil(t, err) + f.Close() + + configFile = FindConfigFileInArgsOrWorkingDirectory([]string{}) + require.Equal(t, filepath.Join(tmp, "foo", "arduino-cli.yaml"), configFile) + + // Create another config file + f, err = os.Create(filepath.Join(target, "arduino-cli.yaml")) + require.Nil(t, err) + f.Close() + + configFile = FindConfigFileInArgsOrWorkingDirectory([]string{}) + require.Equal(t, filepath.Join(target, "arduino-cli.yaml"), configFile) +} diff --git a/configuration/defaults.go b/configuration/defaults.go index 5f8c46db110..61e495e884e 100644 --- a/configuration/defaults.go +++ b/configuration/defaults.go @@ -17,27 +17,39 @@ package configuration import ( "path/filepath" + "strings" "github.com/spf13/viper" ) -func setDefaults(dataDir, userDir string) { +// SetDefaults sets the default values for certain keys +func SetDefaults(settings *viper.Viper) { // logging - viper.SetDefault("logging.level", "info") - viper.SetDefault("logging.format", "text") + settings.SetDefault("logging.level", "info") + settings.SetDefault("logging.format", "text") // Boards Manager - viper.SetDefault("board_manager.additional_urls", []string{}) + settings.SetDefault("board_manager.additional_urls", []string{}) // arduino directories - viper.SetDefault("directories.Data", dataDir) - viper.SetDefault("directories.Downloads", filepath.Join(dataDir, "staging")) - viper.SetDefault("directories.User", userDir) + settings.SetDefault("directories.Data", getDefaultArduinoDataDir()) + settings.SetDefault("directories.Downloads", filepath.Join(getDefaultArduinoDataDir(), "staging")) + settings.SetDefault("directories.User", getDefaultUserDir()) // daemon settings - viper.SetDefault("daemon.port", "50051") + settings.SetDefault("daemon.port", "50051") //telemetry settings - viper.SetDefault("telemetry.enabled", true) - viper.SetDefault("telemetry.addr", ":9090") + settings.SetDefault("telemetry.enabled", true) + settings.SetDefault("telemetry.addr", ":9090") + + // Bind env vars + settings.SetEnvPrefix("ARDUINO") + settings.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) + settings.AutomaticEnv() + + // Bind env aliases to keep backward compatibility + settings.BindEnv("directories.User", "ARDUINO_SKETCHBOOK_DIR") + settings.BindEnv("directories.Downloads", "ARDUINO_DOWNLOADS_DIR") + settings.BindEnv("directories.Data", "ARDUINO_DATA_DIR") } diff --git a/configuration/directories.go b/configuration/directories.go index 573d505a651..dc4fb4c57ab 100644 --- a/configuration/directories.go +++ b/configuration/directories.go @@ -21,26 +21,26 @@ import ( ) // HardwareDirectories returns all paths that may contains hardware packages. -func HardwareDirectories() paths.PathList { +func HardwareDirectories(settings *viper.Viper) paths.PathList { res := paths.PathList{} - if IsBundledInDesktopIDE() { - ideDir := paths.New(viper.GetString("IDE.Directory")) + if IsBundledInDesktopIDE(settings) { + ideDir := paths.New(settings.GetString("IDE.Directory")) bundledHardwareDir := ideDir.Join("hardware") if bundledHardwareDir.IsDir() { res.Add(bundledHardwareDir) } } - if viper.IsSet("directories.Data") { - packagesDir := PackagesDir() + if settings.IsSet("directories.Data") { + packagesDir := PackagesDir(Settings) if packagesDir.IsDir() { res.Add(packagesDir) } } - if viper.IsSet("directories.User") { - skDir := paths.New(viper.GetString("directories.User")) + if settings.IsSet("directories.User") { + skDir := paths.New(settings.GetString("directories.User")) hwDir := skDir.Join("hardware") if hwDir.IsDir() { res.Add(hwDir) @@ -51,11 +51,11 @@ func HardwareDirectories() paths.PathList { } // BundleToolsDirectories returns all paths that may contains bundled-tools. -func BundleToolsDirectories() paths.PathList { +func BundleToolsDirectories(settings *viper.Viper) paths.PathList { res := paths.PathList{} - if IsBundledInDesktopIDE() { - ideDir := paths.New(viper.GetString("IDE.Directory")) + if IsBundledInDesktopIDE(settings) { + ideDir := paths.New(settings.GetString("IDE.Directory")) bundledToolsDir := ideDir.Join("hardware", "tools") if bundledToolsDir.IsDir() { res = append(res, bundledToolsDir) @@ -68,9 +68,9 @@ func BundleToolsDirectories() paths.PathList { // IDEBundledLibrariesDir returns the libraries directory bundled in // the Arduino IDE. If there is no Arduino IDE or the directory doesn't // exists then nil is returned -func IDEBundledLibrariesDir() *paths.Path { - if IsBundledInDesktopIDE() { - ideDir := paths.New(viper.GetString("IDE.Directory")) +func IDEBundledLibrariesDir(settings *viper.Viper) *paths.Path { + if IsBundledInDesktopIDE(settings) { + ideDir := paths.New(Settings.GetString("IDE.Directory")) libDir := ideDir.Join("libraries") if libDir.IsDir() { return libDir @@ -82,11 +82,11 @@ func IDEBundledLibrariesDir() *paths.Path { // LibrariesDir returns the full path to the user directory containing // custom libraries -func LibrariesDir() *paths.Path { - return paths.New(viper.GetString("directories.User")).Join("libraries") +func LibrariesDir(settings *viper.Viper) *paths.Path { + return paths.New(settings.GetString("directories.User")).Join("libraries") } // PackagesDir returns the full path to the packages folder -func PackagesDir() *paths.Path { - return paths.New(viper.GetString("directories.Data")).Join("packages") +func PackagesDir(settings *viper.Viper) *paths.Path { + return paths.New(settings.GetString("directories.Data")).Join("packages") } diff --git a/docsgen/main.go b/docsgen/main.go index 128d1802f67..05bf95b7801 100644 --- a/docsgen/main.go +++ b/docsgen/main.go @@ -20,6 +20,7 @@ import ( "os" "github.com/arduino/arduino-cli/cli" + "github.com/arduino/arduino-cli/configuration" "github.com/spf13/cobra/doc" ) @@ -28,8 +29,9 @@ func main() { log.Fatal("Please provide output folder") } + configuration.Settings = configuration.Init(configuration.FindConfigFileInArgsOrWorkingDirectory(os.Args)) cli := cli.NewCommand() - cli.DisableAutoGenTag = true // Disable addition of auto-generated date stamp + cli.DisableAutoGenTag = true // Disable addition of auto-generated date stamp err := doc.GenMarkdownTree(cli, os.Args[1]) if err != nil { log.Fatal(err) diff --git a/httpclient/httpclient_config.go b/httpclient/httpclient_config.go index 9fdf540fc21..a038c5f4a13 100644 --- a/httpclient/httpclient_config.go +++ b/httpclient/httpclient_config.go @@ -22,7 +22,7 @@ import ( "runtime" "github.com/arduino/arduino-cli/cli/globals" - "github.com/spf13/viper" + "github.com/arduino/arduino-cli/configuration" ) // Config is the configuration of the http client @@ -35,8 +35,8 @@ type Config struct { func DefaultConfig() (*Config, error) { var proxy *url.URL var err error - if viper.IsSet("network.proxy") { - proxyConfig := viper.GetString("network.proxy") + if configuration.Settings.IsSet("network.proxy") { + proxyConfig := configuration.Settings.GetString("network.proxy") if proxyConfig == "" { // empty configuration // this workaround must be here until viper can UnSet properties: @@ -54,7 +54,7 @@ func DefaultConfig() (*Config, error) { // UserAgent returns the user agent for the cli http client func UserAgent() string { - subComponent := viper.GetString("network.user_agent_ext") + subComponent := configuration.Settings.GetString("network.user_agent_ext") if subComponent != "" { subComponent = " " + subComponent } diff --git a/i18n/i18n.go b/i18n/i18n.go index 7e60ffd17bc..6881922b96a 100644 --- a/i18n/i18n.go +++ b/i18n/i18n.go @@ -15,9 +15,7 @@ package i18n -import ( - "github.com/spf13/viper" -) +import "github.com/arduino/arduino-cli/configuration" // Init initializes the i18n module, setting the locale according to this order of preference: // 1. Configuration set in arduino-cli.yaml @@ -27,7 +25,7 @@ func Init() { initRiceBox() locales := supportedLocales() - if configLocale := viper.GetString("locale"); configLocale != "" { + if configLocale := configuration.Settings.GetString("locale"); configLocale != "" { if locale := findMatchingLocale(configLocale, locales); locale != "" { setLocale(locale) return diff --git a/main.go b/main.go index d4a43cf81c8..fd691f526c9 100644 --- a/main.go +++ b/main.go @@ -25,7 +25,7 @@ import ( ) func main() { - configuration.Init(configuration.FindConfigFile()) + configuration.Settings = configuration.Init(configuration.FindConfigFileInArgsOrWorkingDirectory(os.Args)) i18n.Init() arduinoCmd := cli.NewCommand() if err := arduinoCmd.Execute(); err != nil { diff --git a/telemetry/telemetry.go b/telemetry/telemetry.go index b2180f6b7ce..8788ab8b2c9 100644 --- a/telemetry/telemetry.go +++ b/telemetry/telemetry.go @@ -19,12 +19,13 @@ import ( "crypto/hmac" "crypto/sha256" "encoding/hex" + "net/http" + + "github.com/arduino/arduino-cli/configuration" "github.com/arduino/arduino-cli/inventory" "github.com/segmentio/stats/v4" "github.com/segmentio/stats/v4/prometheus" "github.com/sirupsen/logrus" - "github.com/spf13/viper" - "net/http" ) // serverPattern is the telemetry endpoint resource path for consume metrics @@ -42,7 +43,7 @@ func Activate(metricPrefix string) { stats.Register(ph) // Configure using viper settings - serverAddr := viper.GetString("telemetry.addr") + serverAddr := configuration.Settings.GetString("telemetry.addr") logrus.Infof("Setting up Prometheus telemetry on %s%s", serverAddr, serverPattern) go func() { http.Handle(serverPattern, ph) diff --git a/test/test_config.py b/test/test_config.py index 28320b3d40f..4f51d809d12 100644 --- a/test/test_config.py +++ b/test/test_config.py @@ -13,16 +13,216 @@ # software without disclosing the source code of your own applications. To purchase # a commercial license, send an email to license@arduino.cc. from pathlib import Path +import json +import yaml def test_init(run_command, data_dir, working_dir): result = run_command("config init") + assert "" == result.stderr assert result.ok assert data_dir in result.stdout -def test_init_dest(run_command, working_dir): - dest = str(Path(working_dir) / "config" / "test") +def test_init_with_existing_custom_config(run_command, data_dir, working_dir, downloads_dir): + result = run_command("config init --additional-urls https://example.com") + assert result.ok + assert data_dir in result.stdout + + config_file = open(Path(data_dir) / "arduino-cli.yaml", "r") + configs = yaml.load(config_file.read(), Loader=yaml.FullLoader) + config_file.close() + assert ["https://example.com"] == configs["board_manager"]["additional_urls"] + assert "50051" == configs["daemon"]["port"] + assert data_dir == configs["directories"]["data"] + assert downloads_dir == configs["directories"]["downloads"] + assert data_dir == configs["directories"]["user"] + assert "" == configs["logging"]["file"] + assert "text" == configs["logging"]["format"] + assert "info" == configs["logging"]["level"] + assert ":9090" == configs["telemetry"]["addr"] + assert configs["telemetry"]["enabled"] + + config_file_path = Path(working_dir) / "config" / "test" / "config.yaml" + assert not config_file_path.exists() + result = run_command(f'config init --dest-file "{config_file_path}"') + assert result.ok + assert str(config_file_path) in result.stdout + + config_file = open(config_file_path, "r") + configs = yaml.load(config_file.read(), Loader=yaml.FullLoader) + config_file.close() + assert [] == configs["board_manager"]["additional_urls"] + assert "50051" == configs["daemon"]["port"] + assert data_dir == configs["directories"]["data"] + assert downloads_dir == configs["directories"]["downloads"] + assert data_dir == configs["directories"]["user"] + assert "" == configs["logging"]["file"] + assert "text" == configs["logging"]["format"] + assert "info" == configs["logging"]["level"] + assert ":9090" == configs["telemetry"]["addr"] + assert configs["telemetry"]["enabled"] + + +def test_init_overwrite_existing_custom_file(run_command, data_dir, working_dir, downloads_dir): + result = run_command("config init --additional-urls https://example.com") + assert result.ok + assert data_dir in result.stdout + + config_file = open(Path(data_dir) / "arduino-cli.yaml", "r") + configs = yaml.load(config_file.read(), Loader=yaml.FullLoader) + config_file.close() + assert ["https://example.com"] == configs["board_manager"]["additional_urls"] + assert "50051" == configs["daemon"]["port"] + assert data_dir == configs["directories"]["data"] + assert downloads_dir == configs["directories"]["downloads"] + assert data_dir == configs["directories"]["user"] + assert "" == configs["logging"]["file"] + assert "text" == configs["logging"]["format"] + assert "info" == configs["logging"]["level"] + assert ":9090" == configs["telemetry"]["addr"] + assert configs["telemetry"]["enabled"] + + result = run_command("config init --overwrite") + assert result.ok + assert data_dir in result.stdout + + config_file = open(Path(data_dir) / "arduino-cli.yaml", "r") + configs = yaml.load(config_file.read(), Loader=yaml.FullLoader) + config_file.close() + assert [] == configs["board_manager"]["additional_urls"] + assert "50051" == configs["daemon"]["port"] + assert data_dir == configs["directories"]["data"] + assert downloads_dir == configs["directories"]["downloads"] + assert data_dir == configs["directories"]["user"] + assert "" == configs["logging"]["file"] + assert "text" == configs["logging"]["format"] + assert "info" == configs["logging"]["level"] + assert ":9090" == configs["telemetry"]["addr"] + assert configs["telemetry"]["enabled"] + + +def test_init_dest_absolute_path(run_command, working_dir): + dest = Path(working_dir) / "config" / "test" + expected_config_file = dest / "arduino-cli.yaml" + assert not expected_config_file.exists() result = run_command(f'config init --dest-dir "{dest}"') assert result.ok - assert dest in result.stdout + assert str(expected_config_file) in result.stdout + assert expected_config_file.exists() + + +def test_init_dest_relative_path(run_command, working_dir): + dest = Path(working_dir) / "config" / "test" + expected_config_file = dest / "arduino-cli.yaml" + assert not expected_config_file.exists() + result = run_command('config init --dest-dir "config/test"') + assert result.ok + assert str(expected_config_file) in result.stdout + assert expected_config_file.exists() + + +def test_init_dest_flag_with_overwrite_flag(run_command, working_dir): + dest = Path(working_dir) / "config" / "test" + + expected_config_file = dest / "arduino-cli.yaml" + assert not expected_config_file.exists() + + result = run_command(f'config init --dest-dir "{dest}"') + assert result.ok + assert expected_config_file.exists() + + result = run_command(f'config init --dest-dir "{dest}"') + assert result.failed + assert "Config file already exists, use --overwrite to discard the existing one." in result.stderr + + result = run_command(f'config init --dest-dir "{dest}" --overwrite') + assert result.ok + assert str(expected_config_file) in result.stdout + + +def test_init_dest_and_config_file_flags(run_command, working_dir): + result = run_command('config init --dest-file "some_other_path" --dest-dir "some_path"') + assert result.failed + assert "Can't use both --dest-file and --dest-dir flags at the same time." in result.stderr + + +def test_init_config_file_flag_absolute_path(run_command, working_dir): + config_file = Path(working_dir) / "config" / "test" / "config.yaml" + assert not config_file.exists() + result = run_command(f'config init --dest-file "{config_file}"') + assert result.ok + assert str(config_file) in result.stdout + assert config_file.exists() + + +def test_init_config_file_flag_relative_path(run_command, working_dir): + config_file = Path(working_dir) / "config.yaml" + assert not config_file.exists() + result = run_command('config init --dest-file "config.yaml"') + assert result.ok + assert str(config_file) in result.stdout + assert config_file.exists() + + +def test_init_config_file_flag_with_overwrite_flag(run_command, working_dir): + config_file = Path(working_dir) / "config" / "test" / "config.yaml" + assert not config_file.exists() + + result = run_command(f'config init --dest-file "{config_file}"') + assert result.ok + assert config_file.exists() + + result = run_command(f'config init --dest-file "{config_file}"') + assert result.failed + assert "Config file already exists, use --overwrite to discard the existing one." in result.stderr + + result = run_command(f'config init --dest-file "{config_file}" --overwrite') + assert result.ok + assert str(config_file) in result.stdout + + +def test_dump(run_command, data_dir, working_dir): + # Create a config file first + config_file = Path(working_dir) / "config" / "test" / "config.yaml" + assert not config_file.exists() + result = run_command(f'config init --dest-file "{config_file}"') + assert result.ok + assert config_file.exists() + + result = run_command(f'config dump --config-file "{config_file}" --format json') + assert result.ok + settings_json = json.loads(result.stdout) + assert [] == settings_json["board_manager"]["additional_urls"] + + result = run_command('config init --additional-urls "https://example.com"') + assert result.ok + config_file = Path(data_dir) / "arduino-cli.yaml" + assert str(config_file) in result.stdout + assert config_file.exists() + + result = run_command("config dump --format json") + assert result.ok + settings_json = json.loads(result.stdout) + assert ["https://example.com"] == settings_json["board_manager"]["additional_urls"] + + +def test_dump_with_config_file_flag(run_command, working_dir): + # Create a config file first + config_file = Path(working_dir) / "config" / "test" / "config.yaml" + assert not config_file.exists() + result = run_command(f'config init --dest-file "{config_file}" --additional-urls=https://example.com') + assert result.ok + assert config_file.exists() + + result = run_command(f'config dump --config-file "{config_file}" --format json') + assert result.ok + settings_json = json.loads(result.stdout) + assert ["https://example.com"] == settings_json["board_manager"]["additional_urls"] + + result = run_command( + f'config dump --config-file "{config_file}" --additional-urls=https://another-url.com --format json' + ) + assert result.ok + settings_json = json.loads(result.stdout) + assert ["https://another-url.com"] == settings_json["board_manager"]["additional_urls"]