1
0
mirror of https://github.com/goreleaser/goreleaser.git synced 2025-03-17 20:47:50 +02:00

Merge pull request #317 from elopio/snapcraft

add the snapcraft pipeline
This commit is contained in:
Carlos Alexandro Becker 2017-08-02 08:31:05 -03:00 committed by GitHub
commit e45c4a9cb7
6 changed files with 263 additions and 7 deletions

View File

@ -41,3 +41,11 @@ fpm:
- deb
dependencies:
- git
snapcraft:
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 repository. All that
wrapped in your favorite CI.
grade: stable
confinement: classic

View File

@ -1,8 +1,14 @@
dist: trusty
sudo: required
language: go
go: 1.8.3
install:
- make setup
- gem install fpm
- sudo apt-get update
- sudo apt-get install --yes snapd
- export PATH=/snap/bin:$PATH
- sudo snap install snapcraft --candidate --classic
script:
- make test
- test -n "$TRAVIS_TAG" || go run main.go --skip-validate --skip-publish

View File

@ -123,6 +123,17 @@ type FPM struct {
XXX map[string]interface{} `yaml:",inline"`
}
// Snapcraft config
type Snapcraft struct {
Summary string `yaml:",omitempty"`
Description string `yaml:",omitempty"`
Grade string `yaml:",omitempty"`
Confinement string `yaml:",omitempty"`
// Capture all undefined fields and should be empty after loading
XXX map[string]interface{} `yaml:",inline"`
}
// Snapshot config
type Snapshot struct {
NameTemplate string `yaml:"name_template,omitempty"`
@ -133,13 +144,14 @@ type Snapshot struct {
// Project includes all project configuration
type Project struct {
ProjectName string `yaml:"project_name,omitempty"`
Release Release `yaml:",omitempty"`
Brew Homebrew `yaml:",omitempty"`
Builds []Build `yaml:",omitempty"`
Archive Archive `yaml:",omitempty"`
FPM FPM `yaml:",omitempty"`
Snapshot Snapshot `yaml:",omitempty"`
ProjectName string `yaml:"project_name,omitempty"`
Release Release `yaml:",omitempty"`
Brew Homebrew `yaml:",omitempty"`
Builds []Build `yaml:",omitempty"`
Archive Archive `yaml:",omitempty"`
FPM FPM `yaml:",omitempty"`
Snapcraft Snapcraft `yaml:",omitempty"`
Snapshot Snapshot `yaml:",omitempty"`
// this is a hack ¯\_(ツ)_/¯
SingleBuild Build `yaml:"build,omitempty"`
@ -191,6 +203,7 @@ func checkOverflows(config Project) error {
}
}
checker.check(config.FPM.XXX, "fpm")
checker.check(config.Snapcraft.XXX, "snapcraft")
checker.check(config.Release.XXX, "release")
checker.check(config.Release.GitHub.XXX, "release.github")
checker.check(config.SingleBuild.XXX, "build")

View File

@ -20,6 +20,7 @@ import (
"github.com/goreleaser/goreleaser/pipeline/fpm"
"github.com/goreleaser/goreleaser/pipeline/git"
"github.com/goreleaser/goreleaser/pipeline/release"
"github.com/goreleaser/goreleaser/pipeline/snapcraft"
yaml "gopkg.in/yaml.v2"
)
@ -31,6 +32,7 @@ var pipes = []pipeline.Pipe{
build.Pipe{}, // build
archive.Pipe{}, // archive (tar.gz, zip, etc)
fpm.Pipe{}, // archive via fpm (deb, rpm, etc)
snapcraft.Pipe{}, // archive via snapcraft (snap)
checksums.Pipe{}, // checksums of the files
release.Pipe{}, // release to github
brew.Pipe{}, // push to brew tap

View File

@ -0,0 +1,142 @@
// Package snapcraft implements the Pipe interface providing Snapcraft bindings.
package snapcraft
import (
"errors"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"strings"
"github.com/apex/log"
"github.com/goreleaser/goreleaser/context"
"golang.org/x/sync/errgroup"
yaml "gopkg.in/yaml.v2"
)
// ErrNoSnapcraft is shown when snapcraft cannot be found in $PATH
var ErrNoSnapcraft = errors.New("snapcraft not present in $PATH")
// SnapcraftMetadata to generate the snap package
type SnapcraftMetadata struct {
Name string
Version string
Summary string
Description string
Grade string `yaml:",omitempty"`
Confinement string `yaml:",omitempty"`
Architectures []string
Apps map[string]AppsMetadata
}
// AppsMetadata for the binaries that will be in the snap package
type AppsMetadata struct {
Command string
// Plugs []string
// Daemon string
}
// Pipe for snapcraft packaging
type Pipe struct{}
// Description of the pipe
func (Pipe) Description() string {
return "Creating Linux packages with snapcraft"
}
// Run the pipe
func (Pipe) Run(ctx *context.Context) error {
if ctx.Config.Snapcraft.Summary == "" {
log.Error("no snapcraft summary defined, skipping")
return nil
}
if ctx.Config.Snapcraft.Summary == "" {
log.Error("no snapcraft description defined, skipping")
return nil
}
_, err := exec.LookPath("snapcraft")
if err != nil {
return ErrNoSnapcraft
}
var g errgroup.Group
for platform, groups := range ctx.Binaries {
if !strings.Contains(platform, "linux") {
log.WithField("platform", platform).Debug("skipped non-linux builds for snapcraft")
continue
}
arch := archFor(platform)
for folder, binaries := range groups {
g.Go(func() error {
return create(ctx, folder, arch, binaries)
})
}
}
return g.Wait()
}
func archFor(key string) string {
switch {
case strings.Contains(key, "amd64"):
return "amd64"
case strings.Contains(key, "386"):
return "i386"
case strings.Contains(key, "arm64"):
return "arm64"
case strings.Contains(key, "arm6"):
return "armhf"
}
return key
}
func create(ctx *context.Context, folder, arch string, binaries []context.Binary) error {
// prime is the directory that then will be compressed to make the .snap package.
folderDir := filepath.Join(ctx.Config.Dist, folder)
primeDir := filepath.Join(folderDir, "prime")
metaDir := filepath.Join(primeDir, "meta")
if err := os.MkdirAll(metaDir, 0755); err != nil {
return err
}
var file = filepath.Join(primeDir, "meta", "snap.yaml")
log.WithField("file", file).Info("creating snap metadata")
var metadata = &SnapcraftMetadata{
Name: ctx.Config.ProjectName,
Version: ctx.Version,
Summary: ctx.Config.Snapcraft.Summary,
Description: ctx.Config.Snapcraft.Description,
Grade: ctx.Config.Snapcraft.Grade,
Confinement: ctx.Config.Snapcraft.Confinement,
Architectures: []string{arch},
Apps: make(map[string]AppsMetadata),
}
for _, binary := range binaries {
log.WithField("path", binary.Path).
WithField("name", binary.Name).
Info("passed binary to snapcraft")
metadata.Apps[binary.Name] = AppsMetadata{Command: binary.Name}
destBinaryPath := filepath.Join(primeDir, filepath.Base(binary.Path))
os.Link(binary.Path, destBinaryPath)
}
out, err := yaml.Marshal(metadata)
if err != nil {
return err
}
if err = ioutil.WriteFile(file, out, 0644); err != nil {
return err
}
snap := metadata.Name + "_" + metadata.Version + "_" + arch + ".snap"
cmd := exec.Command("snapcraft", "snap", "prime", "--output", snap)
cmd.Dir = folderDir
if out, err = cmd.CombinedOutput(); err != nil {
return errors.New(string(out))
}
ctx.AddArtifact(filepath.Join(folderDir, snap))
return nil
}

View File

@ -0,0 +1,85 @@
package snapcraft
import (
"io/ioutil"
"os"
"path/filepath"
"testing"
"github.com/goreleaser/goreleaser/config"
"github.com/goreleaser/goreleaser/context"
"github.com/stretchr/testify/assert"
)
func TestDescription(t *testing.T) {
assert.NotEmpty(t, Pipe{}.Description())
}
func TestRunPipeNoSummary(t *testing.T) {
var assert = assert.New(t)
var ctx = &context.Context{
Config: config.Project{
Snapcraft: config.Snapcraft{
Description: "dummy",
},
},
}
assert.NoError(Pipe{}.Run(ctx))
}
func TestRunPipeNoDescription(t *testing.T) {
var assert = assert.New(t)
var ctx = &context.Context{
Config: config.Project{
Snapcraft: config.Snapcraft{
Summary: "dummy",
},
},
}
assert.NoError(Pipe{}.Run(ctx))
}
func TestRunPipe(t *testing.T) {
var assert = assert.New(t)
folder, err := ioutil.TempDir("", "archivetest")
assert.NoError(err)
var dist = filepath.Join(folder, "dist")
assert.NoError(os.Mkdir(dist, 0755))
assert.NoError(os.Mkdir(filepath.Join(dist, "mybin"), 0755))
var binPath = filepath.Join(dist, "mybin", "mybin")
_, err = os.Create(binPath)
assert.NoError(err)
var ctx = &context.Context{
Version: "testversion",
Config: config.Project{
ProjectName: "mybin",
Dist: dist,
Snapcraft: config.Snapcraft{
Summary: "test summary",
Description: "test description",
},
},
}
for _, plat := range []string{"linuxamd64", "linux386", "darwinamd64"} {
ctx.AddBinary(plat, "mybin", "mybin", binPath)
}
assert.NoError(Pipe{}.Run(ctx))
}
func TestNoSnapcraftInPath(t *testing.T) {
var assert = assert.New(t)
var path = os.Getenv("PATH")
defer func() {
assert.NoError(os.Setenv("PATH", path))
}()
assert.NoError(os.Setenv("PATH", ""))
var ctx = &context.Context{
Config: config.Project{
Snapcraft: config.Snapcraft{
Summary: "dummy",
Description: "dummy",
},
},
}
assert.EqualError(Pipe{}.Run(ctx), ErrNoSnapcraft.Error())
}