1
0
mirror of https://github.com/goreleaser/goreleaser.git synced 2025-01-10 03:47:03 +02:00
goreleaser/internal/pipe/winget/winget.go
Carlos Alexandro Becker 530223c2ac
chore: improvel logs
Signed-off-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>
2023-09-24 19:18:10 +00:00

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 ""
}
}