Skip to content

Add ways to let users verify if new CLI released #1416

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Aug 31, 2021
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 34 additions & 8 deletions cli/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import (
"github.com/arduino/arduino-cli/cli/output"
"github.com/arduino/arduino-cli/cli/sketch"
"github.com/arduino/arduino-cli/cli/update"
"github.com/arduino/arduino-cli/cli/updater"
"github.com/arduino/arduino-cli/cli/upgrade"
"github.com/arduino/arduino-cli/cli/upload"
"github.com/arduino/arduino-cli/cli/version"
Expand All @@ -50,12 +51,14 @@ import (
"github.com/rifflock/lfshook"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
semver "go.bug.st/relaxed-semver"
)

var (
verbose bool
outputFormat string
configFile string
verbose bool
outputFormat string
configFile string
updaterMessageChan chan *semver.Version = make(chan *semver.Version)
)

// NewCommand creates a new ArduinoCli command root
Expand All @@ -64,11 +67,12 @@ func NewCommand() *cobra.Command {

// ArduinoCli is the root command
arduinoCli := &cobra.Command{
Use: "arduino-cli",
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,
Use: "arduino-cli",
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,
PersistentPostRun: postRun,
}

arduinoCli.SetUsageTemplate(usageTemplate)
Expand Down Expand Up @@ -151,6 +155,20 @@ func preRun(cmd *cobra.Command, args []string) {
feedback.SetOut(colorable.NewColorableStdout())
feedback.SetErr(colorable.NewColorableStderr())

updaterMessageChan = make(chan *semver.Version)
go func() {
if cmd.Name() == "version" {
// The version command checks by itself if there's a new available version
updaterMessageChan <- nil
}
// Starts checking for updates
currentVersion, err := semver.Parse(globals.VersionInfo.VersionString)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is VersionString supposed to be always semver compliant? if yes you can use semver.MustParse to avoid checking the error

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not always semver compliant, it can also be git-snapshot or nightly-<timestamp> so I can't use semver.MustParse.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't those versions be changed to 0.0.0-git or 0.0.0-nightly-<timestamp>?
These non-semver versions plugged in feels "wrong" to me...

Also we already had:

var (
	defaultVersionString = "0.0.0-git"
	versionString        = ""
	commit               = ""
	status               = "alpha"
	date                 = ""
	tr                   = i18n.Tr
)

in version.go IMHO we should follow the same pattern and force semver compliance.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I get what you mean but probably would be better if done in a separate PR.

if err != nil {
updaterMessageChan <- nil
}
updaterMessageChan <- updater.CheckForUpdate(currentVersion)
}()

//
// Prepare logging
//
Expand Down Expand Up @@ -236,3 +254,11 @@ func preRun(cmd *cobra.Command, args []string) {
})
}
}

func postRun(cmd *cobra.Command, args []string) {
latestVersion := <-updaterMessageChan
if latestVersion != nil {
// Notify the user a new version is available
updater.NotifyNewVersionIsAvailable(latestVersion.String())
}
}
1 change: 1 addition & 0 deletions cli/config/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ var validMap = map[string]reflect.Kind{
"network.proxy": reflect.String,
"network.user_agent_ext": reflect.String,
"output.no_color": reflect.Bool,
"updater.enable_notification": reflect.Bool,
}

func typeOf(key string) (reflect.Kind, error) {
Expand Down
138 changes: 138 additions & 0 deletions cli/updater/updater.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
// This file is part of arduino-cli.
//
// Copyright 2020 ARDUINO SA (http://www.arduino.cc/)
//
// This software is released under the GNU General Public License version 3,
// which covers the main part of arduino-cli.
// The terms of this license can be found at:
// https://www.gnu.org/licenses/gpl-3.0.en.html
//
// You can be released from the requirements of the above licenses by purchasing
// a commercial license. Buying such a license is mandatory if you want to
// modify or otherwise use the software for commercial activities involving the
// Arduino software without disclosing the source code of your own applications.
// To purchase a commercial license, send an email to [email protected].

package updater

import (
"os"
"strings"
"time"

"github.com/arduino/arduino-cli/cli/feedback"
"github.com/arduino/arduino-cli/cli/globals"
"github.com/arduino/arduino-cli/configuration"
"github.com/arduino/arduino-cli/httpclient"
"github.com/arduino/arduino-cli/i18n"
"github.com/arduino/arduino-cli/inventory"
"github.com/fatih/color"
semver "go.bug.st/relaxed-semver"
)

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) {
return nil
}

return checkForUpdate(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 {
return checkForUpdate(currentVersion)
}

func checkForUpdate(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())
if err != nil {
return nil
}

if currentVersion.GreaterThanOrEqual(latestVersion) {
// Current version is already good enough
return nil
}

return latestVersion
}

// NotifyNewVersionIsAvailable prints information about the new latestVersion
func NotifyNewVersionIsAvailable(latestVersion string) {
feedback.Errorf("\n\n%s %s → %s\n%s",
color.YellowString(tr("A new release of Arduino CLI is available:")),
color.CyanString(globals.VersionInfo.VersionString),
color.CyanString(latestVersion),
color.YellowString("https://arduino.github.io/arduino-cli/latest/installation/#latest-packages"))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this URL going to stay? Will it move at some point from arduino.github.io to arduino.cc/something?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally we'll change it in the future when the Arduino CLI will be added to https://www.arduino.cc/en/software.

}

