mirror of
https://github.com/kopia/kopia.git
synced 2026-01-07 22:17:50 -05:00
307 lines
7.4 KiB
Go
307 lines
7.4 KiB
Go
// Package gettool combines and replaces curl, tar and gunzip, sha256sum and a bunch of Makefile scripts
|
|
// to quickly download, verify and install OS-specific version of tools (typically from GitHub)
|
|
// in a platform-agnostic manner without external tooling.
|
|
package main
|
|
|
|
import (
|
|
"bufio"
|
|
_ "embed"
|
|
"flag"
|
|
"fmt"
|
|
"log"
|
|
"os"
|
|
"path/filepath"
|
|
"runtime"
|
|
"sort"
|
|
"strings"
|
|
|
|
"github.com/pkg/errors"
|
|
|
|
"github.com/kopia/kopia/tools/gettool/autodownload"
|
|
)
|
|
|
|
// ToolInfo encapsulates all information required to download a tool.
|
|
type ToolInfo struct {
|
|
urlTemplate string
|
|
osMap map[string]string
|
|
archMap map[string]string
|
|
stripPathComponents int
|
|
unsupportedArch map[string]bool
|
|
unsupportedOSArch map[string]bool
|
|
macosUniversalArch string
|
|
}
|
|
|
|
func (ti ToolInfo) actualURL(version, goos, goarch string) string {
|
|
if ti.unsupportedArch[goarch] {
|
|
return ""
|
|
}
|
|
|
|
if ti.unsupportedOSArch[goos+"/"+goarch] {
|
|
return ""
|
|
}
|
|
|
|
u := ti.urlTemplate
|
|
u = strings.ReplaceAll(u, "VERSION", version)
|
|
|
|
if goos == "darwin" && ti.macosUniversalArch != "" {
|
|
goarch = ti.macosUniversalArch
|
|
}
|
|
|
|
u = strings.ReplaceAll(u, "GOARCH", replacementFromMap(goarch, ti.archMap))
|
|
u = strings.ReplaceAll(u, "GOOS", replacementFromMap(goos, ti.osMap))
|
|
u = strings.ReplaceAll(u, "EXT", replacementFromMap(goos, map[string]string{
|
|
"windows": "zip",
|
|
"linux": "tar.gz",
|
|
"darwin": "tar.gz",
|
|
}))
|
|
|
|
return u
|
|
}
|
|
|
|
//nolint:gochecknoglobals
|
|
var tools = map[string]ToolInfo{
|
|
"linter": {
|
|
urlTemplate: "https://github.com/golangci/golangci-lint/releases/download/vVERSION/golangci-lint-VERSION-GOOS-GOARCH.EXT",
|
|
archMap: map[string]string{
|
|
"arm": "armv6",
|
|
},
|
|
stripPathComponents: 1,
|
|
},
|
|
"hugo": {
|
|
urlTemplate: "https://github.com/gohugoio/hugo/releases/download/vVERSION/hugo_extended_VERSION_GOOS-GOARCH.EXT",
|
|
unsupportedArch: map[string]bool{
|
|
"arm": true,
|
|
},
|
|
unsupportedOSArch: map[string]bool{
|
|
"linux/arm64": true,
|
|
},
|
|
macosUniversalArch: "universal",
|
|
},
|
|
"gotestsum": {
|
|
urlTemplate: "https://github.com/gotestyourself/gotestsum/releases/download/vVERSION/gotestsum_VERSION_GOOS_GOARCH.tar.gz",
|
|
archMap: map[string]string{
|
|
"arm": "armv6",
|
|
},
|
|
},
|
|
"kopia": {
|
|
urlTemplate: "https://github.com/kopia/kopia/releases/download/vVERSION/kopia-VERSION-GOOS-GOARCH.EXT",
|
|
archMap: map[string]string{
|
|
"amd64": "x64",
|
|
},
|
|
osMap: map[string]string{
|
|
"darwin": "macOS",
|
|
},
|
|
stripPathComponents: 1,
|
|
},
|
|
"rclone": {
|
|
urlTemplate: "https://github.com/rclone/rclone/releases/download/vVERSION/rclone-vVERSION-GOOS-GOARCH.zip",
|
|
osMap: map[string]string{"darwin": "osx"},
|
|
stripPathComponents: 1,
|
|
},
|
|
"goreleaser": {
|
|
urlTemplate: "https://github.com/goreleaser/goreleaser/releases/download/VERSION/goreleaser_GOOS_GOARCH.EXT",
|
|
archMap: map[string]string{
|
|
"amd64": "x86_64",
|
|
"arm": "armv6",
|
|
},
|
|
osMap: map[string]string{
|
|
"darwin": "Darwin",
|
|
"linux": "Linux",
|
|
"windows": "Windows",
|
|
},
|
|
},
|
|
"gitchglog": {
|
|
urlTemplate: "https://github.com/git-chglog/git-chglog/releases/download/vVERSION/git-chglog_VERSION_GOOS_GOARCH.EXT",
|
|
archMap: map[string]string{
|
|
"arm": "armv6",
|
|
},
|
|
},
|
|
"node": {
|
|
urlTemplate: "https://nodejs.org/dist/vVERSION/node-vVERSION-GOOS-GOARCH.EXT",
|
|
osMap: map[string]string{"windows": "win"},
|
|
archMap: map[string]string{"arm": "armv7l", "amd64": "x64"},
|
|
stripPathComponents: 1,
|
|
},
|
|
}
|
|
|
|
//nolint:gochecknoglobals
|
|
var (
|
|
tool = flag.String("tool", "", "Name of the tool:version")
|
|
outputDir = flag.String("output-dir", "", "Output directory")
|
|
goos = flag.String("goos", runtime.GOOS, "Override GOOS")
|
|
goarch = flag.String("goarch", runtime.GOARCH, "Override GOARCH")
|
|
|
|
testAll = flag.Bool("test-all", false, "Unpacks the package for all GOOS/ARCH combinations")
|
|
regenerateChecksums = flag.String("regenerate-checksums", "", "Regenerate checksums")
|
|
)
|
|
|
|
//nolint:gochecknoglobals
|
|
var buildArchitectures = []struct {
|
|
goos string
|
|
goarch string
|
|
}{
|
|
{"linux", "amd64"},
|
|
{"linux", "arm64"},
|
|
{"linux", "arm"},
|
|
{"darwin", "amd64"},
|
|
{"darwin", "arm64"},
|
|
{"windows", "amd64"},
|
|
}
|
|
|
|
func replacementFromMap(defaultValue string, m map[string]string) string {
|
|
if v, ok := m[defaultValue]; ok {
|
|
return v
|
|
}
|
|
|
|
return defaultValue
|
|
}
|
|
|
|
//go:embed checksums.txt
|
|
var checksumsFileContents string
|
|
|
|
func parseEmbeddedChecksums() map[string]string {
|
|
m := map[string]string{}
|
|
|
|
s := bufio.NewScanner(strings.NewReader(checksumsFileContents))
|
|
for s.Scan() {
|
|
p := strings.Split(s.Text(), ": ")
|
|
|
|
m[p[0]] = p[1]
|
|
}
|
|
|
|
return m
|
|
}
|
|
|
|
func main() {
|
|
flag.Parse()
|
|
|
|
if *outputDir == "" {
|
|
log.Fatalf("--output-dir must be set")
|
|
}
|
|
|
|
checksums := parseEmbeddedChecksums()
|
|
downloadedChecksums := map[string]string{}
|
|
|
|
var errorCount int
|
|
|
|
for _, toolNameVersion := range strings.Split(*tool, ",") {
|
|
parts := strings.Split(toolNameVersion, ":")
|
|
|
|
//nolint:mnd
|
|
if len(parts) != 2 {
|
|
log.Fatalf("invalid tool spec, must be tool:version[,tool:version]")
|
|
}
|
|
|
|
toolName := parts[0]
|
|
toolVersion := parts[1]
|
|
|
|
if err := downloadTool(toolName, toolVersion, checksums, downloadedChecksums, &errorCount); err != nil {
|
|
log.Fatalf("unable to download %v version %v: %v", toolName, toolVersion, err)
|
|
}
|
|
}
|
|
|
|
// all good
|
|
if errorCount == 0 && *regenerateChecksums == "" {
|
|
return
|
|
}
|
|
|
|
// on failure print current checksums, so they can be copy/pasted as the new baseline
|
|
var lines []string
|
|
|
|
for k, v := range downloadedChecksums {
|
|
lines = append(lines, fmt.Sprintf("%v: %v", k, v))
|
|
}
|
|
|
|
sort.Strings(lines)
|
|
|
|
for _, l := range lines {
|
|
fmt.Println(l)
|
|
}
|
|
|
|
if *regenerateChecksums != "" {
|
|
if err := writeLinesToFile(lines); err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
log.Fatalf("Error(s) encountered, see log messages above.")
|
|
}
|
|
|
|
func writeLinesToFile(lines []string) error {
|
|
f, err := os.Create(*regenerateChecksums)
|
|
if err != nil {
|
|
return errors.Wrap(err, "writeLinesToFile")
|
|
}
|
|
|
|
defer f.Close() //nolint:errcheck
|
|
|
|
for _, l := range lines {
|
|
fmt.Fprintln(f, l) //nolint:errcheck
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func downloadTool(toolName, toolVersion string, oldChecksums, downloadedChecksums map[string]string, errorCount *int) error {
|
|
t, ok := tools[toolName]
|
|
if !ok {
|
|
return errors.Errorf("unsupported tool: %q", toolName)
|
|
}
|
|
|
|
if *testAll {
|
|
for _, ba := range buildArchitectures {
|
|
u := t.actualURL(toolVersion, ba.goos, ba.goarch)
|
|
if u == "" {
|
|
continue
|
|
}
|
|
|
|
if err := autodownload.Download(u, filepath.Join(*outputDir, ba.goos, ba.goarch), oldChecksums, t.stripPathComponents); err != nil {
|
|
log.Printf("ERROR %v: %v", u, err)
|
|
|
|
*errorCount++
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
if *regenerateChecksums != "" {
|
|
for _, ba := range buildArchitectures {
|
|
u := t.actualURL(toolVersion, ba.goos, ba.goarch)
|
|
if u == "" {
|
|
continue
|
|
}
|
|
|
|
if oldChecksums[u] != "" {
|
|
downloadedChecksums[u] = oldChecksums[u]
|
|
continue
|
|
}
|
|
|
|
log.Printf("downloading %v...", u)
|
|
|
|
if err := autodownload.Download(u, filepath.Join(*outputDir, ba.goos, ba.goarch), downloadedChecksums, t.stripPathComponents); err != nil {
|
|
log.Printf("ERROR %v: %v", u, err)
|
|
|
|
*errorCount++
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
u := t.actualURL(toolVersion, *goos, *goarch)
|
|
if u == "" {
|
|
log.Fatalf("Tool '%v' is not supported on %v/%v", toolName, *goos, *goarch)
|
|
}
|
|
|
|
fmt.Printf("Downloading %v version %v from %v...\n", toolName, toolVersion, u)
|
|
|
|
if err := autodownload.Download(u, *outputDir, oldChecksums, t.stripPathComponents); err != nil {
|
|
return errors.Wrap(err, "unable to download")
|
|
}
|
|
|
|
return nil
|
|
}
|