Skip to content

Commit bc998ed

Browse files
silvanocerzaper1234cmaglie
authored
Add ways to let users verify if new CLI released (#1416)
* Add ways to let users verify if new CLI released * Code review fixes Co-authored-by: per1234 <[email protected]> * Enhance docs Co-authored-by: per1234 <[email protected]> * Fix version check for git-snapshots and nightlies * Change method to request latest release * Remove ansi library in favor of color * Fix go mod errors * Remove useless function Co-authored-by: Cristian Maglie <[email protected]> Co-authored-by: per1234 <[email protected]> Co-authored-by: Cristian Maglie <[email protected]>
1 parent ff4eb92 commit bc998ed

File tree

11 files changed

+248
-42
lines changed

11 files changed

+248
-42
lines changed

cli/cli.go

+34-8
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ import (
3939
"github.com/arduino/arduino-cli/cli/output"
4040
"github.com/arduino/arduino-cli/cli/sketch"
4141
"github.com/arduino/arduino-cli/cli/update"
42+
"github.com/arduino/arduino-cli/cli/updater"
4243
"github.com/arduino/arduino-cli/cli/upgrade"
4344
"github.com/arduino/arduino-cli/cli/upload"
4445
"github.com/arduino/arduino-cli/cli/version"
@@ -50,12 +51,14 @@ import (
5051
"github.com/rifflock/lfshook"
5152
"github.com/sirupsen/logrus"
5253
"github.com/spf13/cobra"
54+
semver "go.bug.st/relaxed-semver"
5355
)
5456

5557
var (
56-
verbose bool
57-
outputFormat string
58-
configFile string
58+
verbose bool
59+
outputFormat string
60+
configFile string
61+
updaterMessageChan chan *semver.Version = make(chan *semver.Version)
5962
)
6063

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

6568
// ArduinoCli is the root command
6669
arduinoCli := &cobra.Command{
67-
Use: "arduino-cli",
68-
Short: tr("Arduino CLI."),
69-
Long: tr("Arduino Command Line Interface (arduino-cli)."),
70-
Example: fmt.Sprintf(" %s <%s> [%s...]", os.Args[0], tr("command"), tr("flags")),
71-
PersistentPreRun: preRun,
70+
Use: "arduino-cli",
71+
Short: tr("Arduino CLI."),
72+
Long: tr("Arduino Command Line Interface (arduino-cli)."),
73+
Example: fmt.Sprintf(" %s <%s> [%s...]", os.Args[0], tr("command"), tr("flags")),
74+
PersistentPreRun: preRun,
75+
PersistentPostRun: postRun,
7276
}
7377

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

158+
updaterMessageChan = make(chan *semver.Version)
159+
go func() {
160+
if cmd.Name() == "version" {
161+
// The version command checks by itself if there's a new available version
162+
updaterMessageChan <- nil
163+
}
164+
// Starts checking for updates
165+
currentVersion, err := semver.Parse(globals.VersionInfo.VersionString)
166+
if err != nil {
167+
updaterMessageChan <- nil
168+
}
169+
updaterMessageChan <- updater.CheckForUpdate(currentVersion)
170+
}()
171+
154172
//
155173
// Prepare logging
156174
//
@@ -236,3 +254,11 @@ func preRun(cmd *cobra.Command, args []string) {
236254
})
237255
}
238256
}
257+
258+
func postRun(cmd *cobra.Command, args []string) {
259+
latestVersion := <-updaterMessageChan
260+
if latestVersion != nil {
261+
// Notify the user a new version is available
262+
updater.NotifyNewVersionIsAvailable(latestVersion.String())
263+
}
264+
}

cli/config/validate.go

+1
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ var validMap = map[string]reflect.Kind{
3636
"network.proxy": reflect.String,
3737
"network.user_agent_ext": reflect.String,
3838
"output.no_color": reflect.Bool,
39+
"updater.enable_notification": reflect.Bool,
3940
}
4041

