From 0f79adca8796d0882616f582a58d2e7b69a38bfb Mon Sep 17 00:00:00 2001 From: Adam Harvey Date: Thu, 10 Nov 2022 20:30:18 -0500 Subject: [PATCH 01/10] docs: fix wording (#3542) Fixes wording in the docs page about Git being in a dirty state. The goal is to improve the end-user reading of the instructions. ... --- www/docs/errors/dirty.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/www/docs/errors/dirty.md b/www/docs/errors/dirty.md index 2116e9e68a2..857315e76f2 100644 --- a/www/docs/errors/dirty.md +++ b/www/docs/errors/dirty.md @@ -21,4 +21,4 @@ From here on, you have a couple of options: - add the file to `.gitignore` (recommended if the file is temporary and/or generated); -- change your process the build process to not touch any git tracked files. +- change your build process to not touch any git tracked files. From 25522b5c52de888a61e42914fde7719a2489814b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 11 Nov 2022 09:42:55 -0300 Subject: [PATCH 02/10] feat(deps): bump golang from `8558ae6` to `dc4f475` (#3544) Bumps golang from `8558ae6` to `dc4f475`. [![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=golang&package-manager=docker&previous-version=1.19.3-alpine&new-version=1.19.3-alpine)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 9ca4391b747..9129c5467ef 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.19.3-alpine@sha256:8558ae624304387d18694b9ea065cc9813dd4f7f9bd5073edb237541f2d0561b +FROM golang:1.19.3-alpine@sha256:dc4f4756a4fb91b6f496a958e11e00c0621130c8dfbb31ac0737b0229ad6ad9c RUN apk add --no-cache bash \ curl \ From 0a536f08fd4df847b3777a658aea75b788f8fa4a Mon Sep 17 00:00:00 2001 From: Giorgio Azzinnaro Date: Sat, 12 Nov 2022 03:35:51 +0100 Subject: [PATCH 03/10] feat: build of shared/static libraries (#3511) This PR improves the handling of shared or static libraries by GoReleaser. It uses the default behaviour of the Go compiler by appending the right extension to libraries. * `.so` and `.a` for Linux shared libraries and static libraries respectively * `.dylib` and `.a.` on Darwin * `.dll` and `.lib` on Windows (pre-existent) It does not add any configuration option to `.goreleaser.yml`, since it leverages the existing `buildmode` flag. Additionally, this PR takes care of adding the generated header file into the archive. Personally I would leverage this change to release some software both as a CLI and as a shared library. I believe others who use CGo or need interoperability with Go from other languages could benefit from this. This was previously discussed in #3497. I couldn't quite think of a proper way to add some tests to the header archiving feature. Any recommendation? --- internal/artifact/artifact.go | 12 ++++++ internal/builders/golang/build.go | 72 +++++++++++++++++++++++++++---- internal/pipe/archive/archive.go | 3 ++ internal/pipe/build/build.go | 41 ++++++++++++------ internal/pipe/build/build_test.go | 45 ++++++++++++++----- pkg/config/config.go | 13 +++--- www/docs/customization/build.md | 31 +++++++++++++ 7 files changed, 178 insertions(+), 39 deletions(-) diff --git a/internal/artifact/artifact.go b/internal/artifact/artifact.go index 06434e02c4c..c917c13c228 100644 --- a/internal/artifact/artifact.go +++ b/internal/artifact/artifact.go @@ -66,6 +66,12 @@ const ( ScoopManifest // SBOM is a Software Bill of Materials file. SBOM + // Header is a C header file, generated for CGo library builds. + Header + // CArchive is a C static library, generated via a CGo build with buildmode=c-archive. + CArchive + // CShared is a C shared library, generated via a CGo build with buildmode=c-shared. + CShared ) func (t Type) String() string { @@ -104,6 +110,12 @@ func (t Type) String() string { return "PKGBUILD" case SrcInfo: return "SRCINFO" + case Header: + return "C Header" + case CArchive: + return "C Archive Library" + case CShared: + return "C Shared Library" default: return "unknown" } diff --git a/internal/builders/golang/build.go b/internal/builders/golang/build.go index 7d76e135329..0d3c6366d7c 100644 --- a/internal/builders/golang/build.go +++ b/internal/builders/golang/build.go @@ -135,7 +135,7 @@ func (*Builder) Build(ctx *context.Context, build config.Build, options api.Opti return err } - artifact := &artifact.Artifact{ + a := &artifact.Artifact{ Type: artifact.Binary, Path: options.Path, Name: options.Name, @@ -151,6 +151,15 @@ func (*Builder) Build(ctx *context.Context, build config.Build, options api.Opti }, } + if build.Buildmode == "c-archive" { + a.Type = artifact.CArchive + ctx.Artifacts.Add(getHeaderArtifactForLibrary(build, options)) + } + if build.Buildmode == "c-shared" { + a.Type = artifact.CShared + ctx.Artifacts.Add(getHeaderArtifactForLibrary(build, options)) + } + details, err := withOverrides(ctx, build, options) if err != nil { return err @@ -167,7 +176,7 @@ func (*Builder) Build(ctx *context.Context, build config.Build, options api.Opti "GOAMD64="+options.Goamd64, ) - cmd, err := buildGoBuildLine(ctx, build, details, options, artifact, env) + cmd, err := buildGoBuildLine(ctx, build, details, options, a, env) if err != nil { return err } @@ -177,7 +186,7 @@ func (*Builder) Build(ctx *context.Context, build config.Build, options api.Opti } if build.ModTimestamp != "" { - modTimestamp, err := tmpl.New(ctx).WithEnvS(env).WithArtifact(artifact, map[string]string{}).Apply(build.ModTimestamp) + modTimestamp, err := tmpl.New(ctx).WithEnvS(env).WithArtifact(a, map[string]string{}).Apply(build.ModTimestamp) if err != nil { return err } @@ -192,7 +201,7 @@ func (*Builder) Build(ctx *context.Context, build config.Build, options api.Opti } } - ctx.Artifacts.Add(artifact) + ctx.Artifacts.Add(a) return nil } @@ -206,11 +215,12 @@ func withOverrides(ctx *context.Context, build config.Build, options api.Options if optsTarget == overrideTarget { dets := config.BuildDetails{ - Ldflags: build.BuildDetails.Ldflags, - Tags: build.BuildDetails.Tags, - Flags: build.BuildDetails.Flags, - Asmflags: build.BuildDetails.Asmflags, - Gcflags: build.BuildDetails.Gcflags, + Buildmode: build.BuildDetails.Buildmode, + Ldflags: build.BuildDetails.Ldflags, + Tags: build.BuildDetails.Tags, + Flags: build.BuildDetails.Flags, + Asmflags: build.BuildDetails.Asmflags, + Gcflags: build.BuildDetails.Gcflags, } if err := mergo.Merge(&dets, o.BuildDetails, mergo.WithOverride); err != nil { return build.BuildDetails, err @@ -228,6 +238,9 @@ func withOverrides(ctx *context.Context, build config.Build, options api.Options func buildGoBuildLine(ctx *context.Context, build config.Build, details config.BuildDetails, options api.Options, artifact *artifact.Artifact, env []string) ([]string, error) { cmd := []string{build.GoBinary, build.Command} + // tags, ldflags, and buildmode, should only appear once, warning only to avoid a breaking change + validateUniqueFlags(details) + flags, err := processFlags(ctx, artifact, env, details.Flags, "") if err != nil { return cmd, err @@ -266,10 +279,28 @@ func buildGoBuildLine(ctx *context.Context, build config.Build, details config.B cmd = append(cmd, "-ldflags="+strings.Join(ldflags, " ")) } + if details.Buildmode != "" { + cmd = append(cmd, "-buildmode="+details.Buildmode) + } + cmd = append(cmd, "-o", options.Path, build.Main) return cmd, nil } +func validateUniqueFlags(details config.BuildDetails) { + for _, flag := range details.Flags { + if strings.HasPrefix(flag, "-tags") && len(details.Tags) > 0 { + log.WithField("flag", flag).WithField("tags", details.Tags).Warn("tags is defined twice") + } + if strings.HasPrefix(flag, "-ldflags") && len(details.Ldflags) > 0 { + log.WithField("flag", flag).WithField("ldflags", details.Ldflags).Warn("ldflags is defined twice") + } + if strings.HasPrefix(flag, "-buildmode") && details.Buildmode != "" { + log.WithField("flag", flag).WithField("buildmode", details.Buildmode).Warn("buildmode is defined twice") + } + } +} + func processFlags(ctx *context.Context, a *artifact.Artifact, env, flags []string, flagPrefix string) ([]string, error) { processed := make([]string, 0, len(flags)) for _, rawFlag := range flags { @@ -366,3 +397,26 @@ func hasMain(file *ast.File) bool { } return false } + +func getHeaderArtifactForLibrary(build config.Build, options api.Options) *artifact.Artifact { + fullPathWithoutExt := strings.TrimSuffix(options.Path, options.Ext) + basePath := filepath.Base(fullPathWithoutExt) + fullPath := fullPathWithoutExt + ".h" + headerName := basePath + ".h" + + return &artifact.Artifact{ + Type: artifact.Header, + Path: fullPath, + Name: headerName, + Goos: options.Goos, + Goarch: options.Goarch, + Goamd64: options.Goamd64, + Goarm: options.Goarm, + Gomips: options.Gomips, + Extra: map[string]interface{}{ + artifact.ExtraBinary: headerName, + artifact.ExtraExt: ".h", + artifact.ExtraID: build.ID, + }, + } +} diff --git a/internal/pipe/archive/archive.go b/internal/pipe/archive/archive.go index e5219ed4b59..4b20472e638 100644 --- a/internal/pipe/archive/archive.go +++ b/internal/pipe/archive/archive.go @@ -91,6 +91,9 @@ func (Pipe) Run(ctx *context.Context) error { filter := []artifact.Filter{artifact.Or( artifact.ByType(artifact.Binary), artifact.ByType(artifact.UniversalBinary), + artifact.ByType(artifact.Header), + artifact.ByType(artifact.CArchive), + artifact.ByType(artifact.CShared), )} if len(archive.Builds) > 0 { filter = append(filter, artifact.ByIDs(archive.Builds...)) diff --git a/internal/pipe/build/build.go b/internal/pipe/build/build.go index a05f42d2ed0..003c715ed15 100644 --- a/internal/pipe/build/build.go +++ b/internal/pipe/build/build.go @@ -156,7 +156,7 @@ func doBuild(ctx *context.Context, build config.Build, opts builders.Options) er } func buildOptionsForTarget(ctx *context.Context, build config.Build, target string) (*builders.Options, error) { - ext := extFor(target, build.Flags) + ext := extFor(target, build.BuildDetails) parts := strings.Split(target, "_") if len(parts) < 2 { return nil, fmt.Errorf("%s is not a valid build target", target) @@ -211,20 +211,35 @@ func buildOptionsForTarget(ctx *context.Context, build config.Build, target stri return &buildOpts, nil } -func extFor(target string, flags config.FlagArray) string { - if strings.Contains(target, "windows") { - for _, s := range flags { - if s == "-buildmode=c-shared" { - return ".dll" - } - if s == "-buildmode=c-archive" { - return ".lib" - } - } - return ".exe" - } +func extFor(target string, build config.BuildDetails) string { if target == "js_wasm" { return ".wasm" } + + // Configure the extensions for shared and static libraries - by default .so and .a respectively - + // with overrides for Windows (.dll for shared and .lib for static) and .dylib for macOS. + buildmode := build.Buildmode + + if buildmode == "c-shared" { + if strings.Contains(target, "darwin") { + return ".dylib" + } + if strings.Contains(target, "windows") { + return ".dll" + } + return ".so" + } + + if buildmode == "c-archive" { + if strings.Contains(target, "windows") { + return ".lib" + } + return ".a" + } + + if strings.Contains(target, "windows") { + return ".exe" + } + return "" } diff --git a/internal/pipe/build/build_test.go b/internal/pipe/build/build_test.go index 7155f8fd9f1..596b0e0740a 100644 --- a/internal/pipe/build/build_test.go +++ b/internal/pipe/build/build_test.go @@ -426,24 +426,47 @@ func TestSkipBuild(t *testing.T) { require.Len(t, ctx.Artifacts.List(), 0) } +func TestExtDarwin(t *testing.T) { + require.Equal(t, "", extFor("darwin_amd64", config.BuildDetails{})) + require.Equal(t, "", extFor("darwin_arm64", config.BuildDetails{})) + require.Equal(t, "", extFor("darwin_amd64", config.BuildDetails{})) + require.Equal(t, ".dylib", extFor("darwin_amd64", config.BuildDetails{Buildmode: "c-shared"})) + require.Equal(t, ".dylib", extFor("darwin_arm64", config.BuildDetails{Buildmode: "c-shared"})) + require.Equal(t, ".a", extFor("darwin_amd64", config.BuildDetails{Buildmode: "c-archive"})) + require.Equal(t, ".a", extFor("darwin_arm64", config.BuildDetails{Buildmode: "c-archive"})) +} + +func TestExtLinux(t *testing.T) { + require.Equal(t, "", extFor("linux_amd64", config.BuildDetails{})) + require.Equal(t, "", extFor("linux_386", config.BuildDetails{})) + require.Equal(t, "", extFor("linux_amd64", config.BuildDetails{})) + require.Equal(t, ".so", extFor("linux_amd64", config.BuildDetails{Buildmode: "c-shared"})) + require.Equal(t, ".so", extFor("linux_386", config.BuildDetails{Buildmode: "c-shared"})) + require.Equal(t, ".a", extFor("linux_amd64", config.BuildDetails{Buildmode: "c-archive"})) + require.Equal(t, ".a", extFor("linux_386", config.BuildDetails{Buildmode: "c-archive"})) +} + func TestExtWindows(t *testing.T) { - require.Equal(t, ".exe", extFor("windows_amd64", config.FlagArray{})) - require.Equal(t, ".exe", extFor("windows_386", config.FlagArray{})) - require.Equal(t, ".exe", extFor("windows_amd64", config.FlagArray{"-tags=dev", "-v"})) - require.Equal(t, ".dll", extFor("windows_amd64", config.FlagArray{"-tags=dev", "-v", "-buildmode=c-shared"})) - require.Equal(t, ".dll", extFor("windows_386", config.FlagArray{"-buildmode=c-shared"})) - require.Equal(t, ".lib", extFor("windows_amd64", config.FlagArray{"-buildmode=c-archive"})) - require.Equal(t, ".lib", extFor("windows_386", config.FlagArray{"-tags=dev", "-v", "-buildmode=c-archive"})) + require.Equal(t, ".exe", extFor("windows_amd64", config.BuildDetails{})) + require.Equal(t, ".exe", extFor("windows_386", config.BuildDetails{})) + require.Equal(t, ".exe", extFor("windows_amd64", config.BuildDetails{})) + require.Equal(t, ".dll", extFor("windows_amd64", config.BuildDetails{Buildmode: "c-shared"})) + require.Equal(t, ".dll", extFor("windows_386", config.BuildDetails{Buildmode: "c-shared"})) + require.Equal(t, ".lib", extFor("windows_amd64", config.BuildDetails{Buildmode: "c-archive"})) + require.Equal(t, ".lib", extFor("windows_386", config.BuildDetails{Buildmode: "c-archive"})) } func TestExtWasm(t *testing.T) { - require.Equal(t, ".wasm", extFor("js_wasm", config.FlagArray{})) + require.Equal(t, ".wasm", extFor("js_wasm", config.BuildDetails{})) } func TestExtOthers(t *testing.T) { - require.Empty(t, "", extFor("linux_amd64", config.FlagArray{})) - require.Empty(t, "", extFor("linuxwin_386", config.FlagArray{})) - require.Empty(t, "", extFor("winasdasd_sad", config.FlagArray{})) + require.Equal(t, "", extFor("linux_amd64", config.BuildDetails{})) + require.Equal(t, "", extFor("linuxwin_386", config.BuildDetails{})) + require.Equal(t, "", extFor("winasdasd_sad", config.BuildDetails{})) + require.Equal(t, ".so", extFor("aix_amd64", config.BuildDetails{Buildmode: "c-shared"})) + require.Equal(t, ".a", extFor("android_386", config.BuildDetails{Buildmode: "c-archive"})) + require.Equal(t, ".so", extFor("winasdasd_sad", config.BuildDetails{Buildmode: "c-shared"})) } func TestTemplate(t *testing.T) { diff --git a/pkg/config/config.go b/pkg/config/config.go index 6060202c23b..29a803b9f56 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -333,12 +333,13 @@ type BuildDetailsOverride struct { } type BuildDetails struct { - Ldflags StringArray `yaml:"ldflags,omitempty" json:"ldflags,omitempty"` - Tags FlagArray `yaml:"tags,omitempty" json:"tags,omitempty"` - Flags FlagArray `yaml:"flags,omitempty" json:"flags,omitempty"` - Asmflags StringArray `yaml:"asmflags,omitempty" json:"asmflags,omitempty"` - Gcflags StringArray `yaml:"gcflags,omitempty" json:"gcflags,omitempty"` - Env []string `yaml:"env,omitempty" json:"env,omitempty"` + Buildmode string `yaml:"buildmode,omitempty" json:"buildmode,omitempty"` + Ldflags StringArray `yaml:"ldflags,omitempty" json:"ldflags,omitempty"` + Tags FlagArray `yaml:"tags,omitempty" json:"tags,omitempty"` + Flags FlagArray `yaml:"flags,omitempty" json:"flags,omitempty"` + Asmflags StringArray `yaml:"asmflags,omitempty" json:"asmflags,omitempty"` + Gcflags StringArray `yaml:"gcflags,omitempty" json:"gcflags,omitempty"` + Env []string `yaml:"env,omitempty" json:"env,omitempty"` } type BuildHookConfig struct { diff --git a/www/docs/customization/build.md b/www/docs/customization/build.md index e260dcde1c7..d8e5a9d2ce2 100644 --- a/www/docs/customization/build.md +++ b/www/docs/customization/build.md @@ -51,6 +51,11 @@ builds: - -s -w -X main.build={{.Version}} - ./usemsan=-msan + # Custom Go build mode. + # `c-shared` and `c-archive` configure the publishing of the header and set the correct extension. + # Default is empty. + buildmode: c-shared + # Custom build tags templates. # Default is empty. tags: @@ -517,3 +522,29 @@ will evaluate to the list of first class ports as defined in the Go wiki. You can read more about it [here](https://github.com/golang/go/wiki/PortingPolicy#first-class-ports). + +## Building shared or static libraries + +GoReleaser supports compiling and releasing C shared or static libraries, +by configuring the [Go build mode](https://pkg.go.dev/cmd/go#hdr-Build_modes). + +This can be set with `buildmode` in your build. It currently supports `c-shared` and `c-archive`. +Other values will transparently be applied to the build line (via the `-buildmode` flag), +but GoReleaser will not attempt to configure any additional logic. + +As of today, a template may not be applied to this field. + +GoReleaser will: + +* set the correct file extension for the target OS. +* package the generated header file (`.h`) in the release bundle. + +```yaml +# .goreleaser.yaml +builds: + - + id: "my-library" + + # Configure the buildmode flag to output a shared library + buildmode: "c-shared" # or "c-archive" for a static library +``` From 59948928e5da5a2943834fa0acb5b93f7ba9bac6 Mon Sep 17 00:00:00 2001 From: actions-user Date: Sat, 12 Nov 2022 02:37:32 +0000 Subject: [PATCH 04/10] chore: docs auto-update --- www/docs/static/schema.json | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/www/docs/static/schema.json b/www/docs/static/schema.json index e7dc0bb516d..277295cbaac 100644 --- a/www/docs/static/schema.json +++ b/www/docs/static/schema.json @@ -329,6 +329,9 @@ "no_main_check": { "type": "boolean" }, + "buildmode": { + "type": "string" + }, "ldflags": { "$ref": "#/$defs/StringArray" }, @@ -377,6 +380,9 @@ "goamd64": { "type": "string" }, + "buildmode": { + "type": "string" + }, "ldflags": { "$ref": "#/$defs/StringArray" }, From e47bad4997bfa489b072ad42ba682a90abe18a9e Mon Sep 17 00:00:00 2001 From: Carlos A Becker Date: Fri, 11 Nov 2022 23:39:15 -0300 Subject: [PATCH 05/10] docs: small improvement --- www/docs/customization/build.md | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/www/docs/customization/build.md b/www/docs/customization/build.md index d8e5a9d2ce2..a069958c41e 100644 --- a/www/docs/customization/build.md +++ b/www/docs/customization/build.md @@ -52,7 +52,12 @@ builds: - ./usemsan=-msan # Custom Go build mode. - # `c-shared` and `c-archive` configure the publishing of the header and set the correct extension. + # + # Valid options: + # - `c-shared` + # - `c-archive` + # + # Since GoReleaser v1.13. # Default is empty. buildmode: c-shared @@ -525,20 +530,23 @@ You can read more about it ## Building shared or static libraries -GoReleaser supports compiling and releasing C shared or static libraries, -by configuring the [Go build mode](https://pkg.go.dev/cmd/go#hdr-Build_modes). +> Since: v1.13.0 -This can be set with `buildmode` in your build. It currently supports `c-shared` and `c-archive`. -Other values will transparently be applied to the build line (via the `-buildmode` flag), -but GoReleaser will not attempt to configure any additional logic. +GoReleaser supports compiling and releasing C shared or static libraries, by +configuring the [Go build mode](https://pkg.go.dev/cmd/go#hdr-Build_modes). -As of today, a template may not be applied to this field. +This can be set with `buildmode` in your build. +It now supports `c-shared` and `c-archive`. Other values will transparently be +applied to the build line (via the `-buildmode` flag), but GoReleaser will not +attempt to configure any additional logic. GoReleaser will: * set the correct file extension for the target OS. * package the generated header file (`.h`) in the release bundle. +Example usage: + ```yaml # .goreleaser.yaml builds: From 22a7a9a8357703e95a2302270d7e6f178343e02b Mon Sep 17 00:00:00 2001 From: Carlos A Becker Date: Fri, 11 Nov 2022 23:42:45 -0300 Subject: [PATCH 06/10] refactor: small improvements Signed-off-by: Carlos A Becker --- internal/pipe/build/build.go | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/internal/pipe/build/build.go b/internal/pipe/build/build.go index 003c715ed15..d2ae35d53c8 100644 --- a/internal/pipe/build/build.go +++ b/internal/pipe/build/build.go @@ -212,15 +212,10 @@ func buildOptionsForTarget(ctx *context.Context, build config.Build, target stri } func extFor(target string, build config.BuildDetails) string { - if target == "js_wasm" { - return ".wasm" - } - // Configure the extensions for shared and static libraries - by default .so and .a respectively - // with overrides for Windows (.dll for shared and .lib for static) and .dylib for macOS. - buildmode := build.Buildmode - - if buildmode == "c-shared" { + switch build.Buildmode { + case "c-shared": if strings.Contains(target, "darwin") { return ".dylib" } @@ -228,15 +223,17 @@ func extFor(target string, build config.BuildDetails) string { return ".dll" } return ".so" - } - - if buildmode == "c-archive" { + case "c-archive": if strings.Contains(target, "windows") { return ".lib" } return ".a" } + if target == "js_wasm" { + return ".wasm" + } + if strings.Contains(target, "windows") { return ".exe" } From f2281e8ff237e679c6f461a05d1eda7e3a426a3e Mon Sep 17 00:00:00 2001 From: Fabio Ribeiro Date: Sat, 12 Nov 2022 03:52:32 +0100 Subject: [PATCH 07/10] feat: chocolatey support (#3509) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR adds support for generating the structure used to pack and push Chocolatey Packages. And will solve the #3154 Is not ready for merge yet, but has the main structure, and ready for comments. Accordingly to Chocolatey, in order to build a package, it's necessary a `.nuspec` and `chocolateyinstall.ps1` files at least, having these ones, we could pack and distribute without adding the binary inside the final package and that was implemented here. To complete, will be necessary to define the package build and distribute, however will be required to have Chocolatey installed (Windows Only). One of alternatives that I thought was, publish the files like Scoop and Brew in a separate repository, and there we could use `chocolatey` through [crazy-max/ghaction-chocolatey](https://github.com/crazy-max/ghaction-chocolatey). Chocolatey has a lot of good examples of repositories: https://github.com/chocolatey-community/chocolatey-packages/tree/master/automatic/curl A final compilation of the missing parts: - [x] How to pack and push (chocolatey) - [x] Documentation Sorry for the long description😄 All feedback very welcome! Co-authored-by: Carlos Alexandro Becker --- internal/artifact/artifact.go | 4 + internal/pipe/chocolatey/chocolatey.go | 340 ++++++++++++++++++ internal/pipe/chocolatey/chocolatey_test.go | 330 +++++++++++++++++ internal/pipe/chocolatey/nuspec.go | 87 +++++ internal/pipe/chocolatey/nuspec_test.go | 45 +++ internal/pipe/chocolatey/template.go | 38 ++ .../testdata/TestNuspecBytes.nuspec.golden | 29 ++ .../testdata/Test_buildNuspec.nuspec.golden | 20 ++ .../Test_buildTemplate.script.ps1.golden | 20 ++ internal/pipe/publish/publish.go | 2 + internal/pipeline/pipeline.go | 3 + pkg/config/config.go | 35 ++ pkg/defaults/defaults.go | 2 + www/docs/customization/chocolatey.md | 133 +++++++ www/docs/static/schema.json | 102 ++++++ www/mkdocs.yml | 1 + 16 files changed, 1191 insertions(+) create mode 100644 internal/pipe/chocolatey/chocolatey.go create mode 100644 internal/pipe/chocolatey/chocolatey_test.go create mode 100644 internal/pipe/chocolatey/nuspec.go create mode 100644 internal/pipe/chocolatey/nuspec_test.go create mode 100644 internal/pipe/chocolatey/template.go create mode 100644 internal/pipe/chocolatey/testdata/TestNuspecBytes.nuspec.golden create mode 100644 internal/pipe/chocolatey/testdata/Test_buildNuspec.nuspec.golden create mode 100644 internal/pipe/chocolatey/testdata/Test_buildTemplate.script.ps1.golden create mode 100644 www/docs/customization/chocolatey.md diff --git a/internal/artifact/artifact.go b/internal/artifact/artifact.go index c917c13c228..b351339b389 100644 --- a/internal/artifact/artifact.go +++ b/internal/artifact/artifact.go @@ -66,6 +66,8 @@ const ( ScoopManifest // SBOM is a Software Bill of Materials file. SBOM + // PublishableChocolatey is a chocolatey package yet to be published. + PublishableChocolatey // Header is a C header file, generated for CGo library builds. Header // CArchive is a C static library, generated via a CGo build with buildmode=c-archive. @@ -110,6 +112,8 @@ func (t Type) String() string { return "PKGBUILD" case SrcInfo: return "SRCINFO" + case PublishableChocolatey: + return "Chocolatey" case Header: return "C Header" case CArchive: diff --git a/internal/pipe/chocolatey/chocolatey.go b/internal/pipe/chocolatey/chocolatey.go new file mode 100644 index 00000000000..bf0a47cce15 --- /dev/null +++ b/internal/pipe/chocolatey/chocolatey.go @@ -0,0 +1,340 @@ +package chocolatey + +import ( + "bytes" + "errors" + "fmt" + "os" + "os/exec" + "path/filepath" + "text/template" + + "github.com/caarlos0/log" + "github.com/goreleaser/goreleaser/internal/artifact" + "github.com/goreleaser/goreleaser/internal/client" + "github.com/goreleaser/goreleaser/internal/pipe" + "github.com/goreleaser/goreleaser/internal/tmpl" + "github.com/goreleaser/goreleaser/pkg/config" + "github.com/goreleaser/goreleaser/pkg/context" +) + +// nuget package extension. +const nupkgFormat = "nupkg" + +// custom chocolatey config placed in artifact. +const chocoConfigExtra = "ChocolateyConfig" + +// cmd represents a command executor. +var cmd cmder = stdCmd{} + +// Pipe for chocolatey packaging. +type Pipe struct{} + +func (Pipe) String() string { return "chocolatey packages" } +func (Pipe) Skip(ctx *context.Context) bool { return len(ctx.Config.Chocolateys) == 0 } + +// Default sets the pipe defaults. +func (Pipe) Default(ctx *context.Context) error { + for i := range ctx.Config.Chocolateys { + choco := &ctx.Config.Chocolateys[i] + + if choco.Name == "" { + choco.Name = ctx.Config.ProjectName + } + + if choco.Title == "" { + choco.Title = ctx.Config.ProjectName + } + + if choco.Goamd64 == "" { + choco.Goamd64 = "v1" + } + + if choco.SourceRepo == "" { + choco.SourceRepo = "https://push.chocolatey.org/" + } + } + + return nil +} + +// Run the pipe. +func (Pipe) Run(ctx *context.Context) error { + client, err := client.New(ctx) + if err != nil { + return err + } + + for _, choco := range ctx.Config.Chocolateys { + if err := doRun(ctx, client, choco); err != nil { + return err + } + } + + return nil +} + +// Publish packages. +func (Pipe) Publish(ctx *context.Context) error { + if ctx.SkipPublish { + return pipe.ErrSkipPublishEnabled + } + + artifacts := ctx.Artifacts.Filter( + artifact.ByType(artifact.PublishableChocolatey), + ).List() + + for _, artifact := range artifacts { + if err := doPush(ctx, artifact); err != nil { + return err + } + } + + return nil +} + +func doRun(ctx *context.Context, cl client.Client, choco config.Chocolatey) error { + filters := []artifact.Filter{ + artifact.ByGoos("windows"), + artifact.ByType(artifact.UploadableArchive), + artifact.Or( + artifact.And( + artifact.ByGoarch("amd64"), + artifact.ByGoamd64(choco.Goamd64), + ), + artifact.ByGoarch("386"), + ), + } + + if len(choco.IDs) > 0 { + filters = append(filters, artifact.ByIDs(choco.IDs...)) + } + + artifacts := ctx.Artifacts. + Filter(artifact.And(filters...)). + List() + + if len(artifacts) == 0 { + return errors.New("chocolatey requires a windows build and archive") + } + + // folderDir is the directory that then will be compressed to make the + // chocolatey package. + folderPath := filepath.Join(ctx.Config.Dist, choco.Name+".choco") + toolsPath := filepath.Join(folderPath, "tools") + if err := os.MkdirAll(toolsPath, 0o755); err != nil { + return err + } + + nuspecFile := filepath.Join(folderPath, choco.Name+".nuspec") + nuspec, err := buildNuspec(ctx, choco) + if err != nil { + return err + } + + if err = os.WriteFile(nuspecFile, nuspec, 0o644); err != nil { + return err + } + + data, err := dataFor(ctx, cl, choco, artifacts) + if err != nil { + return err + } + + script, err := buildTemplate(choco.Name, scriptTemplate, data) + if err != nil { + return err + } + + scriptFile := filepath.Join(toolsPath, "chocolateyinstall.ps1") + log.WithField("file", scriptFile).Debug("creating") + if err = os.WriteFile(scriptFile, script, 0o644); err != nil { + return err + } + + log.WithField("nuspec", nuspecFile).Info("packing") + out, err := cmd.Exec(ctx, "choco", "pack", nuspecFile, "--out", ctx.Config.Dist) + if err != nil { + return fmt.Errorf("failed to generate chocolatey package: %w: %s", err, string(out)) + } + + if choco.SkipPublish { + return nil + } + + pkgFile := fmt.Sprintf("%s.%s.%s", choco.Name, ctx.Version, nupkgFormat) + + ctx.Artifacts.Add(&artifact.Artifact{ + Type: artifact.PublishableChocolatey, + Path: filepath.Join(ctx.Config.Dist, pkgFile), + Name: pkgFile, + Extra: map[string]interface{}{ + artifact.ExtraFormat: nupkgFormat, + chocoConfigExtra: choco, + }, + }) + + return nil +} + +func doPush(ctx *context.Context, art *artifact.Artifact) error { + choco, err := artifact.Extra[config.Chocolatey](*art, chocoConfigExtra) + if err != nil { + return err + } + + key, err := tmpl.New(ctx).Apply(choco.APIKey) + if err != nil { + return err + } + + log := log.WithField("name", choco.Name) + if key == "" { + log.Warn("skip pushing: no api key") + return nil + } + + log.Info("pushing package") + + args := []string{ + "push", + "--source", + choco.SourceRepo, + "--api-key", + key, + art.Path, + } + + if out, err := cmd.Exec(ctx, "choco", args...); err != nil { + return fmt.Errorf("failed to push chocolatey package: %w: %s", err, string(out)) + } + + log.Info("package sent") + + return nil +} + +func buildNuspec(ctx *context.Context, choco config.Chocolatey) ([]byte, error) { + tpl := tmpl.New(ctx) + summary, err := tpl.Apply(choco.Summary) + if err != nil { + return nil, err + } + + description, err := tpl.Apply(choco.Description) + if err != nil { + return nil, err + } + + releaseNotes, err := tpl.Apply(choco.ReleaseNotes) + if err != nil { + return nil, err + } + + m := &Nuspec{ + Xmlns: schema, + Metadata: Metadata{ + ID: choco.Name, + Version: ctx.Version, + PackageSourceURL: choco.PackageSourceURL, + Owners: choco.Owners, + Title: choco.Title, + Authors: choco.Authors, + ProjectURL: choco.ProjectURL, + IconURL: choco.IconURL, + Copyright: choco.Copyright, + LicenseURL: choco.LicenseURL, + RequireLicenseAcceptance: choco.RequireLicenseAcceptance, + ProjectSourceURL: choco.ProjectSourceURL, + DocsURL: choco.DocsURL, + BugTrackerURL: choco.BugTrackerURL, + Tags: choco.Tags, + Summary: summary, + Description: description, + ReleaseNotes: releaseNotes, + }, + Files: Files{File: []File{ + {Source: "tools\\**", Target: "tools"}, + }}, + } + + deps := make([]Dependency, len(choco.Dependencies)) + for i, dep := range choco.Dependencies { + deps[i] = Dependency{ID: dep.ID, Version: dep.Version} + } + + if len(deps) > 0 { + m.Metadata.Dependencies = &Dependencies{Dependency: deps} + } + + return m.Bytes() +} + +func buildTemplate(name string, text string, data templateData) ([]byte, error) { + tp, err := template.New(name).Parse(text) + if err != nil { + return nil, err + } + + var out bytes.Buffer + if err = tp.Execute(&out, data); err != nil { + return nil, err + } + + return out.Bytes(), nil +} + +func dataFor(ctx *context.Context, cl client.Client, choco config.Chocolatey, artifacts []*artifact.Artifact) (templateData, error) { + result := templateData{} + + if choco.URLTemplate == "" { + url, err := cl.ReleaseURLTemplate(ctx) + if err != nil { + return result, err + } + + choco.URLTemplate = url + } + + for _, artifact := range artifacts { + sum, err := artifact.Checksum("sha256") + if err != nil { + return result, err + } + + url, err := tmpl.New(ctx). + WithArtifact(artifact, map[string]string{}). + Apply(choco.URLTemplate) + if err != nil { + return result, err + } + + pkg := releasePackage{ + DownloadURL: url, + Checksum: sum, + Arch: artifact.Goarch, + } + + result.Packages = append(result.Packages, pkg) + } + + return result, nil +} + +// cmder is a special interface to execute external commands. +// +// The intention is to be used to wrap the standard exec and provide the +// ability to create a fake one for testing. +type cmder interface { + // Exec executes an command. + Exec(*context.Context, string, ...string) ([]byte, error) +} + +// stdCmd uses the standard golang exec. +type stdCmd struct{} + +var _ cmder = &stdCmd{} + +func (stdCmd) Exec(ctx *context.Context, name string, args ...string) ([]byte, error) { + return exec.CommandContext(ctx, name, args...).CombinedOutput() +} diff --git a/internal/pipe/chocolatey/chocolatey_test.go b/internal/pipe/chocolatey/chocolatey_test.go new file mode 100644 index 00000000000..c89f0049757 --- /dev/null +++ b/internal/pipe/chocolatey/chocolatey_test.go @@ -0,0 +1,330 @@ +package chocolatey + +import ( + "errors" + "os" + "path/filepath" + "testing" + + "github.com/goreleaser/goreleaser/internal/artifact" + "github.com/goreleaser/goreleaser/internal/client" + "github.com/goreleaser/goreleaser/internal/golden" + "github.com/goreleaser/goreleaser/internal/testlib" + "github.com/goreleaser/goreleaser/pkg/config" + "github.com/goreleaser/goreleaser/pkg/context" + "github.com/stretchr/testify/require" +) + +func TestDescription(t *testing.T) { + require.NotEmpty(t, Pipe{}.String()) +} + +func TestSkip(t *testing.T) { + ctx := context.New(config.Project{}) + require.True(t, Pipe{}.Skip(ctx)) +} + +func TestDefault(t *testing.T) { + testlib.Mktmp(t) + + ctx := &context.Context{ + TokenType: context.TokenTypeGitHub, + Config: config.Project{ + ProjectName: "myproject", + Chocolateys: []config.Chocolatey{ + {}, + }, + }, + } + + require.NoError(t, Pipe{}.Default(ctx)) + require.Equal(t, ctx.Config.ProjectName, ctx.Config.Chocolateys[0].Name) + require.Equal(t, ctx.Config.ProjectName, ctx.Config.Chocolateys[0].Title) + require.Equal(t, "v1", ctx.Config.Chocolateys[0].Goamd64) +} + +func Test_doRun(t *testing.T) { + folder := t.TempDir() + file := filepath.Join(folder, "archive") + require.NoError(t, os.WriteFile(file, []byte("lorem ipsum"), 0o644)) + + tests := []struct { + name string + choco config.Chocolatey + exec func() ([]byte, error) + published int + err string + }{ + { + name: "no artifacts", + choco: config.Chocolatey{ + Name: "app", + IDs: []string{"no-app"}, + Goamd64: "v1", + }, + err: "chocolatey requires a windows build and archive", + }, + { + name: "choco command not found", + choco: config.Chocolatey{ + Name: "app", + Goamd64: "v1", + }, + exec: func() ([]byte, error) { + return nil, errors.New(`exec: "choco.exe": executable file not found in $PATH`) + }, + err: `failed to generate chocolatey package: exec: "choco.exe": executable file not found in $PATH: `, + }, + { + name: "skip publish", + choco: config.Chocolatey{ + Name: "app", + Goamd64: "v1", + SkipPublish: true, + }, + exec: func() ([]byte, error) { + return []byte("success"), nil + }, + }, + { + name: "success", + choco: config.Chocolatey{ + Name: "app", + Goamd64: "v1", + }, + exec: func() ([]byte, error) { + return []byte("success"), nil + }, + published: 1, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cmd = fakeCmd{execFn: tt.exec} + t.Cleanup(func() { + cmd = stdCmd{} + }) + + ctx := &context.Context{ + Git: context.GitInfo{ + CurrentTag: "v1.0.1", + }, + Version: "1.0.1", + Artifacts: artifact.New(), + Config: config.Project{ + Dist: folder, + ProjectName: "run-all", + }, + } + + ctx.Artifacts.Add(&artifact.Artifact{ + Name: "app_1.0.1_windows_amd64.zip", + Path: file, + Goos: "windows", + Goarch: "amd64", + Goamd64: "v1", + Type: artifact.UploadableArchive, + Extra: map[string]interface{}{ + artifact.ExtraID: "app", + artifact.ExtraFormat: "zip", + }, + }) + + client := client.NewMock() + got := doRun(ctx, client, tt.choco) + + var err string + if got != nil { + err = got.Error() + } + if tt.err != err { + t.Errorf("Unexpected error: %s (expected %s)", err, tt.err) + } + + list := ctx.Artifacts.Filter(artifact.ByType(artifact.PublishableChocolatey)).List() + require.Len(t, list, tt.published) + }) + } +} + +func Test_buildNuspec(t *testing.T) { + ctx := &context.Context{ + Version: "1.12.3", + } + choco := config.Chocolatey{ + Name: "goreleaser", + IDs: []string{}, + Title: "GoReleaser", + Authors: "caarlos0", + ProjectURL: "https://goreleaser.com/", + Tags: "go docker homebrew golang package", + Summary: "Deliver Go binaries as fast and easily as possible", + Description: "GoReleaser builds Go binaries for several platforms, creates a GitHub release and then pushes a Homebrew formula to a tap repository. All that wrapped in your favorite CI.", + Dependencies: []config.ChocolateyDependency{ + {ID: "nfpm"}, + }, + } + + out, err := buildNuspec(ctx, choco) + require.NoError(t, err) + + golden.RequireEqualExt(t, out, ".nuspec") +} + +func Test_buildTemplate(t *testing.T) { + folder := t.TempDir() + file := filepath.Join(folder, "archive") + require.NoError(t, os.WriteFile(file, []byte("lorem ipsum"), 0o644)) + + ctx := &context.Context{ + Version: "1.0.0", + Git: context.GitInfo{ + CurrentTag: "v1.0.0", + }, + } + + artifacts := []*artifact.Artifact{ + { + Name: "app_1.0.0_windows_386.zip", + Goos: "windows", + Goarch: "386", + Goamd64: "v1", + Path: file, + }, + { + Name: "app_1.0.0_windows_amd64.zip", + Goos: "windows", + Goarch: "amd64", + Goamd64: "v1", + Path: file, + }, + } + + choco := config.Chocolatey{ + Name: "app", + } + + client := client.NewMock() + + data, err := dataFor(ctx, client, choco, artifacts) + if err != nil { + t.Error(err) + } + + out, err := buildTemplate(choco.Name, scriptTemplate, data) + require.NoError(t, err) + + golden.RequireEqualExt(t, out, ".script.ps1") +} + +func TestPublish(t *testing.T) { + folder := t.TempDir() + file := filepath.Join(folder, "archive") + require.NoError(t, os.WriteFile(file, []byte("lorem ipsum"), 0o644)) + + tests := []struct { + name string + artifacts []artifact.Artifact + exec func() ([]byte, error) + skip bool + err string + }{ + { + name: "skip publish", + skip: true, + err: "publishing is disabled", + }, + { + name: "no artifacts", + }, + { + name: "no api key", + artifacts: []artifact.Artifact{ + { + Type: artifact.PublishableChocolatey, + Name: "app.1.0.1.nupkg", + Extra: map[string]interface{}{ + artifact.ExtraFormat: nupkgFormat, + chocoConfigExtra: config.Chocolatey{}, + }, + }, + }, + }, + { + name: "push error", + artifacts: []artifact.Artifact{ + { + Type: artifact.PublishableChocolatey, + Name: "app.1.0.1.nupkg", + Extra: map[string]interface{}{ + artifact.ExtraFormat: nupkgFormat, + chocoConfigExtra: config.Chocolatey{ + APIKey: "abcd", + }, + }, + }, + }, + exec: func() ([]byte, error) { + return nil, errors.New(`unable to push`) + }, + err: "failed to push chocolatey package: unable to push: ", + }, + { + name: "success", + artifacts: []artifact.Artifact{ + { + Type: artifact.PublishableChocolatey, + Name: "app.1.0.1.nupkg", + Extra: map[string]interface{}{ + artifact.ExtraFormat: nupkgFormat, + chocoConfigExtra: config.Chocolatey{ + APIKey: "abcd", + }, + }, + }, + }, + exec: func() ([]byte, error) { + return []byte("success"), nil + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cmd = fakeCmd{execFn: tt.exec} + t.Cleanup(func() { + cmd = stdCmd{} + }) + + ctx := &context.Context{ + SkipPublish: tt.skip, + Artifacts: artifact.New(), + } + + for _, artifact := range tt.artifacts { + ctx.Artifacts.Add(&artifact) + } + + got := Pipe{}.Publish(ctx) + + var err string + if got != nil { + err = got.Error() + } + if tt.err != err { + t.Errorf("Unexpected error: %s (expected %s)", err, tt.err) + } + }) + } +} + +type fakeCmd struct { + execFn func() ([]byte, error) +} + +var _ cmder = fakeCmd{} + +func (f fakeCmd) Exec(ctx *context.Context, name string, args ...string) ([]byte, error) { + return f.execFn() +} diff --git a/internal/pipe/chocolatey/nuspec.go b/internal/pipe/chocolatey/nuspec.go new file mode 100644 index 00000000000..85d6637ac78 --- /dev/null +++ b/internal/pipe/chocolatey/nuspec.go @@ -0,0 +1,87 @@ +package chocolatey + +import ( + "bytes" + "encoding/xml" + "strings" +) + +const schema = "http://schemas.microsoft.com/packaging/2015/06/nuspec.xsd" + +// Nuspec represents a Nuget/Chocolatey Nuspec. +// More info: https://learn.microsoft.com/en-us/nuget/reference/nuspec +// https://docs.chocolatey.org/en-us/create/create-packages +type Nuspec struct { + XMLName xml.Name `xml:"package"` + Xmlns string `xml:"xmlns,attr,omitempty"` + Metadata Metadata `xml:"metadata"` + Files Files `xml:"files,omitempty"` +} + +// Metadata contains information about a single package. +type Metadata struct { + ID string `xml:"id"` + Version string `xml:"version"` + PackageSourceURL string `xml:"packageSourceUrl,omitempty"` + Owners string `xml:"owners,omitempty"` + Title string `xml:"title,omitempty"` + Authors string `xml:"authors"` + ProjectURL string `xml:"projectUrl,omitempty"` + IconURL string `xml:"iconUrl,omitempty"` + Copyright string `xml:"copyright,omitempty"` + LicenseURL string `xml:"licenseUrl,omitempty"` + RequireLicenseAcceptance bool `xml:"requireLicenseAcceptance"` + ProjectSourceURL string `xml:"projectSourceUrl,omitempty"` + DocsURL string `xml:"docsUrl,omitempty"` + BugTrackerURL string `xml:"bugTrackerUrl,omitempty"` + Tags string `xml:"tags,omitempty"` + Summary string `xml:"summary,omitempty"` + Description string `xml:"description"` + ReleaseNotes string `xml:"releaseNotes,omitempty"` + Dependencies *Dependencies `xml:"dependencies,omitempty"` +} + +// Dependency represents a dependency element. +type Dependency struct { + ID string `xml:"id,attr"` + Version string `xml:"version,attr,omitempty"` +} + +// Dependencies represents a collection zero or more dependency elements. +type Dependencies struct { + Dependency []Dependency `xml:"dependency"` +} + +// File represents a file to be copied. +type File struct { + Source string `xml:"src,attr"` + Target string `xml:"target,attr,omitempty"` +} + +// Files represents files that will be copied during packaging. +type Files struct { + File []File `xml:"file"` +} + +// Bytes marshals the Nuspec into XML format and return as []byte. +func (m *Nuspec) Bytes() ([]byte, error) { + b := &bytes.Buffer{} + b.WriteString(strings.ToLower(xml.Header)) + + enc := xml.NewEncoder(b) + enc.Indent("", " ") + + if err := enc.Encode(m); err != nil { + return nil, err + } + + out := b.Bytes() + + // Follows the nuget specification of self-closing xml tags. + tags := []string{"dependency", "file"} + for _, tag := range tags { + out = bytes.ReplaceAll(out, []byte(">"), []byte(" />")) + } + + return out, nil +} diff --git a/internal/pipe/chocolatey/nuspec_test.go b/internal/pipe/chocolatey/nuspec_test.go new file mode 100644 index 00000000000..3d51e0e1b57 --- /dev/null +++ b/internal/pipe/chocolatey/nuspec_test.go @@ -0,0 +1,45 @@ +package chocolatey + +import ( + "testing" + + "github.com/goreleaser/goreleaser/internal/golden" + "github.com/stretchr/testify/require" +) + +func TestNuspecBytes(t *testing.T) { + m := &Nuspec{ + Xmlns: schema, + Metadata: Metadata{ + ID: "goreleaser", + Version: "1.12.3", + PackageSourceURL: "https://github.com/goreleaser/goreleaser", + Owners: "caarlos0", + Title: "GoReleaser", + Authors: "caarlos0", + ProjectURL: "https://goreleaser.com/", + IconURL: "https://raw.githubusercontent.com/goreleaser/goreleaser/main/www/docs/static/avatar.png", + Copyright: "2016-2022 Carlos Alexandro Becker", + LicenseURL: "https://github.com/goreleaser/goreleaser/blob/main/LICENSE.md", + RequireLicenseAcceptance: true, + ProjectSourceURL: "https://github.com/goreleaser/goreleaser", + DocsURL: "https://github.com/goreleaser/goreleaser/blob/main/README.md", + BugTrackerURL: "https://github.com/goreleaser/goreleaser/issues", + Tags: "go docker homebrew golang package", + Summary: "Deliver Go binaries as fast and easily as possible", + Description: "GoReleaser builds Go binaries for several platforms, creates a GitHub release and then pushes a Homebrew formula to a tap repository. All that wrapped in your favorite CI.", + ReleaseNotes: "This tag is only to keep version parity with the pro version, which does have a couple of bugfixes.", + Dependencies: &Dependencies{Dependency: []Dependency{ + {ID: "nfpm", Version: "2.20.0"}, + }}, + }, + Files: Files{File: []File{ + {Source: "tools\\**", Target: "tools"}, + }}, + } + + out, err := m.Bytes() + require.NoError(t, err) + + golden.RequireEqualExt(t, out, ".nuspec") +} diff --git a/internal/pipe/chocolatey/template.go b/internal/pipe/chocolatey/template.go new file mode 100644 index 00000000000..252950f1cbc --- /dev/null +++ b/internal/pipe/chocolatey/template.go @@ -0,0 +1,38 @@ +package chocolatey + +type templateData struct { + Packages []releasePackage +} + +type releasePackage struct { + DownloadURL string + Checksum string + Arch string +} + +const scriptTemplate = `# This file was generated by GoReleaser. DO NOT EDIT. +$ErrorActionPreference = 'Stop'; + +$version = $env:chocolateyPackageVersion +$packageName = $env:chocolateyPackageName +$toolsDir = "$(Split-Path -parent $MyInvocation.MyCommand.Definition)" + +$packageArgs = @{ + packageName = $packageName + unzipLocation = $toolsDir + fileType = 'exe' + {{- range $release := .Packages }} + {{- if eq $release.Arch "amd64" }} + url64bit = '{{ $release.DownloadURL }}' + checksum64 = '{{ $release.Checksum }}' + checksumType64 = 'sha256' + {{- else }} + url = '{{ $release.DownloadURL }}' + checksum = '{{ $release.Checksum }}' + checksumType = 'sha256' + {{- end }} + {{- end }} +} + +Install-ChocolateyZipPackage @packageArgs +` diff --git a/internal/pipe/chocolatey/testdata/TestNuspecBytes.nuspec.golden b/internal/pipe/chocolatey/testdata/TestNuspecBytes.nuspec.golden new file mode 100644 index 00000000000..02968097568 --- /dev/null +++ b/internal/pipe/chocolatey/testdata/TestNuspecBytes.nuspec.golden @@ -0,0 +1,29 @@ + + + + goreleaser + 1.12.3 + https://github.com/goreleaser/goreleaser + caarlos0 + GoReleaser + caarlos0 + https://goreleaser.com/ + https://raw.githubusercontent.com/goreleaser/goreleaser/main/www/docs/static/avatar.png + 2016-2022 Carlos Alexandro Becker + https://github.com/goreleaser/goreleaser/blob/main/LICENSE.md + true + https://github.com/goreleaser/goreleaser + https://github.com/goreleaser/goreleaser/blob/main/README.md + https://github.com/goreleaser/goreleaser/issues + go docker homebrew golang package + Deliver Go binaries as fast and easily as possible + GoReleaser builds Go binaries for several platforms, creates a GitHub release and then pushes a Homebrew formula to a tap repository. All that wrapped in your favorite CI. + This tag is only to keep version parity with the pro version, which does have a couple of bugfixes. + + + + + + + + \ No newline at end of file diff --git a/internal/pipe/chocolatey/testdata/Test_buildNuspec.nuspec.golden b/internal/pipe/chocolatey/testdata/Test_buildNuspec.nuspec.golden new file mode 100644 index 00000000000..7601fef7ef6 --- /dev/null +++ b/internal/pipe/chocolatey/testdata/Test_buildNuspec.nuspec.golden @@ -0,0 +1,20 @@ + + + + goreleaser + 1.12.3 + GoReleaser + caarlos0 + https://goreleaser.com/ + false + go docker homebrew golang package + Deliver Go binaries as fast and easily as possible + GoReleaser builds Go binaries for several platforms, creates a GitHub release and then pushes a Homebrew formula to a tap repository. All that wrapped in your favorite CI. + + + + + + + + \ No newline at end of file diff --git a/internal/pipe/chocolatey/testdata/Test_buildTemplate.script.ps1.golden b/internal/pipe/chocolatey/testdata/Test_buildTemplate.script.ps1.golden new file mode 100644 index 00000000000..779da005c22 --- /dev/null +++ b/internal/pipe/chocolatey/testdata/Test_buildTemplate.script.ps1.golden @@ -0,0 +1,20 @@ +# This file was generated by GoReleaser. DO NOT EDIT. +$ErrorActionPreference = 'Stop'; + +$version = $env:chocolateyPackageVersion +$packageName = $env:chocolateyPackageName +$toolsDir = "$(Split-Path -parent $MyInvocation.MyCommand.Definition)" + +$packageArgs = @{ + packageName = $packageName + unzipLocation = $toolsDir + fileType = 'exe' + url = 'https://dummyhost/download/v1.0.0/app_1.0.0_windows_386.zip' + checksum = '5e2bf57d3f40c4b6df69daf1936cb766f832374b4fc0259a7cbff06e2f70f269' + checksumType = 'sha256' + url64bit = 'https://dummyhost/download/v1.0.0/app_1.0.0_windows_amd64.zip' + checksum64 = '5e2bf57d3f40c4b6df69daf1936cb766f832374b4fc0259a7cbff06e2f70f269' + checksumType64 = 'sha256' +} + +Install-ChocolateyZipPackage @packageArgs diff --git a/internal/pipe/publish/publish.go b/internal/pipe/publish/publish.go index cb4c288cb10..cb4c0a9a0c4 100644 --- a/internal/pipe/publish/publish.go +++ b/internal/pipe/publish/publish.go @@ -11,6 +11,7 @@ import ( "github.com/goreleaser/goreleaser/internal/pipe/aur" "github.com/goreleaser/goreleaser/internal/pipe/blob" "github.com/goreleaser/goreleaser/internal/pipe/brew" + "github.com/goreleaser/goreleaser/internal/pipe/chocolatey" "github.com/goreleaser/goreleaser/internal/pipe/custompublishers" "github.com/goreleaser/goreleaser/internal/pipe/docker" "github.com/goreleaser/goreleaser/internal/pipe/krew" @@ -48,6 +49,7 @@ var publishers = []Publisher{ aur.Pipe{}, krew.Pipe{}, scoop.Pipe{}, + chocolatey.Pipe{}, milestone.Pipe{}, } diff --git a/internal/pipeline/pipeline.go b/internal/pipeline/pipeline.go index 9d5dd684368..10f61ed4721 100644 --- a/internal/pipeline/pipeline.go +++ b/internal/pipeline/pipeline.go @@ -12,6 +12,7 @@ import ( "github.com/goreleaser/goreleaser/internal/pipe/build" "github.com/goreleaser/goreleaser/internal/pipe/changelog" "github.com/goreleaser/goreleaser/internal/pipe/checksums" + "github.com/goreleaser/goreleaser/internal/pipe/chocolatey" "github.com/goreleaser/goreleaser/internal/pipe/defaults" "github.com/goreleaser/goreleaser/internal/pipe/dist" "github.com/goreleaser/goreleaser/internal/pipe/docker" @@ -106,6 +107,8 @@ var Pipeline = append( krew.Pipe{}, // create scoop buckets scoop.Pipe{}, + // create chocolatey pkg and publish + chocolatey.Pipe{}, // create and push docker images docker.Pipe{}, // creates a metadata.json and an artifacts.json files in the dist folder diff --git a/pkg/config/config.go b/pkg/config/config.go index 29a803b9f56..56895435a4d 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -930,6 +930,7 @@ type Project struct { GoMod GoMod `yaml:"gomod,omitempty" json:"gomod,omitempty"` Announce Announce `yaml:"announce,omitempty" json:"announce,omitempty"` SBOMs []SBOM `yaml:"sboms,omitempty" json:"sboms,omitempty"` + Chocolateys []Chocolatey `yaml:"chocolateys,omitempty" json:"chocolatey,omitempty"` UniversalBinaries []UniversalBinary `yaml:"universal_binaries,omitempty" json:"universal_binaries,omitempty"` @@ -1116,3 +1117,37 @@ func (a *SlackAttachment) UnmarshalYAML(unmarshal func(interface{}) error) error func (a SlackAttachment) MarshalJSON() ([]byte, error) { return json.Marshal(a.Internal) } + +// Chocolatey contains the chocolatey section. +type Chocolatey struct { + Name string `yaml:"name,omitempty" json:"name,omitempty"` + IDs []string `yaml:"ids,omitempty" json:"ids,omitempty"` + PackageSourceURL string `yaml:"package_source_url,omitempty" json:"package_source_url,omitempty"` + Owners string `yaml:"owners,omitempty" json:"authoers,omitempty"` + Title string `yaml:"title,omitempty" json:"title,omitempty"` + Authors string `yaml:"authors,omitempty" json:"authors,omitempty"` + ProjectURL string `yaml:"project_url,omitempty" json:"project_url,omitempty"` + URLTemplate string `yaml:"url_template,omitempty" json:"url_template,omitempty"` + IconURL string `yaml:"icon_url,omitempty" json:"icon_url,omitempty"` + Copyright string `yaml:"copyright,omitempty" json:"copyright,omitempty"` + LicenseURL string `yaml:"license_url,omitempty" json:"license_url,omitempty"` + RequireLicenseAcceptance bool `yaml:"require_license_acceptance,omitempty" json:"require_license_acceptance,omitempty"` + ProjectSourceURL string `yaml:"project_source_url,omitempty" json:"project_source_url,omitempty"` + DocsURL string `yaml:"docs_url,omitempty" json:"docs_url,omitempty"` + BugTrackerURL string `yaml:"bug_tracker_url,omitempty" json:"bug_tracker_url,omitempty"` + Tags string `yaml:"tags,omitempty" json:"tags,omitempty"` + Summary string `yaml:"summary,omitempty" json:"summary,omitempty"` + Description string `yaml:"description,omitempty" json:"description,omitempty"` + ReleaseNotes string `yaml:"release_notes,omitempty" json:"release_notes,omitempty"` + Dependencies []ChocolateyDependency `yaml:"dependencies,omitempty" json:"dependencies,omitempty"` + SkipPublish bool `yaml:"skip_publish,omitempty" json:"skip_publish,omitempty"` + APIKey string `yaml:"api_key,omitempty" json:"api_key,omitempty"` + SourceRepo string `yaml:"source_repo,omitempty" json:"source_repo,omitempty"` + Goamd64 string `yaml:"goamd64,omitempty" json:"goamd64,omitempty"` +} + +// ChcolateyDependency represents Chocolatey dependency. +type ChocolateyDependency struct { + ID string `yaml:"id,omitempty" json:"id,omitempty"` + Version string `yaml:"version,omitempty" json:"version,omitempty"` +} diff --git a/pkg/defaults/defaults.go b/pkg/defaults/defaults.go index bdb625e12ae..321f6420522 100644 --- a/pkg/defaults/defaults.go +++ b/pkg/defaults/defaults.go @@ -12,6 +12,7 @@ import ( "github.com/goreleaser/goreleaser/internal/pipe/brew" "github.com/goreleaser/goreleaser/internal/pipe/build" "github.com/goreleaser/goreleaser/internal/pipe/checksums" + "github.com/goreleaser/goreleaser/internal/pipe/chocolatey" "github.com/goreleaser/goreleaser/internal/pipe/discord" "github.com/goreleaser/goreleaser/internal/pipe/docker" "github.com/goreleaser/goreleaser/internal/pipe/gomod" @@ -84,4 +85,5 @@ var Defaulters = []Defaulter{ linkedin.Pipe{}, telegram.Pipe{}, webhook.Pipe{}, + chocolatey.Pipe{}, } diff --git a/www/docs/customization/chocolatey.md b/www/docs/customization/chocolatey.md new file mode 100644 index 00000000000..5ea9ff95c2d --- /dev/null +++ b/www/docs/customization/chocolatey.md @@ -0,0 +1,133 @@ +# Chocolatey Packages + +GoReleaser can also generate `nupkg` packages. +[Chocolatey](http://chocolatey.org/) are packages based on `nupkg` format, that +will let you publish your project directly to the Chocolatey Repository. From +there it will be able to install locally or in Windows distributions. + +You can read more about it in the [chocolatey docs](https://docs.chocolatey.org/). + +Available options: + +```yaml +# .goreleaser.yaml +chocolateys: + - + # Your app's package name. + # The value may not contain spaces or character that are not valid for a URL. + # If you want a good separator for words, use '-', not '.'. + # + # Defaults to `ProjectName`. + name: foo + + # IDs of the archives to use. + # Defaults to empty, which includes all artifacts. + ids: + - foo + - bar + + # Your app's owner. + # It basically means your. + # Defaults empty. + owners: Drum Roll Inc + + # The app's title. + # A human-friendly title of the package. + # Defaults to `ProjectName`. + title: Foo Bar + + # Your app's authors (probably you). + # Defaults are shown below. + authors: Drummer + + # Your app's project url. + # It is a required field. + project_url: https://example.com/ + + # Template for the url which is determined by the given Token (github, + # gitlab or gitea) + # Default depends on the client. + url_template: "https://github.com/foo/bar/releases/download/{{ .Tag }}/{{ .ArtifactName }}" + + # App's icon. + # Default is empty. + icon_url: 'https://rawcdn.githack.com/foo/bar/efbdc760-395b-43f1-bf69-ba25c374d473/icon.png' + + # Your app's copyright details. + # Default is empty. + copyright: 2022 Drummer Roll Inc + + # App's license information url. + license_url: https://github.com/foo/bar/blob/main/LICENSE + + # Your apps's require license acceptance: + # Specify whether the client must prompt the consumer to accept the package + # license before installing. + # Default is false. + require_license_acceptance: false + + # Your app's source url. + # Default is empty. + project_source_url: https://github.com/foo/bar + + # Your app's documentation url. + # Default is empty. + docs_url: https://github.com/foo/bar/blob/main/README.md + + # App's bugtracker url. + # Default is empty. + bug_tracker_url: https://github.com/foo/barr/issues + + # Your app's tag list. + # Default is empty. + tags: "foo bar baz" + + # Your app's summary: + summary: Software to create fast and easy drum rolls. + + # This the description of your chocolatey package. + # Supports markdown. + description: | + {{ .ProjectName }} installer package. + Software to create fast and easy drum rolls. + + # Your app's release notes. + # A description of the changes made in this release of the package. + # Supports markdown. To prevent the need to continually update this field, + # providing a URL to an external list of Release Notes is perfectly + # acceptable. + # Default is empty. + release_notes: "https://github.com/foo/bar/releases/tag/v{{ .Version }}" + + # App's dependencies + # Default is empty. Version is not required. + dependencies: + - id: nfpm + version: 2.20.0 + + # The api key that should be used to push to the chocolatey repository. + # + # WARNING: do not expose your api key in the configuration file! + api_key: '{{ .Env.CHOCOLATEY_API_KEY }}' + + # The source repository that will push the package to. + # + # Defaults are shown below. + source_repo: "https://push.chocolatey.org/" + + # Setting this will prevent goreleaser to actually try to push the package + # to chocolatey repository, leaving the responsability of publishing it to + # the user. + skip_publish: false + + # GOAMD64 to specify which amd64 version to use if there are multiple + # versions from the build section. + # Default is v1. + goamd64: v1 +``` + +!!! tip + Learn more about the [name template engine](/customization/templates/). + +!!! note + GoReleaser will not install `chocolatey` nor any of its dependencies for you. diff --git a/www/docs/static/schema.json b/www/docs/static/schema.json index 277295cbaac..fac2bfe888f 100644 --- a/www/docs/static/schema.json +++ b/www/docs/static/schema.json @@ -496,6 +496,102 @@ "additionalProperties": false, "type": "object" }, + "Chocolatey": { + "properties": { + "name": { + "type": "string" + }, + "ids": { + "items": { + "type": "string" + }, + "type": "array" + }, + "package_source_url": { + "type": "string" + }, + "authoers": { + "type": "string" + }, + "title": { + "type": "string" + }, + "authors": { + "type": "string" + }, + "project_url": { + "type": "string" + }, + "url_template": { + "type": "string" + }, + "icon_url": { + "type": "string" + }, + "copyright": { + "type": "string" + }, + "license_url": { + "type": "string" + }, + "require_license_acceptance": { + "type": "boolean" + }, + "project_source_url": { + "type": "string" + }, + "docs_url": { + "type": "string" + }, + "bug_tracker_url": { + "type": "string" + }, + "tags": { + "type": "string" + }, + "summary": { + "type": "string" + }, + "description": { + "type": "string" + }, + "release_notes": { + "type": "string" + }, + "dependencies": { + "items": { + "$ref": "#/$defs/ChocolateyDependency" + }, + "type": "array" + }, + "skip_publish": { + "type": "boolean" + }, + "api_key": { + "type": "string" + }, + "source_repo": { + "type": "string" + }, + "goamd64": { + "type": "string" + } + }, + "additionalProperties": false, + "type": "object" + }, + "ChocolateyDependency": { + "properties": { + "id": { + "type": "string" + }, + "version": { + "type": "string" + } + }, + "additionalProperties": false, + "type": "object" + }, "CommitAuthor": { "properties": { "name": { @@ -1747,6 +1843,12 @@ }, "type": "array" }, + "chocolatey": { + "items": { + "$ref": "#/$defs/Chocolatey" + }, + "type": "array" + }, "universal_binaries": { "items": { "$ref": "#/$defs/UniversalBinary" diff --git a/www/mkdocs.yml b/www/mkdocs.yml index 8f67dce1c09..b036ef4081f 100644 --- a/www/mkdocs.yml +++ b/www/mkdocs.yml @@ -96,6 +96,7 @@ nav: - customization/nfpm.md - customization/checksum.md - customization/snapcraft.md + - customization/chocolatey.md - customization/docker.md - customization/docker_manifest.md - customization/sbom.md From 964e703fb4c54e94e1ee4a1990766a094ad3d383 Mon Sep 17 00:00:00 2001 From: Carlos A Becker Date: Fri, 11 Nov 2022 23:54:16 -0300 Subject: [PATCH 08/10] docs: small updates Signed-off-by: Carlos A Becker --- www/docs/customization/chocolatey.md | 5 +++- www/docs/static/schema-pro.json | 39 ++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/www/docs/customization/chocolatey.md b/www/docs/customization/chocolatey.md index 5ea9ff95c2d..27f8143820c 100644 --- a/www/docs/customization/chocolatey.md +++ b/www/docs/customization/chocolatey.md @@ -1,5 +1,7 @@ # Chocolatey Packages +> Since: v1.13.0 + GoReleaser can also generate `nupkg` packages. [Chocolatey](http://chocolatey.org/) are packages based on `nupkg` format, that will let you publish your project directly to the Chocolatey Repository. From @@ -130,4 +132,5 @@ chocolateys: Learn more about the [name template engine](/customization/templates/). !!! note - GoReleaser will not install `chocolatey` nor any of its dependencies for you. + GoReleaser will not install `chocolatey`/`choco` nor any of its dependencies + for you. diff --git a/www/docs/static/schema-pro.json b/www/docs/static/schema-pro.json index a171522033d..ce09c7acee5 100644 --- a/www/docs/static/schema-pro.json +++ b/www/docs/static/schema-pro.json @@ -350,6 +350,9 @@ "no_main_check": { "type": "boolean" }, + "buildmode": { + "type": "string" + }, "ldflags": { "$ref": "#/$defs/StringArray" }, @@ -401,6 +404,9 @@ "goamd64": { "type": "string" }, + "buildmode": { + "type": "string" + }, "ldflags": { "$ref": "#/$defs/StringArray" }, @@ -1340,6 +1346,9 @@ "apk": { "$ref": "#/$defs/NFPMAPK" }, + "archlinux": { + "$ref": "#/$defs/NFPMArchLinux" + }, "overrides": { "patternProperties": { ".*": { @@ -1433,6 +1442,33 @@ "additionalProperties": false, "type": "object" }, + "NFPMArchLinux": { + "properties": { + "pkgbase": { + "type": "string" + }, + "packager": { + "type": "string" + }, + "scripts": { + "$ref": "#/$defs/NFPMArchLinuxScripts" + } + }, + "additionalProperties": false, + "type": "object" + }, + "NFPMArchLinuxScripts": { + "properties": { + "preupgrade": { + "type": "string" + }, + "postupgrade": { + "type": "string" + } + }, + "additionalProperties": false, + "type": "object" + }, "NFPMDeb": { "properties": { "scripts": { @@ -1604,6 +1640,9 @@ }, "apk": { "$ref": "#/$defs/NFPMAPK" + }, + "archlinux": { + "$ref": "#/$defs/NFPMArchLinux" } }, "additionalProperties": false, From c4fd0a0c5ba04f3fa301fab1bb44a1167cac6b01 Mon Sep 17 00:00:00 2001 From: Carlos A Becker Date: Fri, 11 Nov 2022 23:57:03 -0300 Subject: [PATCH 09/10] chore: schema update Signed-off-by: Carlos A Becker --- www/docs/static/schema-pro.json | 102 ++++++++++++++++++++++++++++++++ 1 file changed, 102 insertions(+) diff --git a/www/docs/static/schema-pro.json b/www/docs/static/schema-pro.json index ce09c7acee5..ca3a8fb3d24 100644 --- a/www/docs/static/schema-pro.json +++ b/www/docs/static/schema-pro.json @@ -526,6 +526,102 @@ "additionalProperties": false, "type": "object" }, + "Chocolatey": { + "properties": { + "name": { + "type": "string" + }, + "ids": { + "items": { + "type": "string" + }, + "type": "array" + }, + "package_source_url": { + "type": "string" + }, + "authoers": { + "type": "string" + }, + "title": { + "type": "string" + }, + "authors": { + "type": "string" + }, + "project_url": { + "type": "string" + }, + "url_template": { + "type": "string" + }, + "icon_url": { + "type": "string" + }, + "copyright": { + "type": "string" + }, + "license_url": { + "type": "string" + }, + "require_license_acceptance": { + "type": "boolean" + }, + "project_source_url": { + "type": "string" + }, + "docs_url": { + "type": "string" + }, + "bug_tracker_url": { + "type": "string" + }, + "tags": { + "type": "string" + }, + "summary": { + "type": "string" + }, + "description": { + "type": "string" + }, + "release_notes": { + "type": "string" + }, + "dependencies": { + "items": { + "$ref": "#/$defs/ChocolateyDependency" + }, + "type": "array" + }, + "skip_publish": { + "type": "boolean" + }, + "api_key": { + "type": "string" + }, + "source_repo": { + "type": "string" + }, + "goamd64": { + "type": "string" + } + }, + "additionalProperties": false, + "type": "object" + }, + "ChocolateyDependency": { + "properties": { + "id": { + "type": "string" + }, + "version": { + "type": "string" + } + }, + "additionalProperties": false, + "type": "object" + }, "CommitAuthor": { "properties": { "name": { @@ -1898,6 +1994,12 @@ }, "type": "array" }, + "chocolatey": { + "items": { + "$ref": "#/$defs/Chocolatey" + }, + "type": "array" + }, "universal_binaries": { "items": { "$ref": "#/$defs/UniversalBinary" From 778f099a9a603867dcda91896bb76f7497b2b0fb Mon Sep 17 00:00:00 2001 From: Carlos Alexandro Becker Date: Sat, 12 Nov 2022 00:49:08 -0300 Subject: [PATCH 10/10] fix: improve artifactory error handling (#3546) closes #3543 Signed-off-by: Carlos A Becker --- internal/pipe/artifactory/artifactory.go | 2 +- internal/pipe/artifactory/artifactory_test.go | 8 ++------ 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/internal/pipe/artifactory/artifactory.go b/internal/pipe/artifactory/artifactory.go index e3c5034b50d..bdc4a3b1d65 100644 --- a/internal/pipe/artifactory/artifactory.go +++ b/internal/pipe/artifactory/artifactory.go @@ -79,7 +79,7 @@ func checkResponse(r *h.Response) error { if err == nil && data != nil { err := json.Unmarshal(data, errorResponse) if err != nil { - return err + return fmt.Errorf("unexpected error: %w: %s", err, string(data)) } } return errorResponse diff --git a/internal/pipe/artifactory/artifactory_test.go b/internal/pipe/artifactory/artifactory_test.go index d8e9ce0cd8b..e7192801d46 100644 --- a/internal/pipe/artifactory/artifactory_test.go +++ b/internal/pipe/artifactory/artifactory_test.go @@ -461,11 +461,7 @@ func TestRunPipe_UnparsableErrorResponse(t *testing.T) { requireHeader(t, r, "Authorization", "Basic ZGVwbG95dXNlcjpkZXBsb3l1c2VyLXNlY3JldA==") w.WriteHeader(http.StatusUnauthorized) - fmt.Fprint(w, `...{ - "errors" : [ { - ... - } ] - }`) + fmt.Fprint(w, `

error

`) }) ctx := context.New(config.Project{ @@ -495,7 +491,7 @@ func TestRunPipe_UnparsableErrorResponse(t *testing.T) { }) require.NoError(t, Pipe{}.Default(ctx)) - require.EqualError(t, Pipe{}.Publish(ctx), `artifactory: upload failed: invalid character '.' looking for beginning of value`) + require.EqualError(t, Pipe{}.Publish(ctx), `artifactory: upload failed: unexpected error: invalid character '<' looking for beginning of value:

error

`) } func TestRunPipe_FileNotFound(t *testing.T) {