Skip to content

Commit

Permalink
feat(winget): support installing .exe directly (#4498)
Browse files Browse the repository at this point in the history
closes #4494

---------

Co-authored-by: Vedant <83997633+vedantmgoyal2009@users.noreply.github.com>
  • Loading branch information
caarlos0 and vedantmgoyal9 committed Dec 26, 2023
1 parent 2b9e471 commit 27f0e33
Show file tree
Hide file tree
Showing 5 changed files with 179 additions and 19 deletions.
@@ -0,0 +1,24 @@
# This file was generated by GoReleaser. DO NOT EDIT.
# yaml-language-server: $schema=https://aka.ms/winget-manifest.installer.1.5.0.schema.json
PackageIdentifier: goreleaser.foo
PackageVersion: 1.2.1
InstallerLocale: en-US
InstallerType: portable
Commands:
- foo
ReleaseDate: "2023-06-12"
Installers:
- Architecture: x64
InstallerUrl: https://dummyhost/download/v1.2.1/foo_windows_amd64v1.exe
InstallerSha256: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
UpgradeBehavior: uninstallPrevious
- Architecture: x86
InstallerUrl: https://dummyhost/download/v1.2.1/foo_windows_386.exe
InstallerSha256: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
UpgradeBehavior: uninstallPrevious
- Architecture: arm64
InstallerUrl: https://dummyhost/download/v1.2.1/foo_windows_arm64.exe
InstallerSha256: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
UpgradeBehavior: uninstallPrevious
ManifestType: installer
ManifestVersion: 1.5.0
@@ -0,0 +1,12 @@
# This file was generated by GoReleaser. DO NOT EDIT.
# yaml-language-server: $schema=https://aka.ms/winget-manifest.defaultLocale.1.5.0.schema.json
PackageIdentifier: goreleaser.foo
PackageVersion: 1.2.1
PackageLocale: en-US
Publisher: goreleaser
PackageName: foo
License: MIT
ShortDescription: foo bar zaz
Moniker: foo
ManifestType: defaultLocale
ManifestVersion: 1.5.0
@@ -0,0 +1,7 @@
# This file was generated by GoReleaser. DO NOT EDIT.
# yaml-language-server: $schema=https://aka.ms/winget-manifest.version.1.5.0.schema.json
PackageIdentifier: goreleaser.foo
PackageVersion: 1.2.1
DefaultLocale: en-US
ManifestType: version
ManifestVersion: 1.5.0
61 changes: 42 additions & 19 deletions internal/pipe/winget/winget.go
Expand Up @@ -28,6 +28,7 @@ var (
errSkipUpload = pipe.Skip("winget.skip_upload is set")
errSkipUploadAuto = pipe.Skip("winget.skip_upload is set to 'auto', and current version is a pre-release")
errMultipleArchives = pipe.Skip("found multiple archives for the same platform, please consider filtering by id")
errMixedFormats = pipe.Skip("found archives with multiple formats (.exe and .zip)")

// copied from winget src
packageIdentifierValid = regexp.MustCompile("^[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}(\\.[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}){1,7}$")
Expand Down Expand Up @@ -165,8 +166,13 @@ func (p Pipe) doRun(ctx *context.Context, winget config.Winget, cl client.Releas

filters := []artifact.Filter{
artifact.ByGoos("windows"),
artifact.ByFormats("zip"),
artifact.ByType(artifact.UploadableArchive),
artifact.Or(
artifact.And(
artifact.ByFormats("zip"),
artifact.ByType(artifact.UploadableArchive),
),
artifact.ByType(artifact.UploadableBinary),
),
artifact.Or(
artifact.ByGoarch("386"),
artifact.ByGoarch("arm64"),
Expand Down Expand Up @@ -231,32 +237,33 @@ func (p Pipe) doRun(ctx *context.Context, winget config.Winget, cl client.Releas
},
}

var amd64Count, i386count int
var amd64Count, i386count, zipCount, binaryCount int
for _, archive := range archives {
sha256, err := archive.Checksum("sha256")
if err != nil {
return err
}
var files []InstallerItemFile
folder := artifact.ExtraOr(*archive, artifact.ExtraWrappedIn, ".")
for _, bin := range artifact.ExtraOr(*archive, artifact.ExtraBinaries, []string{}) {
files = append(files, InstallerItemFile{
RelativeFilePath: strings.ReplaceAll(filepath.Join(folder, bin), "/", "\\"),
PortableCommandAlias: strings.TrimSuffix(filepath.Base(bin), ".exe"),
})
}
url, err := tmpl.New(ctx).WithArtifact(archive).Apply(winget.URLTemplate)
if err != nil {
return err
}
installer.Installers = append(installer.Installers, InstallerItem{
Architecture: fromGoArch[archive.Goarch],
NestedInstallerType: "portable",
NestedInstallerFiles: files,
InstallerURL: url,
InstallerSha256: sha256,
UpgradeBehavior: "uninstallPrevious",
})
item := InstallerItem{
Architecture: fromGoArch[archive.Goarch],
InstallerURL: url,
InstallerSha256: sha256,
UpgradeBehavior: "uninstallPrevious",
}
if archive.Format() == "zip" {
zipCount++
installer.InstallerType = "zip"
item.NestedInstallerType = "portable"
item.NestedInstallerFiles = installerItemFilesFor(*archive)
} else {
binaryCount++
installer.InstallerType = "portable"
installer.Commands = []string{winget.Name}
}
installer.Installers = append(installer.Installers, item)
switch archive.Goarch {
case "386":
i386count++
Expand All @@ -265,6 +272,10 @@ func (p Pipe) doRun(ctx *context.Context, winget config.Winget, cl client.Releas
}
}

if binaryCount > 0 && zipCount > 0 {
return errMixedFormats
}

if i386count > 1 || amd64Count > 1 {
return errMultipleArchives
}
Expand Down Expand Up @@ -437,3 +448,15 @@ func repoFileID(tp artifact.Type) string {
return ""
}
}

func installerItemFilesFor(archive artifact.Artifact) []InstallerItemFile {
var files []InstallerItemFile
folder := artifact.ExtraOr(archive, artifact.ExtraWrappedIn, ".")
for _, bin := range artifact.ExtraOr(archive, artifact.ExtraBinaries, []string{}) {
files = append(files, InstallerItemFile{
RelativeFilePath: strings.ReplaceAll(filepath.Join(folder, bin), "/", "\\"),
PortableCommandAlias: strings.TrimSuffix(filepath.Base(bin), ".exe"),
})
}
return files
}
94 changes: 94 additions & 0 deletions internal/pipe/winget/winget_test.go
Expand Up @@ -64,6 +64,21 @@ func TestRunPipe(t *testing.T) {
},
},
},
{
name: "mixed-formats",
expectRunErrorIs: errMixedFormats,
winget: config.Winget{
Name: "mixed",
Publisher: "Foo",
License: "MIT",
ShortDescription: "foo bar zaz",
IDs: []string{"zaz", "bar"},
Repository: config.RepoRef{
Owner: "foo",
Name: "bar",
},
},
},
{
name: "full",
expectPath: "manifests/b/Beckersoft LTDA/foo/1.2.1",
Expand Down Expand Up @@ -650,13 +665,25 @@ func TestRunPipe(t *testing.T) {
goarch := "amd64"
createFakeArtifact("partial", goos, goarch, "v1", "", nil)
createFakeArtifact("foo", goos, goarch, "v1", "", nil)
createFakeArtifact("zaz", goos, goarch, "v1", "", nil)
createFakeArtifact("wrapped-in-dir", goos, goarch, "v1", "", map[string]any{
artifact.ExtraWrappedIn: "foo",
artifact.ExtraBinaries: []string{"bin/foo.exe"},
})

goarch = "386"
createFakeArtifact("foo", goos, goarch, "", "", nil)
ctx.Artifacts.Add(&artifact.Artifact{
Name: "bar.exe",
Path: "doesnt-matter",
Goos: goos,
Goarch: goarch,
Type: artifact.UploadableBinary,
Extra: map[string]interface{}{
artifact.ExtraID: "bar",
},
})
createFakeArtifact("bar", goos, goarch, "v1", "", nil)
createFakeArtifact("wrapped-in-dir", goos, goarch, "", "", map[string]any{
artifact.ExtraWrappedIn: "foo",
artifact.ExtraBinaries: []string{"bin/foo.exe"},
Expand Down Expand Up @@ -750,3 +777,70 @@ func TestDefault(t *testing.T) {
require.NotEmpty(t, winget.CommitMessageTemplate)
require.Equal(t, "foo", winget.Name)
}

func TestFormatBinary(t *testing.T) {
folder := t.TempDir()
ctx := testctx.NewWithCfg(
config.Project{
Dist: folder,
ProjectName: "foo",
Winget: []config.Winget{{
Name: "foo",
Publisher: "goreleaser",
License: "MIT",
ShortDescription: "foo bar zaz",
IDs: []string{"foo"},
Repository: config.RepoRef{
Owner: "foo",
Name: "bar",
},
}},
},
testctx.WithVersion("1.2.1"),
testctx.WithCurrentTag("v1.2.1"),
testctx.WithSemver(1, 2, 1, "rc1"),
testctx.WithDate(time.Date(2023, 6, 12, 20, 32, 10, 12, time.Local)),
)
ctx.ReleaseNotes = "the changelog for this release..."
createFakeArtifact := func(id, goos, goarch, goamd64 string) {
path := filepath.Join(folder, "dist/foo_"+goos+goarch+goamd64+".exe")
art := artifact.Artifact{
Name: "foo_" + goos + "_" + goarch + goamd64 + ".exe",
Path: path,
Goos: goos,
Goarch: goarch,
Goamd64: goamd64,
Type: artifact.UploadableBinary,
Extra: map[string]interface{}{
artifact.ExtraID: id,
},
}
ctx.Artifacts.Add(&art)
require.NoError(t, os.MkdirAll(filepath.Dir(path), 0o755))
f, err := os.Create(path)
require.NoError(t, err)
require.NoError(t, f.Close())
}

goos := "windows"
createFakeArtifact("foo", goos, "amd64", "v1")
createFakeArtifact("foo", goos, "386", "")
createFakeArtifact("foo", goos, "arm64", "")

client := client.NewMock()
pipe := Pipe{}

require.NoError(t, pipe.Default(ctx))
require.NoError(t, pipe.runAll(ctx, client))
for _, winget := range ctx.Artifacts.Filter(artifact.Or(
artifact.ByType(artifact.WingetInstaller),
artifact.ByType(artifact.WingetVersion),
artifact.ByType(artifact.WingetDefaultLocale),
)).List() {
bts, err := os.ReadFile(winget.Path)
require.NoError(t, err)
golden.RequireEqualExtSubfolder(t, bts, extFor(winget.Type))
}
require.NoError(t, pipe.publishAll(ctx, client))
require.True(t, client.CreatedFile)
}

0 comments on commit 27f0e33

Please sign in to comment.