mirror of
https://github.com/goreleaser/goreleaser.git
synced 2025-01-18 03:56:52 +02:00
8706fd2e89
Currently, GoReleaser will assume you're running against github, gitea or gitlab. You could set `release.disable: true`, but it would still set and try to use some defaults that could break things up. Now, if you disable the release, goreleaser will not set these defaults. It'll also hard error in some cases in which it would happily produce invalid resources before, namely, if `release.disable` is set, and, for example, `brews.url_template` is empty (in which case it would try to use the one from the release, usually github). closes #4208
563 lines
13 KiB
Go
563 lines
13 KiB
Go
package nix
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"path"
|
|
"path/filepath"
|
|
"sort"
|
|
"strings"
|
|
"text/template"
|
|
|
|
"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"
|
|
)
|
|
|
|
const nixConfigExtra = "NixConfig"
|
|
|
|
// ErrMultipleArchivesSamePlatform happens when the config yields multiple
|
|
// archives for the same platform.
|
|
var ErrMultipleArchivesSamePlatform = errors.New("one nixpkg can handle only one archive of each OS/Arch combination")
|
|
|
|
type errNoArchivesFound struct {
|
|
goamd64 string
|
|
ids []string
|
|
}
|
|
|
|
func (e errNoArchivesFound) Error() string {
|
|
return fmt.Sprintf("no archives found matching goos=[darwin linux] goarch=[amd64 arm arm64 386] goarm=[6 7] goamd64=%s ids=%v", e.goamd64, e.ids)
|
|
}
|
|
|
|
var (
|
|
errNoRepoName = pipe.Skip("repository name is not set")
|
|
errSkipUpload = pipe.Skip("nix.skip_upload is set")
|
|
errSkipUploadAuto = pipe.Skip("nix.skip_upload is set to 'auto', and current version is a pre-release")
|
|
)
|
|
|
|
// NewBuild returns a pipe to be used in the build phase.
|
|
func NewBuild() Pipe {
|
|
return Pipe{buildShaPrefetcher{}}
|
|
}
|
|
|
|
// NewPublish returns a pipe to be used in the publish phase.
|
|
func NewPublish() Pipe {
|
|
return Pipe{publishShaPrefetcher{
|
|
bin: nixPrefetchURLBin,
|
|
}}
|
|
}
|
|
|
|
type Pipe struct {
|
|
prefetcher shaPrefetcher
|
|
}
|
|
|
|
func (Pipe) String() string { return "nixpkgs" }
|
|
func (Pipe) ContinueOnError() bool { return true }
|
|
func (Pipe) Dependencies(_ *context.Context) []string { return []string{"nix-prefetch-url"} }
|
|
func (p Pipe) Skip(ctx *context.Context) bool {
|
|
return len(ctx.Config.Nix) == 0 || !p.prefetcher.Available()
|
|
}
|
|
|
|
func (Pipe) Default(ctx *context.Context) error {
|
|
for i := range ctx.Config.Nix {
|
|
nix := &ctx.Config.Nix[i]
|
|
|
|
nix.CommitAuthor = commitauthor.Default(nix.CommitAuthor)
|
|
|
|
if nix.CommitMessageTemplate == "" {
|
|
nix.CommitMessageTemplate = "{{ .ProjectName }}: {{ .PreviousTag }} -> {{ .Tag }}"
|
|
}
|
|
if nix.Name == "" {
|
|
nix.Name = ctx.Config.ProjectName
|
|
}
|
|
if nix.Goamd64 == "" {
|
|
nix.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 _, nix := range ctx.Config.Nix {
|
|
err := p.doRun(ctx, nix, cli)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (p Pipe) publishAll(ctx *context.Context, cli client.Client) error {
|
|
skips := pipe.SkipMemento{}
|
|
for _, nix := range ctx.Artifacts.Filter(artifact.ByType(artifact.Nixpkg)).List() {
|
|
err := doPublish(ctx, p.prefetcher, cli, nix)
|
|
if err != nil && pipe.IsSkip(err) {
|
|
skips.Remember(err)
|
|
continue
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return skips.Evaluate()
|
|
}
|
|
|
|
func (p Pipe) doRun(ctx *context.Context, nix config.Nix, cl client.ReleaseURLTemplater) error {
|
|
if nix.Repository.Name == "" {
|
|
return errNoRepoName
|
|
}
|
|
|
|
tp := tmpl.New(ctx)
|
|
|
|
err := tp.ApplyAll(
|
|
&nix.Name,
|
|
&nix.SkipUpload,
|
|
&nix.Homepage,
|
|
&nix.Description,
|
|
&nix.Path,
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
nix.Repository, err = client.TemplateRef(tmpl.New(ctx).Apply, nix.Repository)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if nix.Path == "" {
|
|
nix.Path = path.Join("pkgs", nix.Name, "default.nix")
|
|
}
|
|
|
|
path := filepath.Join(ctx.Config.Dist, "nix", nix.Path)
|
|
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
|
|
return err
|
|
}
|
|
|
|
content, err := preparePkg(ctx, nix, cl, p.prefetcher)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
log.WithField("nixpkg", path).Info("writing")
|
|
if err := os.WriteFile(path, []byte(content), 0o644); err != nil { //nolint: gosec
|
|
return fmt.Errorf("failed to write nixpkg: %w", err)
|
|
}
|
|
|
|
ctx.Artifacts.Add(&artifact.Artifact{
|
|
Name: filepath.Base(path),
|
|
Path: path,
|
|
Type: artifact.Nixpkg,
|
|
Extra: map[string]interface{}{
|
|
nixConfigExtra: nix,
|
|
},
|
|
})
|
|
|
|
return nil
|
|
}
|
|
|
|
func preparePkg(
|
|
ctx *context.Context,
|
|
nix config.Nix,
|
|
cli client.ReleaseURLTemplater,
|
|
prefetcher shaPrefetcher,
|
|
) (string, error) {
|
|
filters := []artifact.Filter{
|
|
artifact.Or(
|
|
artifact.ByGoos("darwin"),
|
|
artifact.ByGoos("linux"),
|
|
),
|
|
artifact.Or(
|
|
artifact.And(
|
|
artifact.ByGoarch("amd64"),
|
|
artifact.ByGoamd64(nix.Goamd64),
|
|
),
|
|
artifact.And(
|
|
artifact.ByGoarch("arm"),
|
|
artifact.Or(
|
|
artifact.ByGoarm("6"),
|
|
artifact.ByGoarm("7"),
|
|
),
|
|
),
|
|
artifact.ByGoarch("arm64"),
|
|
artifact.ByGoarch("386"),
|
|
artifact.ByGoarch("all"),
|
|
),
|
|
artifact.And(
|
|
artifact.ByFormats("zip", "tar.gz"),
|
|
artifact.ByType(artifact.UploadableArchive),
|
|
),
|
|
artifact.OnlyReplacingUnibins,
|
|
}
|
|
if len(nix.IDs) > 0 {
|
|
filters = append(filters, artifact.ByIDs(nix.IDs...))
|
|
}
|
|
|
|
archives := ctx.Artifacts.Filter(artifact.And(filters...)).List()
|
|
if len(archives) == 0 {
|
|
return "", errNoArchivesFound{
|
|
goamd64: nix.Goamd64,
|
|
ids: nix.IDs,
|
|
}
|
|
}
|
|
|
|
if nix.URLTemplate == "" {
|
|
url, err := cli.ReleaseURLTemplate(ctx)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
nix.URLTemplate = url
|
|
}
|
|
|
|
installs, err := installs(ctx, nix, archives[0])
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
postInstall, err := postInstall(ctx, nix, archives[0])
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
folder := artifact.ExtraOr(*archives[0], artifact.ExtraWrappedIn, ".")
|
|
if folder == "" {
|
|
folder = "."
|
|
}
|
|
|
|
inputs := []string{"installShellFiles"}
|
|
dependencies := depNames(nix.Dependencies)
|
|
if len(dependencies) > 0 {
|
|
inputs = append(inputs, "makeWrapper")
|
|
}
|
|
if archives[0].Format() == "zip" {
|
|
inputs = append(inputs, "unzip")
|
|
dependencies = append(dependencies, "unzip")
|
|
}
|
|
|
|
data := templateData{
|
|
Name: nix.Name,
|
|
Version: ctx.Version,
|
|
Install: installs,
|
|
PostInstall: postInstall,
|
|
Archives: map[string]Archive{},
|
|
SourceRoot: folder,
|
|
Description: nix.Description,
|
|
Homepage: nix.Homepage,
|
|
License: nix.License,
|
|
Inputs: inputs,
|
|
Dependencies: dependencies,
|
|
}
|
|
|
|
platforms := map[string]bool{}
|
|
for _, art := range archives {
|
|
url, err := tmpl.New(ctx).WithArtifact(art).Apply(nix.URLTemplate)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
sha, err := prefetcher.Prefetch(url)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
archive := Archive{
|
|
URL: url,
|
|
Sha: sha,
|
|
}
|
|
|
|
for _, goarch := range expandGoarch(art.Goarch) {
|
|
key := art.Goos + goarch + art.Goarm
|
|
if _, ok := data.Archives[key]; ok {
|
|
return "", ErrMultipleArchivesSamePlatform
|
|
}
|
|
data.Archives[key] = archive
|
|
plat := goosToPlatform[art.Goos+goarch+art.Goarm]
|
|
platforms[plat] = true
|
|
}
|
|
}
|
|
data.Platforms = keys(platforms)
|
|
sort.Strings(data.Platforms)
|
|
|
|
return doBuildPkg(ctx, data)
|
|
}
|
|
|
|
func expandGoarch(goarch string) []string {
|
|
if goarch == "all" {
|
|
return []string{"amd64", "arm64"}
|
|
}
|
|
return []string{goarch}
|
|
}
|
|
|
|
var goosToPlatform = map[string]string{
|
|
"linuxamd64": "x86_64-linux",
|
|
"linuxarm64": "aarch64-linux",
|
|
"linuxarm6": "armv6l-linux",
|
|
"linuxarm7": "armv7l-linux",
|
|
"linux386": "i686-linux",
|
|
"darwinamd64": "x86_64-darwin",
|
|
"darwinarm64": "aarch64-darwin",
|
|
}
|
|
|
|
func keys(m map[string]bool) []string {
|
|
keys := make([]string, 0, len(m))
|
|
for k := range m {
|
|
keys = append(keys, k)
|
|
}
|
|
return keys
|
|
}
|
|
|
|
func doPublish(ctx *context.Context, prefetcher shaPrefetcher, cl client.Client, pkg *artifact.Artifact) error {
|
|
nix, err := artifact.Extra[config.Nix](*pkg, nixConfigExtra)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if strings.TrimSpace(nix.SkipUpload) == "true" {
|
|
return errSkipUpload
|
|
}
|
|
|
|
if strings.TrimSpace(nix.SkipUpload) == "auto" && ctx.Semver.Prerelease != "" {
|
|
return errSkipUploadAuto
|
|
}
|
|
|
|
repo := client.RepoFromRef(nix.Repository)
|
|
|
|
gpath := nix.Path
|
|
|
|
msg, err := tmpl.New(ctx).Apply(nix.CommitMessageTemplate)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
author, err := commitauthor.Get(ctx, nix.CommitAuthor)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
content, err := preparePkg(ctx, nix, cl, prefetcher)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if nix.Repository.Git.URL != "" {
|
|
return client.NewGitUploadClient(repo.Branch).
|
|
CreateFile(ctx, author, repo, []byte(content), gpath, msg)
|
|
}
|
|
|
|
cl, err = client.NewIfToken(ctx, cl, nix.Repository.Token)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if !nix.Repository.PullRequest.Enabled {
|
|
return cl.CreateFile(ctx, author, repo, []byte(content), gpath, msg)
|
|
}
|
|
|
|
log.Info("nix.pull_request enabled, creating a PR")
|
|
pcl, ok := cl.(client.PullRequestOpener)
|
|
if !ok {
|
|
return fmt.Errorf("client does not support pull requests")
|
|
}
|
|
|
|
if err := cl.CreateFile(ctx, author, repo, []byte(content), gpath, msg); err != nil {
|
|
return err
|
|
}
|
|
|
|
return pcl.OpenPullRequest(ctx, client.Repo{
|
|
Name: nix.Repository.PullRequest.Base.Name,
|
|
Owner: nix.Repository.PullRequest.Base.Owner,
|
|
Branch: nix.Repository.PullRequest.Base.Branch,
|
|
}, repo, msg, nix.Repository.PullRequest.Draft)
|
|
}
|
|
|
|
func doBuildPkg(ctx *context.Context, data templateData) (string, error) {
|
|
t, err := template.
|
|
New(data.Name).
|
|
Parse(string(pkgTmpl))
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
var out bytes.Buffer
|
|
if err := t.Execute(&out, data); err != nil {
|
|
return "", err
|
|
}
|
|
|
|
content, err := tmpl.New(ctx).Apply(out.String())
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
out.Reset()
|
|
|
|
// Sanitize the template output and get rid of trailing whitespace.
|
|
var (
|
|
r = strings.NewReader(content)
|
|
s = bufio.NewScanner(r)
|
|
)
|
|
for s.Scan() {
|
|
l := strings.TrimRight(s.Text(), " ")
|
|
_, _ = out.WriteString(l)
|
|
_ = out.WriteByte('\n')
|
|
}
|
|
if err := s.Err(); err != nil {
|
|
return "", err
|
|
}
|
|
|
|
return out.String(), nil
|
|
}
|
|
|
|
func postInstall(ctx *context.Context, nix config.Nix, art *artifact.Artifact) ([]string, error) {
|
|
applied, err := tmpl.New(ctx).WithArtifact(art).Apply(nix.PostInstall)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return split(applied), nil
|
|
}
|
|
|
|
func installs(ctx *context.Context, nix config.Nix, art *artifact.Artifact) ([]string, error) {
|
|
tpl := tmpl.New(ctx).WithArtifact(art)
|
|
|
|
extraInstall, err := tpl.Apply(nix.ExtraInstall)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
install, err := tpl.Apply(nix.Install)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if install != "" {
|
|
return append(split(install), split(extraInstall)...), nil
|
|
}
|
|
|
|
result := []string{"mkdir -p $out/bin"}
|
|
binInstallFormat := binInstallFormats(nix)
|
|
for _, bin := range artifact.ExtraOr(*art, artifact.ExtraBinaries, []string{}) {
|
|
for _, format := range binInstallFormat {
|
|
result = append(result, fmt.Sprintf(format, bin))
|
|
}
|
|
}
|
|
|
|
log.WithField("install", result).Info("guessing install")
|
|
|
|
return append(result, split(extraInstall)...), nil
|
|
}
|
|
|
|
func binInstallFormats(nix config.Nix) []string {
|
|
formats := []string{"cp -vr ./%[1]s $out/bin/%[1]s"}
|
|
if len(nix.Dependencies) == 0 {
|
|
return formats
|
|
}
|
|
var deps, linuxDeps, darwinDeps []string
|
|
|
|
for _, dep := range nix.Dependencies {
|
|
switch dep.OS {
|
|
case "darwin":
|
|
darwinDeps = append(darwinDeps, dep.Name)
|
|
case "linux":
|
|
linuxDeps = append(linuxDeps, dep.Name)
|
|
default:
|
|
deps = append(deps, dep.Name)
|
|
}
|
|
}
|
|
|
|
var depStrings []string
|
|
|
|
if len(darwinDeps) > 0 {
|
|
depStrings = append(depStrings, fmt.Sprintf("lib.optionals stdenv.isDarwin [ %s ]", strings.Join(darwinDeps, " ")))
|
|
}
|
|
if len(linuxDeps) > 0 {
|
|
depStrings = append(depStrings, fmt.Sprintf("lib.optionals stdenv.isLinux [ %s ]", strings.Join(linuxDeps, " ")))
|
|
}
|
|
if len(deps) > 0 {
|
|
depStrings = append(depStrings, fmt.Sprintf("[ %s ]", strings.Join(deps, " ")))
|
|
}
|
|
|
|
depString := strings.Join(depStrings, " ++ ")
|
|
return append(
|
|
formats,
|
|
"wrapProgram $out/bin/%[1]s --prefix PATH : ${lib.makeBinPath ("+depString+")}",
|
|
)
|
|
}
|
|
|
|
func split(s string) []string {
|
|
var result []string
|
|
for _, line := range strings.Split(strings.TrimSpace(s), "\n") {
|
|
line := strings.TrimSpace(line)
|
|
if line == "" {
|
|
continue
|
|
}
|
|
result = append(result, line)
|
|
}
|
|
return result
|
|
}
|
|
|
|
func depNames(deps []config.NixDependency) []string {
|
|
var result []string
|
|
for _, dep := range deps {
|
|
result = append(result, dep.Name)
|
|
}
|
|
return result
|
|
}
|
|
|
|
type shaPrefetcher interface {
|
|
Prefetch(url string) (string, error)
|
|
Available() bool
|
|
}
|
|
|
|
const (
|
|
zeroHash = "0000000000000000000000000000000000000000000000000000"
|
|
nixPrefetchURLBin = "nix-prefetch-url"
|
|
)
|
|
|
|
type buildShaPrefetcher struct{}
|
|
|
|
func (buildShaPrefetcher) Prefetch(_ string) (string, error) { return zeroHash, nil }
|
|
func (buildShaPrefetcher) Available() bool { return true }
|
|
|
|
type publishShaPrefetcher struct {
|
|
bin string
|
|
}
|
|
|
|
func (p publishShaPrefetcher) Available() bool {
|
|
_, err := exec.LookPath(p.bin)
|
|
if err != nil {
|
|
log.Warnf("%s is not available", p.bin)
|
|
}
|
|
return err == nil
|
|
}
|
|
|
|
func (p publishShaPrefetcher) Prefetch(url string) (string, error) {
|
|
out, err := exec.Command(p.bin, url).Output()
|
|
outStr := strings.TrimSpace(string(out))
|
|
if err != nil {
|
|
return "", fmt.Errorf("could not prefetch url: %s: %w: %s", url, err, outStr)
|
|
}
|
|
return outStr, nil
|
|
}
|