Skip to content

Commit 85a4d70

Browse files
Merge pull request #41 from creativeprojects/support-macos-universal-binaries
* refactoring * introduce ExecutablePath() package method * add support for universal binary as a fallback * add documentation in the README
2 parents 899c547 + 53a7583 commit 85a4d70

21 files changed

+521
-215
lines changed

README.md

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ Self-Update library for Github, Gitea and Gitlab hosted applications in Go
2626
* [SHA256](#sha256)
2727
* [ECDSA](#ecdsa)
2828
* [Using a single checksum file for all your assets](#using-a-single-checksum-file-for-all-your-assets)
29+
* [macOS universal binaries](#macos-universal-binaries)
2930
* [Other providers than Github](#other-providers-than-github)
3031
* [GitLab](#gitlab)
3132
* [Example:](#example-1)
@@ -35,25 +36,27 @@ Self-Update library for Github, Gitea and Gitlab hosted applications in Go
3536

3637
# Introduction
3738

38-
go-selfupdate detects the information of the latest release via a source provider and
39+
`go-selfupdate` detects the information of the latest release via a source provider and
3940
checks the current version. If a newer version than itself is detected, it downloads the released binary from
4041
the source provider and replaces itself.
4142

4243
- Automatically detect the latest version of released binary on the source provider
4344
- Retrieve the proper binary for the OS and arch where the binary is running
4445
- Update the binary with rollback support on failure
4546
- Tested on Linux, macOS and Windows
46-
- Many archive and compression formats are supported (zip, tar, gzip, xzip, bzip2)
47+
- Support for different versions of ARM architecture
48+
- Support macOS universal binaries
49+
- Many archive and compression formats are supported (zip, tar, gzip, xz, bzip2)
4750
- Support private repositories
4851
- Support hash, signature validation
4952

50-
Two source providers are available:
53+
Three source providers are available:
5154
- GitHub
5255
- Gitea
5356
- Gitlab
5457

5558
This library started as a fork of https://github.com/rhysd/go-github-selfupdate. A few things have changed from the original implementation:
56-
- don't expose an external semver.Version type, but provide the same functionality through the API: LessThan, Equal and GreaterThan
59+
- don't expose an external `semver.Version` type, but provide the same functionality through the API: `LessThan`, `Equal` and `GreaterThan`
5760
- use an interface to send logs (compatible with standard log.Logger)
5861
- able to detect different ARM CPU architectures (the original library wasn't working on my different versions of raspberry pi)
5962
- support for assets compressed with bzip2 (.bz2)
@@ -80,7 +83,7 @@ func update(version string) error {
8083
return nil
8184
}
8285

83-
exe, err := os.Executable()
86+
exe, err := selfupdate.ExecutablePath()
8487
if err != nil {
8588
return errors.New("could not locate executable path")
8689
}
@@ -301,6 +304,18 @@ Tools like [goreleaser][] produce a single checksum file for all your assets. A
301304
updater, _ := NewUpdater(Config{Validator: &ChecksumValidator{UniqueFilename: "checksums.txt"}})
302305
```
303306

307+
# macOS universal binaries
308+
309+
You can ask the updater to choose a macOS universal binary as a fallback if the native architecture wasn't found.
310+
311+
You need to provide the architecture name for the universal binary in the `Config` struct:
312+
313+
```go
314+
updater, _ := NewUpdater(Config{UniversalArch: "all"})
315+
```
316+
317+
Default is empty, which means no fallback.
318+
304319
# Other providers than Github
305320

306321
This library can be easily extended by providing a new source and release implementation for any git provider
@@ -353,7 +368,7 @@ func update() {
353368
}
354369
fmt.Printf("found release %s\n", release.Version())
355370

356-
exe, err := os.Executable()
371+
exe, err := selfupdate.ExecutablePath()
357372
if err != nil {
358373
return errors.New("could not locate executable path")
359374
}

arch.go

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,26 @@ const (
99
maxARM = 7
1010
)
1111

12-
// generateAdditionalArch we can use depending on the type of CPU
13-
func generateAdditionalArch(arch string, goarm uint8) []string {
12+
// getAdditionalArch we can use depending on the type of CPU
13+
func getAdditionalArch(arch string, goarm uint8, universalArch string) []string {
14+
const defaultArchCapacity = 3
15+
additionalArch := make([]string, 0, defaultArchCapacity)
16+
1417
if arch == "arm" && goarm >= minARM && goarm <= maxARM {
15-
additionalArch := make([]string, 0, maxARM-minARM)
18+
// more precise arch at the top of the list
1619
for v := goarm; v >= minARM; v-- {
1720
additionalArch = append(additionalArch, fmt.Sprintf("armv%d", v))
1821
}
22+
additionalArch = append(additionalArch, "arm")
1923
return additionalArch
2024
}
25+
26+
additionalArch = append(additionalArch, arch)
2127
if arch == "amd64" {
22-
return []string{"x86_64"}
28+
additionalArch = append(additionalArch, "x86_64")
29+
}
30+
if universalArch != "" {
31+
additionalArch = append(additionalArch, universalArch)
2332
}
24-
return []string{}
33+
return additionalArch
2534
}

arch_test.go

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,23 +9,26 @@ import (
99

1010
func TestAdditionalArch(t *testing.T) {
1111
testData := []struct {
12-
arch string
13-
goarm uint8
14-
expected []string
12+
arch string
13+
goarm uint8
14+
universalArch string
15+
expected []string
1516
}{
16-
{"arm64", 8, []string{}},
17-
{"arm", 8, []string{}}, // armv8 is called arm64 - this shouldn't happen
18-
{"arm", 7, []string{"armv7", "armv6", "armv5"}},
19-
{"arm", 6, []string{"armv6", "armv5"}},
20-
{"arm", 5, []string{"armv5"}},
21-
{"arm", 4, []string{}}, // go is not supporting below armv5
22-
{"amd64", 0, []string{"x86_64"}},
17+
{"arm64", 0, "", []string{"arm64"}},
18+
{"arm64", 0, "all", []string{"arm64", "all"}},
19+
{"arm", 8, "", []string{"arm"}}, // armv8 is called arm64 - this shouldn't happen
20+
{"arm", 7, "", []string{"armv7", "armv6", "armv5", "arm"}},
21+
{"arm", 6, "", []string{"armv6", "armv5", "arm"}},
22+
{"arm", 5, "", []string{"armv5", "arm"}},
23+
{"arm", 4, "", []string{"arm"}}, // go is not supporting below armv5
24+
{"amd64", 0, "", []string{"amd64", "x86_64"}},
25+
{"amd64", 0, "all", []string{"amd64", "x86_64", "all"}},
2326
}
2427

2528
for _, testItem := range testData {
2629
t.Run(fmt.Sprintf("%s-%d", testItem.arch, testItem.goarm), func(t *testing.T) {
27-
result := generateAdditionalArch(testItem.arch, testItem.goarm)
28-
assert.ElementsMatch(t, testItem.expected, result)
30+
result := getAdditionalArch(testItem.arch, testItem.goarm, testItem.universalArch)
31+
assert.Equal(t, testItem.expected, result)
2932
})
3033
}
3134
}

arm.go

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,8 @@ package selfupdate
22

33
import (
44
"debug/buildinfo"
5-
"os"
65
)
76

8-
var goarm uint8
9-
10-
//nolint:gochecknoinits
11-
func init() {
12-
// avoid using runtime.goarm directly
13-
goarm = getGOARM(os.Args[0])
14-
}
15-
167
func getGOARM(goBinary string) uint8 {
178
build, err := buildinfo.ReadFile(goBinary)
189
if err != nil {
@@ -21,7 +12,7 @@ func getGOARM(goBinary string) uint8 {
2112
for _, setting := range build.Settings {
2213
if setting.Key == "GOARM" {
2314
// the value is coming from the linker, so it should be safe to convert
24-
return uint8(setting.Value[0] - '0')
15+
return setting.Value[0] - '0'
2516
}
2617
}
2718
return 0

cmd/detect-latest-release/update.go

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,8 @@ package main
22

33
import (
44
"context"
5-
"errors"
65
"fmt"
76
"log"
8-
"os"
97
"runtime"
108

119
"github.com/creativeprojects/go-selfupdate"
@@ -26,9 +24,9 @@ func update(version string) error {
2624
return nil
2725
}
2826

29-
exe, err := os.Executable()
27+
exe, err := selfupdate.ExecutablePath()
3028
if err != nil {
31-
return errors.New("could not locate executable path")
29+
return fmt.Errorf("could not locate executable path: %w", err)
3230
}
3331
if err := selfupdate.UpdateTo(context.Background(), latest.AssetURL, latest.AssetName, exe); err != nil {
3432
return fmt.Errorf("error occurred while updating binary: %w", err)

codecov.yml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
codecov:
2+
notify:
3+
after_n_builds: 6
4+
5+
comment:
6+
after_n_builds: 6
7+
8+
coverage:
9+
round: nearest
10+
status:
11+
project:
12+
default:
13+
target: auto
14+
threshold: "2%"
15+
patch:
16+
default:
17+
target: "70%"
18+
threshold: "2%"

config.go

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,25 @@ package selfupdate
22

33
// Config represents the configuration of self-update.
44
type Config struct {
5-
// Source where to load the releases from (example: GitHubSource)
5+
// Source where to load the releases from (example: GitHubSource).
66
Source Source
77
// Validator represents types which enable additional validation of downloaded release.
88
Validator Validator
99
// Filters are regexp used to filter on specific assets for releases with multiple assets.
1010
// An asset is selected if it matches any of those, in addition to the regular tag, os, arch, extensions.
1111
// Please make sure that your filter(s) uniquely match an asset.
1212
Filters []string
13-
// OS is set to the value of runtime.GOOS by default, but you can force another value here
13+
// OS is set to the value of runtime.GOOS by default, but you can force another value here.
1414
OS string
15-
// Arch is set to the value of runtime.GOARCH by default, but you can force another value here
15+
// Arch is set to the value of runtime.GOARCH by default, but you can force another value here.
1616
Arch string
17-
// Arm 32bits version. Valid values are 0 (unknown), 5, 6 or 7. Default is detected value (if any)
17+
// Arm 32bits version. Valid values are 0 (unknown), 5, 6 or 7. Default is detected value (if available).
1818
Arm uint8
19-
// Draft permits an upgrade to a "draft" version (default to false)
19+
// Arch name for macOS universal binary. Default to none.
20+
// If set, the updater will only pick the universal binary if the Arch is not found.
21+
UniversalArch string
22+
// Draft permits an upgrade to a "draft" version (default to false).
2023
Draft bool
21-
// Prerelease permits an upgrade to a "pre-release" version (default to false)
24+
// Prerelease permits an upgrade to a "pre-release" version (default to false).
2225
Prerelease bool
2326
}

detect.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ var reVersion = regexp.MustCompile(`\d+\.\d+\.\d+`)
1515
// It fetches releases information from the source provider and find out the latest release with matching the tag names and asset names.
1616
// Drafts and pre-releases are ignored.
1717
// Assets would be suffixed by the OS name and the arch name such as 'foo_linux_amd64' where 'foo' is a command name.
18-
// '-' can also be used as a separator. File can be compressed with zip, gzip, zxip, bzip2, tar&gzip or tar&zxip.
18+
// '-' can also be used as a separator. File can be compressed with zip, gzip, xz, bzip2, tar&gzip or tar&xz.
1919
// So the asset can have a file extension for the corresponding compression format such as '.zip'.
2020
// On Windows, '.exe' also can be contained such as 'foo_windows_amd64.exe.zip'.
2121
func (up *Updater) DetectLatest(ctx context.Context, repository Repository) (release *Release, found bool, err error) {
@@ -131,7 +131,7 @@ func findValidationAsset(rel SourceRelease, validationName string) (SourceAsset,
131131
func (up *Updater) findReleaseAndAsset(rels []SourceRelease, targetVersion string) (SourceRelease, SourceAsset, *semver.Version, bool) {
132132
// we put the detected arch at the end of the list: that's fine for ARM so far,
133133
// as the additional arch are more accurate than the generic one
134-
for _, arch := range append(generateAdditionalArch(up.arch, up.arm), up.arch) {
134+
for _, arch := range getAdditionalArch(up.arch, up.arm, up.universalArch) {
135135
release, asset, version, found := up.findReleaseAndAssetForArch(arch, rels, targetVersion)
136136
if found {
137137
return release, asset, version, found

0 commit comments

Comments
 (0)