mirror of
https://github.com/goreleaser/goreleaser.git
synced 2025-01-10 03:47:03 +02:00
530223c2ac
Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>
439 lines
11 KiB
Go
439 lines
11 KiB
Go
package winget
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"regexp"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/caarlos0/log"
|
|
"github.com/goreleaser/goreleaser/internal/artifact"
|
|
"github.com/goreleaser/goreleaser/internal/client"
|
|
"github.com/goreleaser/goreleaser/internal/commitauthor"
|
|
"github.com/goreleaser/goreleaser/internal/pipe"
|
|
"github.com/goreleaser/goreleaser/internal/tmpl"
|
|
"github.com/goreleaser/goreleaser/pkg/config"
|
|
"github.com/goreleaser/goreleaser/pkg/context"
|
|
)
|
|
|
|
var (
|
|
errNoRepoName = pipe.Skip("winget.repository.name name is required")
|
|
errNoPublisher = pipe.Skip("winget.publisher is required")
|
|
errNoLicense = pipe.Skip("winget.license is required")
|
|
errNoShortDescription = pipe.Skip("winget.short_description is required")
|
|
errInvalidPackageIdentifier = pipe.Skip("winget.package_identifier is invalid")
|
|
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")
|
|
|
|
// copied from winget src
|
|
packageIdentifierValid = regexp.MustCompile("^[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}(\\.[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}){1,7}$")
|
|
)
|
|
|
|
type errNoArchivesFound struct {
|
|
goamd64 string
|
|
ids []string
|
|
}
|
|
|
|
func (e errNoArchivesFound) Error() string {
|
|
return fmt.Sprintf("no zip archives found matching goos=[windows] goarch=[amd64 386] goamd64=%s ids=%v", e.goamd64, e.ids)
|
|
}
|
|
|
|
const wingetConfigExtra = "WingetConfig"
|
|
|
|
type Pipe struct{}
|
|
|
|
func (Pipe) String() string { return "winget" }
|
|
func (Pipe) ContinueOnError() bool { return true }
|
|
func (p Pipe) Skip(ctx *context.Context) bool {
|
|
return len(ctx.Config.Winget) == 0
|
|
}
|
|
|
|
func (Pipe) Default(ctx *context.Context) error {
|
|
for i := range ctx.Config.Winget {
|
|
winget := &ctx.Config.Winget[i]
|
|
|
|
winget.CommitAuthor = commitauthor.Default(winget.CommitAuthor)
|
|
|
|
if winget.CommitMessageTemplate == "" {
|
|
winget.CommitMessageTemplate = "New version: {{ .PackageIdentifier }} {{ .Version }}"
|
|
}
|
|
if winget.Name == "" {
|
|
winget.Name = ctx.Config.ProjectName
|
|
}
|
|
if winget.Goamd64 == "" {
|
|
winget.Goamd64 = "v1"
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (p Pipe) Run(ctx *context.Context) error {
|
|
cli, err := client.NewReleaseClient(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return p.runAll(ctx, cli)
|
|
}
|
|
|
|
// Publish .
|
|
func (p Pipe) Publish(ctx *context.Context) error {
|
|
cli, err := client.New(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return p.publishAll(ctx, cli)
|
|
}
|
|
|
|
func (p Pipe) runAll(ctx *context.Context, cli client.ReleaseURLTemplater) error {
|
|
for _, winget := range ctx.Config.Winget {
|
|
err := p.doRun(ctx, winget, cli)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (p Pipe) doRun(ctx *context.Context, winget config.Winget, cl client.ReleaseURLTemplater) error {
|
|
if winget.Repository.Name == "" {
|
|
return errNoRepoName
|
|
}
|
|
|
|
tp := tmpl.New(ctx)
|
|
|
|
err := tp.ApplyAll(
|
|
&winget.Publisher,
|
|
&winget.Name,
|
|
&winget.Author,
|
|
&winget.PublisherURL,
|
|
&winget.PublisherSupportURL,
|
|
&winget.Homepage,
|
|
&winget.SkipUpload,
|
|
&winget.Description,
|
|
&winget.ShortDescription,
|
|
&winget.ReleaseNotesURL,
|
|
&winget.Path,
|
|
&winget.Copyright,
|
|
&winget.CopyrightURL,
|
|
&winget.License,
|
|
&winget.LicenseURL,
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if winget.Publisher == "" {
|
|
return errNoPublisher
|
|
}
|
|
|
|
if winget.License == "" {
|
|
return errNoLicense
|
|
}
|
|
|
|
winget.Repository, err = client.TemplateRef(tp.Apply, winget.Repository)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if winget.ShortDescription == "" {
|
|
return errNoShortDescription
|
|
}
|
|
|
|
winget.ReleaseNotes, err = tp.WithExtraFields(tmpl.Fields{
|
|
"Changelog": ctx.ReleaseNotes,
|
|
}).Apply(winget.ReleaseNotes)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if winget.URLTemplate == "" {
|
|
winget.URLTemplate, err = cl.ReleaseURLTemplate(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if winget.Path == "" {
|
|
winget.Path = filepath.Join("manifests", strings.ToLower(string(winget.Publisher[0])), winget.Publisher, winget.Name, ctx.Version)
|
|
}
|
|
|
|
filters := []artifact.Filter{
|
|
artifact.ByGoos("windows"),
|
|
artifact.ByFormats("zip"),
|
|
artifact.ByType(artifact.UploadableArchive),
|
|
artifact.Or(
|
|
artifact.ByGoarch("386"),
|
|
artifact.ByGoarch("arm64"),
|
|
artifact.And(
|
|
artifact.ByGoamd64(winget.Goamd64),
|
|
artifact.ByGoarch("amd64"),
|
|
),
|
|
),
|
|
}
|
|
if len(winget.IDs) > 0 {
|
|
filters = append(filters, artifact.ByIDs(winget.IDs...))
|
|
}
|
|
archives := ctx.Artifacts.Filter(artifact.And(filters...)).List()
|
|
if len(archives) == 0 {
|
|
return errNoArchivesFound{
|
|
goamd64: winget.Goamd64,
|
|
ids: winget.IDs,
|
|
}
|
|
}
|
|
|
|
if winget.PackageIdentifier == "" {
|
|
winget.PackageIdentifier = winget.Publisher + "." + winget.Name
|
|
}
|
|
|
|
if !packageIdentifierValid.MatchString(winget.PackageIdentifier) {
|
|
return fmt.Errorf("%w: %s", errInvalidPackageIdentifier, winget.PackageIdentifier)
|
|
}
|
|
|
|
if err := createYAML(ctx, winget, Version{
|
|
PackageIdentifier: winget.PackageIdentifier,
|
|
PackageVersion: ctx.Version,
|
|
DefaultLocale: defaultLocale,
|
|
ManifestType: "version",
|
|
ManifestVersion: manifestVersion,
|
|
}, artifact.WingetVersion); err != nil {
|
|
return err
|
|
}
|
|
|
|
var deps []PackageDependency
|
|
for _, dep := range winget.Dependencies {
|
|
if err := tp.ApplyAll(&dep.MinimumVersion, &dep.PackageIdentifier); err != nil {
|
|
return err
|
|
}
|
|
deps = append(deps, PackageDependency{
|
|
PackageIdentifier: dep.PackageIdentifier,
|
|
MinimumVersion: dep.MinimumVersion,
|
|
})
|
|
}
|
|
|
|
installer := Installer{
|
|
PackageIdentifier: winget.PackageIdentifier,
|
|
PackageVersion: ctx.Version,
|
|
InstallerLocale: defaultLocale,
|
|
InstallerType: "zip",
|
|
Commands: []string{},
|
|
ReleaseDate: ctx.Date.Format(time.DateOnly),
|
|
Installers: []InstallerItem{},
|
|
ManifestType: "installer",
|
|
ManifestVersion: manifestVersion,
|
|
Dependencies: Dependencies{
|
|
PackageDependencies: deps,
|
|
},
|
|
}
|
|
|
|
var amd64Count, i386count 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",
|
|
})
|
|
switch archive.Goarch {
|
|
case "386":
|
|
i386count++
|
|
case "amd64":
|
|
amd64Count++
|
|
}
|
|
}
|
|
|
|
if i386count > 1 || amd64Count > 1 {
|
|
return errMultipleArchives
|
|
}
|
|
|
|
if err := createYAML(ctx, winget, installer, artifact.WingetInstaller); err != nil {
|
|
return err
|
|
}
|
|
|
|
return createYAML(ctx, winget, Locale{
|
|
PackageIdentifier: winget.PackageIdentifier,
|
|
PackageVersion: ctx.Version,
|
|
PackageLocale: defaultLocale,
|
|
Publisher: winget.Publisher,
|
|
PublisherURL: winget.PublisherURL,
|
|
PublisherSupportURL: winget.PublisherSupportURL,
|
|
Author: winget.Author,
|
|
PackageName: winget.Name,
|
|
PackageURL: winget.Homepage,
|
|
License: winget.License,
|
|
LicenseURL: winget.LicenseURL,
|
|
Copyright: winget.Copyright,
|
|
CopyrightURL: winget.CopyrightURL,
|
|
ShortDescription: winget.ShortDescription,
|
|
Description: winget.Description,
|
|
Moniker: winget.Name,
|
|
Tags: winget.Tags,
|
|
ReleaseNotes: winget.ReleaseNotes,
|
|
ReleaseNotesURL: winget.ReleaseNotesURL,
|
|
ManifestType: "defaultLocale",
|
|
ManifestVersion: manifestVersion,
|
|
}, artifact.WingetDefaultLocale)
|
|
}
|
|
|
|
func (p Pipe) publishAll(ctx *context.Context, cli client.Client) error {
|
|
skips := pipe.SkipMemento{}
|
|
for _, files := range ctx.Artifacts.Filter(artifact.Or(
|
|
artifact.ByType(artifact.WingetInstaller),
|
|
artifact.ByType(artifact.WingetVersion),
|
|
artifact.ByType(artifact.WingetDefaultLocale),
|
|
)).GroupByID() {
|
|
err := doPublish(ctx, cli, files)
|
|
if err != nil && pipe.IsSkip(err) {
|
|
skips.Remember(err)
|
|
continue
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return skips.Evaluate()
|
|
}
|
|
|
|
func doPublish(ctx *context.Context, cl client.Client, wingets []*artifact.Artifact) error {
|
|
winget, err := artifact.Extra[config.Winget](*wingets[0], wingetConfigExtra)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if strings.TrimSpace(winget.SkipUpload) == "true" {
|
|
return errSkipUpload
|
|
}
|
|
|
|
if strings.TrimSpace(winget.SkipUpload) == "auto" && ctx.Semver.Prerelease != "" {
|
|
return errSkipUploadAuto
|
|
}
|
|
|
|
msg, err := tmpl.New(ctx).WithExtraFields(tmpl.Fields{
|
|
"PackageIdentifier": winget.PackageIdentifier,
|
|
}).Apply(winget.CommitMessageTemplate)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
author, err := commitauthor.Get(ctx, winget.CommitAuthor)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
repo := client.RepoFromRef(winget.Repository)
|
|
|
|
var files []client.RepoFile
|
|
for _, pkg := range wingets {
|
|
content, err := os.ReadFile(pkg.Path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
files = append(files, client.RepoFile{
|
|
Content: content,
|
|
Path: filepath.Join(winget.Path, pkg.Name),
|
|
Identifier: repoFileID(pkg.Type),
|
|
})
|
|
}
|
|
|
|
if winget.Repository.Git.URL != "" {
|
|
return client.NewGitUploadClient(repo.Branch).
|
|
CreateFiles(ctx, author, repo, msg, files)
|
|
}
|
|
|
|
cl, err = client.NewIfToken(ctx, cl, winget.Repository.Token)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, file := range files {
|
|
if err := cl.CreateFile(
|
|
ctx,
|
|
author,
|
|
repo,
|
|
file.Content,
|
|
file.Path,
|
|
msg+": add "+file.Identifier,
|
|
); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if !winget.Repository.PullRequest.Enabled {
|
|
log.Debug("wingets.pull_request disabled")
|
|
return nil
|
|
}
|
|
|
|
log.Info("winget.pull_request enabled, creating a PR")
|
|
pcl, ok := cl.(client.PullRequestOpener)
|
|
if !ok {
|
|
return fmt.Errorf("client does not support pull requests")
|
|
}
|
|
|
|
return pcl.OpenPullRequest(ctx, client.Repo{
|
|
Name: winget.Repository.PullRequest.Base.Name,
|
|
Owner: winget.Repository.PullRequest.Base.Owner,
|
|
Branch: winget.Repository.PullRequest.Base.Branch,
|
|
}, repo, msg, winget.Repository.PullRequest.Draft)
|
|
}
|
|
|
|
func langserverLineFor(tp artifact.Type) string {
|
|
switch tp {
|
|
case artifact.WingetInstaller:
|
|
return installerLangServer
|
|
case artifact.WingetDefaultLocale:
|
|
return defaultLocaleLangServer
|
|
default:
|
|
return versionLangServer
|
|
}
|
|
}
|
|
|
|
func extFor(tp artifact.Type) string {
|
|
switch tp {
|
|
case artifact.WingetVersion:
|
|
return ".yaml"
|
|
case artifact.WingetInstaller:
|
|
return ".installer.yaml"
|
|
case artifact.WingetDefaultLocale:
|
|
return ".locale." + defaultLocale + ".yaml"
|
|
default:
|
|
// should never happen
|
|
return ""
|
|
}
|
|
}
|
|
|
|
func repoFileID(tp artifact.Type) string {
|
|
switch tp {
|
|
case artifact.WingetVersion:
|
|
return "version"
|
|
case artifact.WingetInstaller:
|
|
return "installer"
|
|
case artifact.WingetDefaultLocale:
|
|
return "locale"
|
|
default:
|
|
// should never happen
|
|
return ""
|
|
}
|
|
}
|