4142
func typeOf(key string) (reflect.Kind, error) {

cli/updater/updater.go

+134
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
// This file is part of arduino-cli.
2+
//
3+
// Copyright 2020 ARDUINO SA (http://www.arduino.cc/)
4+
//
5+
// This software is released under the GNU General Public License version 3,
6+
// which covers the main part of arduino-cli.
7+
// The terms of this license can be found at:
8+
// https://www.gnu.org/licenses/gpl-3.0.en.html
9+
//
10+
// You can be released from the requirements of the above licenses by purchasing
11+
// a commercial license. Buying such a license is mandatory if you want to
12+
// modify or otherwise use the software for commercial activities involving the
13+
// Arduino software without disclosing the source code of your own applications.
14+
// To purchase a commercial license, send an email to [email protected].
15+
16+
package updater
17+
18+
import (
19+
"os"
20+
"strings"
21+
"time"
22+
23+
"github.com/arduino/arduino-cli/cli/feedback"
24+
"github.com/arduino/arduino-cli/cli/globals"
25+
"github.com/arduino/arduino-cli/configuration"
26+
"github.com/arduino/arduino-cli/httpclient"
27+
"github.com/arduino/arduino-cli/i18n"
28+
"github.com/arduino/arduino-cli/inventory"
29+
"github.com/fatih/color"
30+
semver "go.bug.st/relaxed-semver"
31+
)
32+
33+
var tr = i18n.Tr
34+
35+
// CheckForUpdate returns the latest available version if greater than
36+
// the one running and it makes sense to check for an update, nil in all other cases
37+
func CheckForUpdate(currentVersion *semver.Version) *semver.Version {
38+
if !shouldCheckForUpdate(currentVersion) {
39+
return nil
40+
}
41+
42+
return ForceCheckForUpdate(currentVersion)
43+
}
44+
45+
// ForceCheckForUpdate always returns the latest available version if greater than
46+
// the one running, nil in all other cases
47+
func ForceCheckForUpdate(currentVersion *semver.Version) *semver.Version {
48+
defer func() {
49+
// Always save the last time we checked for updates at the end
50+
inventory.Store.Set("updater.last_check_time", time.Now())
51+
inventory.WriteStore()
52+
}()
53+
54+
latestVersion, err := semver.Parse(getLatestRelease())
55+
if err != nil {
56+
return nil
57+
}
58+
59+
if currentVersion.GreaterThanOrEqual(latestVersion) {
60+
// Current version is already good enough
61+
return nil
62+
}
63+
64+
return latestVersion
65+
}
66+
67+
// NotifyNewVersionIsAvailable prints information about the new latestVersion
68+
func NotifyNewVersionIsAvailable(latestVersion string) {
69+
feedback.Errorf("\n\n%s %s → %s\n%s",
70+
color.YellowString(tr("A new release of Arduino CLI is available:")),
71+
color.CyanString(globals.VersionInfo.VersionString),
72+
color.CyanString(latestVersion),
73+
color.YellowString("https://arduino.github.io/arduino-cli/latest/installation/#latest-packages"))
74+
}
75+
76+
// shouldCheckForUpdate return true if it actually makes sense to check for new updates,
77+
// false in all other cases.
78+
func shouldCheckForUpdate(currentVersion *semver.Version) bool {
79+
if strings.Contains(currentVersion.String(), "git-snapshot") || strings.Contains(currentVersion.String(), "nightly") {
80+
// This is a dev build, no need to check for updates
81+
return false
82+
}
83+
84+
if !configuration.Settings.GetBool("updater.enable_notification") {
85+
// Don't check if the user disabled the notification
86+
return false
87+
}
88+
89+
if inventory.Store.IsSet("updater.last_check_time") && time.Since(inventory.Store.GetTime("updater.last_check_time")).Hours() < 24 {
90+
// Checked less than 24 hours ago, let's wait
91+
return false
92+
}
93+
94+
// Don't check when running on CI or on non interactive consoles
95+
return !isCI() && configuration.IsInteractive && configuration.HasConsole
96+
}
97+
98+
// based on https://github.com/watson/ci-info/blob/HEAD/index.js
99+
func isCI() bool {
100+
return os.Getenv("CI") != "" || // GitHub Actions, Travis CI, CircleCI, Cirrus CI, GitLab CI, AppVeyor, CodeShip, dsari
101+
os.Getenv("BUILD_NUMBER") != "" || // Jenkins, TeamCity
102+
os.Getenv("RUN_ID") != "" // TaskCluster, dsari
103+
}
104+
105+
// getLatestRelease queries the official Arduino download server for the latest release,
106+
// if there are no errors or issues a version string is returned, in all other case an empty string.
107+
func getLatestRelease() string {
108+
client, err := httpclient.New()
109+
if err != nil {
110+
return ""
111+
}
112+
113+
// We just use this URL to check if there's a new release available and
114+
// never show it to the user, so it's fine to use the Linux one for all OSs.
115+
URL := "https://downloads.arduino.cc/arduino-cli/arduino-cli_latest_Linux_64bit.tar.gz"
116+
res, err := client.Head(URL)
117+
if err != nil {
118+
// Yes, we ignore it
119+
return ""
120+
}
121+
122+
// Get redirected URL
123+
location := res.Request.URL.String()
124+
125+
// The location header points to the the latest release of the CLI, it's supposed to be formatted like this:
126+
// https://downloads.arduino.cc/arduino-cli/arduino-cli_0.18.3_Linux_64bit.tar.gz
127+
// so we split it to get the version, if there are not enough splits something must have gone wrong.
128+
split := strings.Split(location, "_")
129+
if len(split) < 2 {
130+
return ""
131+
}
132+
133+
return split[1]
134+
}

cli/version/version.go

+29-1
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,15 @@ package version
1717

1818
import (
1919
"os"
20+
"strings"
2021

22+
"github.com/arduino/arduino-cli/cli/errorcodes"
2123
"github.com/arduino/arduino-cli/cli/feedback"
2224
"github.com/arduino/arduino-cli/cli/globals"
25+
"github.com/arduino/arduino-cli/cli/updater"
2326
"github.com/arduino/arduino-cli/i18n"
2427
"github.com/spf13/cobra"
28+
semver "go.bug.st/relaxed-semver"
2529
)
2630

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

4145
func run(cmd *cobra.Command, args []string) {
42-
feedback.Print(globals.VersionInfo)
46+
if strings.Contains(globals.VersionInfo.VersionString, "git-snapshot") || strings.Contains(globals.VersionInfo.VersionString, "nightly") {
47+
// We're using a development version, no need to check if there's a
48+
// new release available
49+
feedback.Print(globals.VersionInfo)
50+
return
51+
}
52+
53+
currentVersion, err := semver.Parse(globals.VersionInfo.VersionString)
54+
if err != nil {
55+
feedback.Errorf("Error parsing current version: %s", err)
56+
os.Exit(errorcodes.ErrGeneric)
57+
}
58+
latestVersion := updater.ForceCheckForUpdate(currentVersion)
59+
60+
versionInfo := globals.VersionInfo
61+
if feedback.GetFormat() == feedback.JSON && latestVersion != nil {
62+
// Set this only we managed to get the latest version
63+
versionInfo.LatestVersion = latestVersion.String()
64+
}
65+
66+
feedback.Print(versionInfo)
67+
68+
if feedback.GetFormat() == feedback.Text && latestVersion != nil {
69+
updater.NotifyNewVersionIsAvailable(latestVersion.String())
70+
}
4371
}

configuration/defaults.go

+3
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,9 @@ func SetDefaults(settings *viper.Viper) {
5252
// output settings
5353
settings.SetDefault("output.no_color", false)
5454

55+
// updater settings
56+
settings.SetDefault("updater.enable_notification", true)
57+
5558
// Bind env vars
5659
settings.SetEnvPrefix("ARDUINO")
5760
settings.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))

docs/configuration.md

+2
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
- `sketch` - configuration options relating to [Arduino sketches][sketch specification].
2525
- `always_export_binaries` - set to `true` to make [`arduino-cli compile`][arduino-cli compile] always save binaries
2626
to the sketch folder. This is the equivalent of using the [`--export-binaries`][arduino-cli compile options] flag.
27+
- `updater` - configuration options related to Arduino CLI updates
28+
- `enable_notification` - set to `false` to disable notifications of new Arduino CLI releases, defaults to `true`
2729

2830
## Configuration methods
2931

docs/installation.md

+4
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,10 @@ as a parameter like this:
4040
curl -fsSL https://github.com/raw/arduino/arduino-cli/master/install.sh | sh -s 0.9.0
4141
```
4242

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

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

0 commit comments

Comments
 (0)