Skip to content

Commit

Permalink
feat: add inital support for source RPMs
Browse files Browse the repository at this point in the history
  • Loading branch information
twpayne committed Sep 28, 2022
1 parent c006c9d commit bb24db1
Show file tree
Hide file tree
Showing 7 changed files with 404 additions and 0 deletions.
8 changes: 8 additions & 0 deletions internal/artifact/artifact.go
Expand Up @@ -66,6 +66,10 @@ const (
ScoopManifest
// SBOM is a Software Bill of Materials file.
SBOM
// SourceRPM is a source RPM.
SourceRPM
// RPMSpec is an RPM .spec file.
RPMSpec
)

func (t Type) String() string {
Expand Down Expand Up @@ -104,6 +108,10 @@ func (t Type) String() string {
return "PKGBUILD"
case SrcInfo:
return "SRCINFO"
case SourceRPM:
return "Source RPM"
case RPMSpec:
return "RPM Spec"
default:
return "unknown"
}
Expand Down
65 changes: 65 additions & 0 deletions internal/pipe/srpm/spec.tmpl
@@ -0,0 +1,65 @@
# Generated by goreleaser
%bcond_without check

%global goipath {{ .ImportPath }}
%global commit {{ .FullCommit }}

%%gometa -f

%global common_description %{expand:
{{ .Description }}}

{{ if .LicenseFileName }}
%global golicenses {{ .LicenseFileName }}
{{ end }}
{{ if .Docs }}
%global godocs {{ range .Docs }} {{ . }}{{ end }}
{{ end }}

Name: %{goname}
Version: {{ .Version }}
Release: %autorelease -p
Summary: {{ .Summary }}

License: {{ .License }}
URL: {{ .URL }}
Source: {{ .Source }}

%description %{common_description}

%gopkg

%prep
%goprep

%generate_buildrequires
%go_generate_buildrequires

%build
{{ range $binary, $importPath := .Bins }}
%gobuild -o %{gobuilddir}/bin/{{ $binary }} {{ $importPath }}
{{ end }}

%install
%gopkginstall
install -m 0755 -vd %{buildroot}%{_bindir}
install -m 0755 -vp %{gobuilddir}/bin/* %{buildroot}%{_bindir}/

%if %{with check}
%check
%gocheck
%endif

%files
{{ range .Docs }}
%doc {{ . }}
{{ end }}
{{ if .LicenseFileName }}
%license {{ .LicenseFileName }}
{{ end }}
%{_bindir}/*

%gopkgfiles

%changelog
%autochangelog
220 changes: 220 additions & 0 deletions internal/pipe/srpm/srpm.go
@@ -0,0 +1,220 @@
// Package srpm implements the Pipe interface building source RPMs.
package srpm

import (
_ "embed"
"fmt"
"os"
"path/filepath"
"strings"

"github.com/caarlos0/log"
"github.com/goreleaser/goreleaser/internal/artifact"
"github.com/goreleaser/goreleaser/internal/tmpl"
"github.com/goreleaser/goreleaser/pkg/context"
"github.com/goreleaser/nfpm/v2"
"github.com/goreleaser/nfpm/v2/files"

_ "github.com/goreleaser/nfpm/v2/rpm" // blank import to register the format
)

var (
defaultFileNameTemplate = "{{ .PackageName }}-{{ .Version }}.src.rpm"
defaultSpecFileNameTemplate = "{{ .PackageName }}.spec"
)

//go:embed spec.tmpl
var defaultSpecTemplate string

// Pipe for source RPMs.
type Pipe struct{}

func (Pipe) String() string { return "source RPMs" }
func (Pipe) Skip(ctx *context.Context) bool { return !ctx.Config.SRPM.Enabled }

// Default sets the pipe defaults.
func (Pipe) Default(ctx *context.Context) error {
srpm := &ctx.Config.SRPM
if srpm.ID == "" {
srpm.ID = "default"
}
if srpm.PackageName == "" {
srpm.PackageName = ctx.Config.ProjectName
}
if srpm.FileNameTemplate == "" {
srpm.FileNameTemplate = defaultFileNameTemplate
}
if srpm.SpecFileNameTemplate == "" {
srpm.SpecFileNameTemplate = defaultSpecFileNameTemplate
}
if srpm.SpecTemplate == "" {
srpm.SpecTemplate = defaultSpecTemplate
}
if srpm.Bins == nil {
srpm.Bins = map[string]string{
ctx.Config.ProjectName: "%{goipath}",
}
}
return nil
}

// Run the pipe.
func (Pipe) Run(ctx *context.Context) error {
sourceArchives := ctx.Artifacts.Filter(artifact.ByType(artifact.UploadableSourceArchive)).List()
if len(sourceArchives) == 0 {
return fmt.Errorf("no source archives found")
} else if len(sourceArchives) > 1 {
return fmt.Errorf("multiple source archives found")
}

srpm := ctx.Config.SRPM
sourceArchive := sourceArchives[0]

t := tmpl.New(ctx).
WithExtraFields(tmpl.Fields{
"PackageName": srpm.PackageName,
"ImportPath": srpm.ImportPath,
"License": srpm.License,
"LicenseFileName": srpm.LicenseFileName,
"URL": srpm.URL,
"Summary": srpm.Summary,
"Description": srpm.Description,
"Source": sourceArchive.Name,
"Bins": srpm.Bins,
"Docs": srpm.Docs,
})

// Generate the spec file.
specFileName, err := t.Apply(srpm.SpecFileNameTemplate)
if err != nil {
return err
}
specContents, err := t.Apply(srpm.SpecTemplate)
if err != nil {
return err
}
specPath := filepath.Join(ctx.Config.Dist, specFileName)
if err := os.WriteFile(specPath, []byte(specContents), 0o666); err != nil {
return err
}
specFileArtifact := &artifact.Artifact{
Type: artifact.RPMSpec,
Name: specFileName,
Path: specPath,
}

// Default file info.
owner := "mockbuild"
group := "mock"
mtime := ctx.Git.CommitDate

contents := files.Contents{}

// Add the source archive.
contents = append(contents, &files.Content{
Source: sourceArchive.Path,
Destination: sourceArchive.Name,
// FIXME Type:
Packager: srpm.Packager,
FileInfo: &files.ContentFileInfo{
Owner: owner,
Group: group,
Mode: 0o664, // Source archives are group-writeable by default.
MTime: mtime,
// FIXME Size:
},
})

// Add extra contents.
contents = append(contents, srpm.Contents...)

// Add the spec file.
contents = append(contents, &files.Content{
Source: specFileArtifact.Path,
Destination: specFileArtifact.Name,
// FIXME Type:
Packager: srpm.Packager,
FileInfo: &files.ContentFileInfo{
Owner: owner,
Group: group,
Mode: 0o660, // Spec files are private by default.
MTime: mtime,
Size: int64(len(specContents)),
},
})

keyFile, err := t.Apply(srpm.Signature.KeyFile)
if err != nil {
return err
}

// Create the source RPM package.
info := &nfpm.Info{
Name: srpm.PackageName,
Epoch: srpm.Epoch,
Version: ctx.Version,
Section: srpm.Section,
Maintainer: srpm.Maintainer,
Description: srpm.Description,
Vendor: srpm.Vendor,
Homepage: srpm.URL,
License: srpm.License,
Overridables: nfpm.Overridables{
Contents: contents,
RPM: nfpm.RPM{
Group: srpm.Group,
Summary: srpm.Summary,
Compression: srpm.Compression,
Packager: srpm.Packager,
Signature: nfpm.RPMSignature{
PackageSignature: nfpm.PackageSignature{
KeyFile: keyFile,
KeyPassphrase: ctx.Env[fmt.Sprintf("SRPM_%s_PASSPHRASE", srpm.ID)],
// TODO: KeyID
},
},
},
},
}

if ctx.SkipSign {
info.RPM.Signature = nfpm.RPMSignature{}
}

packager, err := nfpm.Get("rpm")
if err != nil {
return err
}
info = nfpm.WithDefaults(info)

// Write the source RPM.
srpmFileName, err := t.Apply(srpm.FileNameTemplate)
if err != nil {
return err
}
if !strings.HasSuffix(srpmFileName, ".src.rpm") {
srpmFileName += ".src.rpm"
}
srpmPath := filepath.Join(ctx.Config.Dist, srpmFileName)
log.WithField("file", srpmPath).Info("creating")
srpmFile, err := os.Create(srpmPath)
if err != nil {
return err
}
defer srpmFile.Close()
if err := packager.Package(info, srpmFile); err != nil {
return fmt.Errorf("nfpm failed: %w", err)
}
if err := srpmFile.Close(); err != nil {
return fmt.Errorf("could not close package file: %w", err)
}
srpmArtifact := &artifact.Artifact{
Type: artifact.SourceRPM,
Name: srpmFileName,
Path: srpmPath,
}

ctx.Artifacts.Add(specFileArtifact)
ctx.Artifacts.Add(srpmArtifact)
return nil
}
82 changes: 82 additions & 0 deletions internal/pipe/srpm/srpm_test.go
@@ -0,0 +1,82 @@
package srpm

import (
"os"
"path/filepath"
"regexp"
"testing"

"github.com/goreleaser/goreleaser/internal/artifact"
"github.com/goreleaser/goreleaser/pkg/config"
"github.com/goreleaser/goreleaser/pkg/context"
"github.com/stretchr/testify/require"
)

func TestRunPipe(t *testing.T) {
// Setup a context with a source archive.
folder := t.TempDir()
dist := filepath.Join(folder, "dist")
require.NoError(t, os.Mkdir(dist, 0o755))
sourceArchivePath := filepath.Join(dist, "example-1.0.0.tar.gz")
f, err := os.Create(sourceArchivePath)
require.NoError(t, err)
require.NoError(t, f.Close())
ctx := context.New(config.Project{
ProjectName: "example",
Dist: dist,
SRPM: config.SRPM{
NFPMRPM: config.NFPMRPM{
Summary: "Example summary",
},
Enabled: true,
ImportPath: "github.com/example/example",
License: "MIT",
LicenseFileName: "LICENSE",
Packager: "Example packager",
Vendor: "Example vendor",
URL: "https://example.com",
Description: "Example description",
Docs: []string{
"README.md",
},
},
})
ctx.Version = "1.0.0"
ctx.Git = context.GitInfo{
FullCommit: "e070258c90772fbcf1cb94c2b937ff25a011b5c8",
}
ctx.Artifacts.Add(&artifact.Artifact{
Name: "example-1.0.0.tar.gz",
Path: sourceArchivePath,
Type: artifact.UploadableSourceArchive,
})

// Run the source RPM pipe.
var pipe Pipe
require.NoError(t, pipe.Default(ctx))
require.NoError(t, pipe.Run(ctx))

// Check the source RPM artifact.
sourceRPMs := ctx.Artifacts.Filter(artifact.ByType(artifact.SourceRPM)).List()
require.Len(t, sourceRPMs, 1)
sourceRPM := sourceRPMs[0]
require.Equal(t, "example-1.0.0.src.rpm", sourceRPM.Name)
require.Equal(t, filepath.Join(dist, "example-1.0.0.src.rpm"), sourceRPM.Path)
// FIXME check source RPM contents using https://github.com/sassoftware/go-rpmutils?
// FIXME check source RPM contents using https://github.com/cavaliergopher/rpm?

// Check the .spec artifact.
rpmSpecs := ctx.Artifacts.Filter(artifact.ByType(artifact.RPMSpec)).List()
require.Len(t, rpmSpecs, 1)
rpmSpecContents, err := os.ReadFile(rpmSpecs[0].Path)
require.NoError(t, err)
require.True(t, regexp.MustCompile(`(?m)^%global\s+goipath\s+github.com/example/example$`).Match(rpmSpecContents))
require.True(t, regexp.MustCompile(`(?m)^%global\s+commit\s+e070258c90772fbcf1cb94c2b937ff25a011b5c8$`).Match(rpmSpecContents))
require.True(t, regexp.MustCompile(`(?m)^%global\s+golicenses\s+LICENSE$`).Match(rpmSpecContents))
require.True(t, regexp.MustCompile(`(?m)^%global\s+godocs\s+README\.md$`).Match(rpmSpecContents))
require.True(t, regexp.MustCompile(`(?m)^Version:\s+1\.0\.0$`).Match(rpmSpecContents))
require.True(t, regexp.MustCompile(`(?m)^Summary:\s+Example summary$`).Match(rpmSpecContents))
require.True(t, regexp.MustCompile(`(?m)^%doc\s+README\.md$`).Match(rpmSpecContents))
require.True(t, regexp.MustCompile(`(?m)^%license\s+LICENSE$`).Match(rpmSpecContents))
// FIXME add tests for all remaining configurable fields
}

0 comments on commit bb24db1

Please sign in to comment.