// 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 !isCI() && configuration.IsInteractive && configuration.HasConsole
}

// based on https://github.com/watson/ci-info/blob/HEAD/index.js
func isCI() bool {
return os.Getenv("CI") != "" || // GitHub Actions, Travis CI, CircleCI, Cirrus CI, GitLab CI, AppVeyor, CodeShip, dsari
os.Getenv("BUILD_NUMBER") != "" || // Jenkins, TeamCity
os.Getenv("RUN_ID") != "" // TaskCluster, dsari
}

// 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 {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can make this function return a *semver.Version (or nil if fails or not valid semver)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think that would help that much, this getLatestRelease is only used in checkForUpdate and there we handle eventual errors in parsing the version string, if I change getLatestRelease to return a *semver.Version I'd have to check for the parsing error inside it and handle the nil version in checkForUpdate too.

Don't seem much of an improvement to me really. 🤔

client, err := httpclient.New()
if err != nil {
return ""
}

// We just use this URL to check if there's a new release available and
// never show it to the user, so it's fine to use the Linux one for all OSs.
URL := "https://downloads.arduino.cc/arduino-cli/arduino-cli_latest_Linux_64bit.tar.gz"
res, err := client.Head(URL)
if err != nil {
// Yes, we ignore it
return ""
}

// Get redirected URL
location := res.Request.URL.String()

// The location header points to the the latest release of the CLI, it's supposed to be formatted like this:
// https://downloads.arduino.cc/arduino-cli/arduino-cli_0.18.3_Linux_64bit.tar.gz
// so we split it to get the version, if there are not enough splits something must have gone wrong.
split := strings.Split(location, "_")
if len(split) < 2 {
return ""
}

return split[1]
}
30 changes: 29 additions & 1 deletion cli/version/version.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,15 @@ package version

import (
"os"
"strings"

"github.com/arduino/arduino-cli/cli/errorcodes"
"github.com/arduino/arduino-cli/cli/feedback"
"github.com/arduino/arduino-cli/cli/globals"
"github.com/arduino/arduino-cli/cli/updater"
"github.com/arduino/arduino-cli/i18n"
"github.com/spf13/cobra"
semver "go.bug.st/relaxed-semver"
)

var tr = i18n.Tr
Expand All @@ -39,5 +43,29 @@ func NewCommand() *cobra.Command {
}

func run(cmd *cobra.Command, args []string) {
feedback.Print(globals.VersionInfo)
if strings.Contains(globals.VersionInfo.VersionString, "git-snapshot") || strings.Contains(globals.VersionInfo.VersionString, "nightly") {
// We're using a development version, no need to check if there's a
// new release available
feedback.Print(globals.VersionInfo)
return
}
Comment on lines +46 to +51
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know if this is sensible here... I'd leave the version command to always check for the latest version (even in dev builds)

Suggested change
if strings.Contains(globals.VersionInfo.VersionString, "git-snapshot") || strings.Contains(globals.VersionInfo.VersionString, "nightly") {
// We're using a development version, no need to check if there's a
// new release available
feedback.Print(globals.VersionInfo)
return
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't, it would fail because globals.VersionInfo.VersionString can't be parsed as semver.


currentVersion, err := semver.Parse(globals.VersionInfo.VersionString)
if err != nil {
feedback.Errorf("Error parsing current version: %s", err)
os.Exit(errorcodes.ErrGeneric)
}
latestVersion := updater.ForceCheckForUpdate(currentVersion)

versionInfo := globals.VersionInfo
if feedback.GetFormat() == feedback.JSON && latestVersion != nil {
// Set this only we managed to get the latest version
versionInfo.LatestVersion = latestVersion.String()
}

feedback.Print(versionInfo)

if feedback.GetFormat() == feedback.Text && latestVersion != nil {
updater.NotifyNewVersionIsAvailable(latestVersion.String())
}
}
3 changes: 3 additions & 0 deletions configuration/defaults.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ func SetDefaults(settings *viper.Viper) {
// output settings
settings.SetDefault("output.no_color", false)

// updater settings
settings.SetDefault("updater.enable_notification", true)

// Bind env vars
settings.SetEnvPrefix("ARDUINO")
settings.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
Expand Down
2 changes: 2 additions & 0 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
- `sketch` - configuration options relating to [Arduino sketches][sketch specification].
- `always_export_binaries` - set to `true` to make [`arduino-cli compile`][arduino-cli compile] always save binaries
to the sketch folder. This is the equivalent of using the [`--export-binaries`][arduino-cli compile options] flag.
- `updater` - configuration options related to Arduino CLI updates
- `enable_notification` - set to `false` to disable notifications of new Arduino CLI releases, defaults to `true`

## Configuration methods

Expand Down
4 changes: 4 additions & 0 deletions docs/installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ as a parameter like this:
curl -fsSL https://raw.githubusercontent.com/arduino/arduino-cli/master/install.sh | sh -s 0.9.0
```

Arduino CLI checks for new releases every 24 hours. If you don't like this behaviour you can disable it by setting the
[`updater.enable_notification` config](configuration.md#configuration-keys) or the
[env var `ARDUINO_UPDATER_ENABLE_NOTIFICATION`](configuration.md#environment-variables) to `false`.

### Download

Pre-built binaries for all the supported platforms are available for download from the links below.
Expand Down
Loading