Skip to content
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

feat(winget): support installing .exe directly #4498

Merged
merged 5 commits into from Dec 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
@@ -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
caarlos0 marked this conversation as resolved.
Show resolved Hide resolved
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 @@
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 @@

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 @@
},
}

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"
caarlos0 marked this conversation as resolved.
Show resolved Hide resolved
installer.Commands = []string{winget.Name}
}
installer.Installers = append(installer.Installers, item)
switch archive.Goarch {
case "386":
i386count++
Expand All @@ -265,6 +272,10 @@
}
}

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

Check warning on line 277 in internal/pipe/winget/winget.go

View check run for this annotation

Codecov / codecov/patch

internal/pipe/winget/winget.go#L276-L277

Added lines #L276 - L277 were not covered by tests

if i386count > 1 || amd64Count > 1 {
return errMultipleArchives
}
Expand Down Expand Up @@ -437,3 +448,15 @@
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)
}