Repository: goreleaser/goreleaser
Pull request #1025 (2019-05-26 22:49:17)

feat: multiple snapcraft
part of #705

adding multiple snapcrafts support

codecov-io: # [Codecov](https://codecov.io/gh/goreleaser/goreleaser/pull/1025?src=pr&el=h1) Report
> Merging [#1025](https://codecov.io/gh/goreleaser/goreleaser/pull/1025?src=pr&el=desc) into [master](https://codecov.io/gh/goreleaser/goreleaser/commit/f438398d5517364f360dbfb4c0fbf8d0d423ed7c?src=pr&el=desc) will **increase** coverage by `0.06%`.
> The diff coverage is `100%`.

[![Impacted file tree graph](https://codecov.io/gh/goreleaser/goreleaser/pull/1025/graphs/tree.svg?width=650&token=3bJvv6b7hN&height=150&src=pr)](https://codecov.io/gh/goreleaser/goreleaser/pull/1025?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master    #1025      +/-   ##
==========================================
+ Coverage   88.46%   88.53%   +0.06%     
==========================================
  Files          53       53              
  Lines        2593     2607      +14     
==========================================
+ Hits         2294     2308      +14     
  Misses        236      236              
  Partials       63       63
```


| [Impacted Files](https://codecov.io/gh/goreleaser/goreleaser/pull/1025?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [pkg/config/config.go](https://codecov.io/gh/goreleaser/goreleaser/pull/1025/diff?src=pr&el=tree#diff-cGtnL2NvbmZpZy9jb25maWcuZ28=) | `100% <ø> (ø)` | :arrow_up: |
| [internal/pipe/nfpm/nfpm.go](https://codecov.io/gh/goreleaser/goreleaser/pull/1025/diff?src=pr&el=tree#diff-aW50ZXJuYWwvcGlwZS9uZnBtL25mcG0uZ28=) | `92.42% <ø> (ø)` | :arrow_up: |
| [internal/pipe/snapcraft/snapcraft.go](https://codecov.io/gh/goreleaser/goreleaser/pull/1025/diff?src=pr&el=tree#diff-aW50ZXJuYWwvcGlwZS9zbmFwY3JhZnQvc25hcGNyYWZ0Lmdv) | `89.58% <100%> (+1.12%)` | :arrow_up: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/goreleaser/goreleaser/pull/1025?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/goreleaser/goreleaser/pull/1025?src=pr&el=footer). Last update [f438398...de16adf](https://codecov.io/gh/goreleaser/goreleaser/pull/1025?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/internal/pipe/nfpm/nfpm.go b/internal/pipe/nfpm/nfpm.go
index 09927ff9..a6ece793 100644
--- a/internal/pipe/nfpm/nfpm.go
+++ b/internal/pipe/nfpm/nfpm.go
@@ -70,6 +70,7 @@ func (Pipe) Default(ctx *context.Context) error {
 func (Pipe) Run(ctx *context.Context) error {
 	for _, nfpm := range ctx.Config.NFPMs {
 		if len(nfpm.Formats) == 0 {
+			// FIXME: this assumes other nfpm configs will fail too...
 			return pipe.Skip("no output formats configured")
 		}
 		if err := doRun(ctx, nfpm); err != nil {
diff --git a/internal/pipe/snapcraft/snapcraft.go b/internal/pipe/snapcraft/snapcraft.go
index 28cf4309..b3cebef5 100644
--- a/internal/pipe/snapcraft/snapcraft.go
+++ b/internal/pipe/snapcraft/snapcraft.go
@@ -7,14 +7,17 @@ import (
 	"os"
 	"os/exec"
 	"path/filepath"
+	"reflect"
 	"strings"
 
 	"github.com/apex/log"
 	"github.com/goreleaser/goreleaser/internal/artifact"
+	"github.com/goreleaser/goreleaser/internal/deprecate"
 	"github.com/goreleaser/goreleaser/internal/linux"
 	"github.com/goreleaser/goreleaser/internal/pipe"
 	"github.com/goreleaser/goreleaser/internal/semerrgroup"
 	"github.com/goreleaser/goreleaser/internal/tmpl"
+	"github.com/goreleaser/goreleaser/pkg/config"
 	"github.com/goreleaser/goreleaser/pkg/context"
 	"github.com/pkg/errors"
 	yaml "gopkg.in/yaml.v2"
@@ -62,22 +65,45 @@ func (Pipe) String() string {
 
 // Default sets the pipe defaults
 func (Pipe) Default(ctx *context.Context) error {
-	var snap = &ctx.Config.Snapcraft
-	if snap.NameTemplate == "" {
-		snap.NameTemplate = defaultNameTemplate
+	if len(ctx.Config.Snapcrafts) == 0 {
+		ctx.Config.Snapcrafts = append(ctx.Config.Snapcrafts, ctx.Config.Snapcraft)
+		if !reflect.DeepEqual(ctx.Config.Snapcraft, config.Snapcraft{}) {
+			deprecate.Notice("snapcraft")
+		}
+	}
+	for i := range ctx.Config.Snapcrafts {
+		var snap = &ctx.Config.Snapcrafts[i]
+		if snap.NameTemplate == "" {
+			snap.NameTemplate = defaultNameTemplate
+		}
+		if len(snap.Builds) == 0 {
+			for _, b := range ctx.Config.Builds {
+				snap.Builds = append(snap.Builds, b.ID)
+			}
+		}
 	}
 	return nil
 }
 
 // Run the pipe
 func (Pipe) Run(ctx *context.Context) error {
-	if ctx.Config.Snapcraft.Summary == "" && ctx.Config.Snapcraft.Description == "" {
+	for _, snap := range ctx.Config.Snapcrafts {
+		// TODO: deal with pipe.skip?
+		if err := doRun(ctx, snap); err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+func doRun(ctx *context.Context, snap config.Snapcraft) error {
+	if snap.Summary == "" && snap.Description == "" {
 		return pipe.Skip("no summary nor description were provided")
 	}
-	if ctx.Config.Snapcraft.Summary == "" {
+	if snap.Summary == "" {
 		return ErrNoSummary
 	}
-	if ctx.Config.Snapcraft.Description == "" {
+	if snap.Description == "" {
 		return ErrNoDescription
 	}
 	_, err := exec.LookPath("snapcraft")
@@ -90,6 +116,7 @@ func (Pipe) Run(ctx *context.Context) error {
 		artifact.And(
 			artifact.ByGoos("linux"),
 			artifact.ByType(artifact.Binary),
+			artifact.ByIDs(snap.Builds...),
 		),
 	).GroupByPlatform() {
 		arch := linux.Arch(platform)
@@ -99,7 +126,7 @@ func (Pipe) Run(ctx *context.Context) error {
 		}
 		binaries := binaries
 		g.Go(func() error {
-			return create(ctx, arch, binaries)
+			return create(ctx, snap, arch, binaries)
 		})
 	}
 	return g.Wait()
@@ -118,11 +145,11 @@ func (Pipe) Publish(ctx *context.Context) error {
 	return g.Wait()
 }
 
-func create(ctx *context.Context, arch string, binaries []artifact.Artifact) error {
+func create(ctx *context.Context, snap config.Snapcraft, arch string, binaries []artifact.Artifact) error {
 	var log = log.WithField("arch", arch)
 	folder, err := tmpl.New(ctx).
-		WithArtifact(binaries[0], ctx.Config.Snapcraft.Replacements).
-		Apply(ctx.Config.Snapcraft.NameTemplate)
+		WithArtifact(binaries[0], snap.Replacements).
+		Apply(snap.NameTemplate)
 	if err != nil {
 		return err
 	}
@@ -140,25 +167,25 @@ func create(ctx *context.Context, arch string, binaries []artifact.Artifact) err
 
 	var metadata = &Metadata{
 		Version:       ctx.Version,
-		Summary:       ctx.Config.Snapcraft.Summary,
-		Description:   ctx.Config.Snapcraft.Description,
-		Grade:         ctx.Config.Snapcraft.Grade,
-		Confinement:   ctx.Config.Snapcraft.Confinement,
+		Summary:       snap.Summary,
+		Description:   snap.Description,
+		Grade:         snap.Grade,
+		Confinement:   snap.Confinement,
 		Architectures: []string{arch},
 		Apps:          map[string]AppMetadata{},
 	}
 
-	if ctx.Config.Snapcraft.Base != "" {
-		metadata.Base = ctx.Config.Snapcraft.Base
+	if snap.Base != "" {
+		metadata.Base = snap.Base
 	}
 
-	if ctx.Config.Snapcraft.License != "" {
-		metadata.License = ctx.Config.Snapcraft.License
+	if snap.License != "" {
+		metadata.License = snap.License
 	}
 
 	metadata.Name = ctx.Config.ProjectName
-	if ctx.Config.Snapcraft.Name != "" {
-		metadata.Name = ctx.Config.Snapcraft.Name
+	if snap.Name != "" {
+		metadata.Name = snap.Name
 	}
 
 	for _, binary := range binaries {
@@ -169,7 +196,7 @@ func create(ctx *context.Context, arch string, binaries []artifact.Artifact) err
 		appMetadata := AppMetadata{
 			Command: name,
 		}
-		if configAppMetadata, ok := ctx.Config.Snapcraft.Apps[name]; ok {
+		if configAppMetadata, ok := snap.Apps[name]; ok {
 			appMetadata.Plugs = configAppMetadata.Plugs
 			appMetadata.Daemon = configAppMetadata.Daemon
 			appMetadata.Command = strings.Join([]string{
@@ -178,7 +205,7 @@ func create(ctx *context.Context, arch string, binaries []artifact.Artifact) err
 			}, " ")
 		}
 		metadata.Apps[name] = appMetadata
-		metadata.Plugs = ctx.Config.Snapcraft.Plugs
+		metadata.Plugs = snap.Plugs
 
 		destBinaryPath := filepath.Join(primeDir, filepath.Base(binary.Path))
 		log.WithField("src", binary.Path).
@@ -207,20 +234,20 @@ func create(ctx *context.Context, arch string, binaries []artifact.Artifact) err
 		return err
 	}
 
-	var snap = filepath.Join(ctx.Config.Dist, folder+".snap")
-	log.WithField("snap", snap).Info("creating")
+	var snapFile = filepath.Join(ctx.Config.Dist, folder+".snap")
+	log.WithField("snap", snapFile).Info("creating")
 	/* #nosec */
-	var cmd = exec.CommandContext(ctx, "snapcraft", "pack", primeDir, "--output", snap)
+	var cmd = exec.CommandContext(ctx, "snapcraft", "pack", primeDir, "--output", snapFile)
 	if out, err = cmd.CombinedOutput(); err != nil {
 		return fmt.Errorf("failed to generate snap package: %s", string(out))
 	}
-	if !ctx.Config.Snapcraft.Publish {
+	if !snap.Publish {
 		return nil
 	}
 	ctx.Artifacts.Add(artifact.Artifact{
 		Type:   artifact.PublishableSnapcraft,
 		Name:   folder + ".snap",
-		Path:   snap,
+		Path:   snapFile,
 		Goos:   binaries[0].Goos,
 		Goarch: binaries[0].Goarch,
 		Goarm:  binaries[0].Goarm,
diff --git a/pkg/config/config.go b/pkg/config/config.go
index 330fa2d9..a97dcb86 100644
--- a/pkg/config/config.go
+++ b/pkg/config/config.go
@@ -226,6 +226,7 @@ type Snapcraft struct {
 	Replacements map[string]string `yaml:",omitempty"`
 	Publish      bool              `yaml:",omitempty"`
 
+	Builds      []string                        `yaml:",omitempty"`
 	Name        string                          `yaml:",omitempty"`
 	Summary     string                          `yaml:",omitempty"`
 	Description string                          `yaml:",omitempty"`
@@ -312,28 +313,29 @@ type Put struct {
 
 // Project includes all project configuration
 type Project struct {
-	ProjectName   string    `yaml:"project_name,omitempty"`
-	Env           []string  `yaml:",omitempty"`
-	Release       Release   `yaml:",omitempty"`
-	Brew          Homebrew  `yaml:",omitempty"`
-	Scoop         Scoop     `yaml:",omitempty"`
-	Builds        []Build   `yaml:",omitempty"`
-	Archive       Archive   `yaml:",omitempty"` // TODO: remove this
-	Archives      []Archive `yaml:",omitempty"`
-	NFPM          NFPM      `yaml:",omitempty"` // TODO: remove this
-	NFPMs         []NFPM    `yaml:"nfpms,omitempty"`
-	Snapcraft     Snapcraft `yaml:",omitempty"`
-	Snapshot      Snapshot  `yaml:",omitempty"`
-	Checksum      Checksum  `yaml:",omitempty"`
-	Dockers       []Docker  `yaml:",omitempty"`
-	Artifactories []Put     `yaml:",omitempty"`
-	Puts          []Put     `yaml:",omitempty"`
-	S3            []S3      `yaml:"s3,omitempty"`
-	Changelog     Changelog `yaml:",omitempty"`
-	Dist          string    `yaml:",omitempty"`
-	Sign          Sign      `yaml:",omitempty"`
-	EnvFiles      EnvFiles  `yaml:"env_files,omitempty"`
-	Before        Before    `yaml:",omitempty"`
+	ProjectName   string      `yaml:"project_name,omitempty"`
+	Env           []string    `yaml:",omitempty"`
+	Release       Release     `yaml:",omitempty"`
+	Brew          Homebrew    `yaml:",omitempty"`
+	Scoop         Scoop       `yaml:",omitempty"`
+	Builds        []Build     `yaml:",omitempty"`
+	Archive       Archive     `yaml:",omitempty"` // TODO: remove this
+	Archives      []Archive   `yaml:",omitempty"`
+	NFPM          NFPM        `yaml:",omitempty"` // TODO: remove this
+	NFPMs         []NFPM      `yaml:"nfpms,omitempty"`
+	Snapcraft     Snapcraft   `yaml:",omitempty"` // TODO: remove this
+	Snapcrafts    []Snapcraft `yaml:",omitempty"`
+	Snapshot      Snapshot    `yaml:",omitempty"`
+	Checksum      Checksum    `yaml:",omitempty"`
+	Dockers       []Docker    `yaml:",omitempty"`
+	Artifactories []Put       `yaml:",omitempty"`
+	Puts          []Put       `yaml:",omitempty"`
+	S3            []S3        `yaml:"s3,omitempty"`
+	Changelog     Changelog   `yaml:",omitempty"`
+	Dist          string      `yaml:",omitempty"`
+	Sign          Sign        `yaml:",omitempty"`
+	EnvFiles      EnvFiles    `yaml:"env_files,omitempty"`
+	Before        Before      `yaml:",omitempty"`
 
 	// this is a hack ¯\_(ツ)_/¯
 	SingleBuild Build `yaml:"build,omitempty"`
diff --git a/www/content/deprecations.md b/www/content/deprecations.md
index df6d55ff..7badc41e 100644
--- a/www/content/deprecations.md
+++ b/www/content/deprecations.md
@@ -33,6 +33,29 @@ to this:
 
 -->
 
+### snapcraft
+
+> since 2019-05-39
+
+We now allow multiple Snapcraft configs, so the `snapcraft` statement will be removed.
+
+Change this:
+
+```yaml
+snapcraft:
+  publish: true
+  # ...
+```
+
+to this:
+
+```yaml
+snapcrafts:
+  -
+    publish: true
+    # ...
+```
+
 ### nfpm
 
 > since 2019-05-07
diff --git a/www/content/snapcraft.md b/www/content/snapcraft.md
index 21c7977f..fd2fdf4a 100644
--- a/www/content/snapcraft.md
+++ b/www/content/snapcraft.md
@@ -18,101 +18,106 @@ Available options:
 
 ```yml
 # .goreleaser.yml
-snapcraft:
-  # You can change the name of the package.
-  # Default: `{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}`
-  name_template: "{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}"
-
-  # Replacements for GOOS and GOARCH in the package name.
-  # Keys should be valid GOOSs or GOARCHs.
-  # Values are the respective replacements.
-  # Default is empty.
-  replacements:
-    amd64: 64-bit
-    386: 32-bit
-    darwin: macOS
-    linux: Tux
-
-  # The name of the snap. This is optional.
-  # Default is project name.
-  name: drumroll
-
-  # Wether to publish the snap to the snapcraft store.
-  # Remember you need to `snapcraft login` first.
-  # Defaults to false.
-  publish: true
-
-  # Single-line elevator pitch for your amazing snap.
-  # 79 char long at most.
-  summary: Software to create fast and easy drum rolls.
-
-  # This the description of your snap. You have a paragraph or two to tell the
-  # most important story about your snap. Keep it under 100 words though,
-  # we live in tweetspace and your description wants to look good in the snap
-  # store.
-  description: |
-    This is the best drum roll application out there.
-    Install it and awe!
-
-  # A guardrail to prevent you from releasing a snap to all your users before
-  # it is ready.
-  # `devel` will let you release only to the `edge` and `beta` channels in the
-  # store. `stable` will let you release also to the `candidate` and `stable`
-  # channels. More info about channels here:
-  # https://snapcraft.io/docs/reference/channels
-  grade: stable
-
-  # Snaps can be setup to follow three different confinement policies:
-  # `strict`, `devmode` and `classic`. A strict confinement where the snap
-  # can only read and write in its own namespace is recommended. Extra
-  # permissions for strict snaps can be declared as `plugs` for the app, which
-  # are explained later. More info about confinement here:
-  # https://snapcraft.io/docs/reference/confinement
-  confinement: strict
-  
-  # Your app's license, based on SPDX license expressions: https://spdx.org/licenses
-  # Default is empty.
-  license: MIT
-  
-  # A snap of type base to be used as the execution environment for this snap.
-  # Valid values are:
-  # * bare - Empty base snap;
-  # * core - Ubuntu Core 16;
-  # * core18 - Ubuntu Core 18.
-  # Default is empty.
-  base: core18
-
-  # Each binary built by GoReleaser is an app inside the snap. In this section
-  # you can declare extra details for those binaries. It is optional.
-  apps:
-
-    # The name of the app must be the same name as the binary built or the snapcraft name.
-    drumroll:
-
-      # If your app requires extra permissions to work outside of its default
-      # confined space, declare them here.
-      # You can read the documentation about the available plugs and the
-      # things they allow:
-      # https://snapcraft.io/docs/reference/interfaces.
-      plugs: ["home", "network", "personal-files"]
-
-      # If you want your app to be autostarted and to always run in the
-      # background, you can make it a simple daemon.
-      daemon: simple
-
-      # If you any to pass args to your binary, you can add them with the
-      # args option.
-      args: --foo
-  # Allows plugs to be configured. Plugs like system-files and personal-files
-  # require this.
-  # Default is empty.
-  plugs:
-    personal-files:
-      read:
-      - $HOME/.foo
-      write:
-      - $HOME/.foo
-      - $HOME/.foobar
+snapcrafts:
+  -
+    # Build IDs for the builds you want to create snapcraft packages for.
+    # Defaults to all builds.
+    builds:
+    - foo
+    - bar
+
+    # You can change the name of the package.
+    # Default: `{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}`
+    name_template: "{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}"
+
+    # Replacements for GOOS and GOARCH in the package name.
+    # Keys should be valid GOOSs or GOARCHs.
+    # Values are the respective replacements.
+    # Default is empty.
+    replacements:
+      amd64: 64-bit
+      386: 32-bit
+      darwin: macOS
+      linux: Tux
+
+    # The name of the snap. This is optional.
+    # Default is project name.
+    name: drumroll
+
+    # Wether to publish the snap to the snapcraft store.
+    # Remember you need to `snapcraft login` first.
+    # Defaults to false.
+    publish: true
+
+    # Single-line elevator pitch for your amazing snap.
+    # 79 char long at most.
+    summary: Software to create fast and easy drum rolls.
+
+    # This the description of your snap. You have a paragraph or two to tell the
+    # most important story about your snap. Keep it under 100 words though,
+    # we live in tweetspace and your description wants to look good in the snap
+    # store.
+    description: This is the best drum roll application out there. Install it and awe!
+
+    # A guardrail to prevent you from releasing a snap to all your users before
+    # it is ready.
+    # `devel` will let you release only to the `edge` and `beta` channels in the
+    # store. `stable` will let you release also to the `candidate` and `stable`
+    # channels. More info about channels here:
+    # https://snapcraft.io/docs/reference/channels
+    grade: stable
+
+    # Snaps can be setup to follow three different confinement policies:
+    # `strict`, `devmode` and `classic`. A strict confinement where the snap
+    # can only read and write in its own namespace is recommended. Extra
+    # permissions for strict snaps can be declared as `plugs` for the app, which
+    # are explained later. More info about confinement here:
+    # https://snapcraft.io/docs/reference/confinement
+    confinement: strict
+
+    # Your app's license, based on SPDX license expressions: https://spdx.org/licenses
+    # Default is empty.
+    license: MIT
+
+    # A snap of type base to be used as the execution environment for this snap.
+    # Valid values are:
+    # * bare - Empty base snap;
+    # * core - Ubuntu Core 16;
+    # * core18 - Ubuntu Core 18.
+    # Default is empty.
+    base: core18
+
+    # Each binary built by GoReleaser is an app inside the snap. In this section
+    # you can declare extra details for those binaries. It is optional.
+    apps:
+
+      # The name of the app must be the same name as the binary built or the snapcraft name.
+      drumroll:
+
+        # If your app requires extra permissions to work outside of its default
+        # confined space, declare them here.
+        # You can read the documentation about the available plugs and the
+        # things they allow:
+        # https://snapcraft.io/docs/reference/interfaces.
+        plugs: ["home", "network", "personal-files"]
+
+        # If you want your app to be autostarted and to always run in the
+        # background, you can make it a simple daemon.
+        daemon: simple
+
+        # If you any to pass args to your binary, you can add them with the
+        # args option.
+        args: --foo
+    # Allows plugs to be configured. Plugs like system-files and personal-files
+    # require this.
+    # Default is empty.
+    plugs:
+      personal-files:
+        read:
+        - $HOME/.foo
+        write:
+        - $HOME/.foo
+        - $HOME/.foobar
 ```
 
 > Learn more about the [name template engine](/templates).


Test patch:
diff --git a/internal/pipe/snapcraft/snapcraft_test.go b/internal/pipe/snapcraft/snapcraft_test.go
index 461364ba..4a2fc5ea 100644
--- a/internal/pipe/snapcraft/snapcraft_test.go
+++ b/internal/pipe/snapcraft/snapcraft_test.go
@@ -32,7 +32,9 @@ func TestRunPipeMissingInfo(t *testing.T) {
 		t.Run(fmt.Sprintf("testing if %v happens", eerr), func(t *testing.T) {
 			var ctx = &context.Context{
 				Config: config.Project{
-					Snapcraft: snap,
+					Snapcrafts: []config.Snapcraft{
+						snap,
+					},
 				},
 			}
 			assert.Equal(t, eerr, Pipe{}.Run(ctx))
@@ -49,18 +51,36 @@ func TestRunPipe(t *testing.T) {
 	var ctx = context.New(config.Project{
 		ProjectName: "mybin",
 		Dist:        dist,
-		Snapcraft: config.Snapcraft{
-			NameTemplate: "foo_{{.Arch}}",
-			Summary:      "test summary",
-			Description:  "test description",
-			Publish:      true,
+		Snapcrafts: []config.Snapcraft{
+			{
+				NameTemplate: "foo_{{.Arch}}",
+				Summary:      "test summary",
+				Description:  "test description",
+				Publish:      true,
+				Builds:       []string{"foo"},
+			},
+			{
+				NameTemplate: "foo_and_bar_{{.Arch}}",
+				Summary:      "test summary",
+				Description:  "test description",
+				Publish:      true,
+				Builds:       []string{"foo", "bar"},
+			},
+			{
+				NameTemplate: "bar_{{.Arch}}",
+				Summary:      "test summary",
+				Description:  "test description",
+				Publish:      true,
+				Builds:       []string{"bar"},
+			},
 		},
 	})
 	ctx.Git.CurrentTag = "v1.2.3"
 	ctx.Version = "v1.2.3"
-	addBinaries(t, ctx, "mybin", dist, "mybin")
+	addBinaries(t, ctx, "foo", filepath.Join(dist, "foo"), "foo")
+	addBinaries(t, ctx, "bar", filepath.Join(dist, "bar"), "bar")
 	assert.NoError(t, Pipe{}.Run(ctx))
-	assert.Len(t, ctx.Artifacts.Filter(artifact.ByType(artifact.PublishableSnapcraft)).List(), 2)
+	assert.Len(t, ctx.Artifacts.Filter(artifact.ByType(artifact.PublishableSnapcraft)).List(), 6)
 }
 
 func TestRunPipeInvalidNameTemplate(t *testing.T) {
@@ -72,15 +92,18 @@ func TestRunPipeInvalidNameTemplate(t *testing.T) {
 	var ctx = context.New(config.Project{
 		ProjectName: "mybin",
 		Dist:        dist,
-		Snapcraft: config.Snapcraft{
-			NameTemplate: "foo_{{.Arch}",
-			Summary:      "test summary",
-			Description:  "test description",
+		Snapcrafts: []config.Snapcraft{
+			{
+				NameTemplate: "foo_{{.Arch}",
+				Summary:      "test summary",
+				Description:  "test description",
+				Builds:       []string{"foo"},
+			},
 		},
 	})
 	ctx.Git.CurrentTag = "v1.2.3"
 	ctx.Version = "v1.2.3"
-	addBinaries(t, ctx, "mybin", dist, "mybin")
+	addBinaries(t, ctx, "foo", dist, "mybin")
 	assert.EqualError(t, Pipe{}.Run(ctx), `template: tmpl:1: unexpected "}" in operand`)
 }
 
@@ -93,18 +116,21 @@ func TestRunPipeWithName(t *testing.T) {
 	var ctx = context.New(config.Project{
 		ProjectName: "testprojectname",
 		Dist:        dist,
-		Snapcraft: config.Snapcraft{
-			NameTemplate: "foo_{{.Arch}}",
-			Name:         "testsnapname",
-			Base:         "core18",
-			License:      "MIT",
-			Summary:      "test summary",
-			Description:  "test description",
+		Snapcrafts: []config.Snapcraft{
+			{
+				NameTemplate: "foo_{{.Arch}}",
+				Name:         "testsnapname",
+				Base:         "core18",
+				License:      "MIT",
+				Summary:      "test summary",
+				Description:  "test description",
+				Builds:       []string{"foo"},
+			},
 		},
 	})
 	ctx.Git.CurrentTag = "v1.2.3"
 	ctx.Version = "v1.2.3"
-	addBinaries(t, ctx, "testprojectname", dist, "mybin")
+	addBinaries(t, ctx, "foo", dist, "mybin")
 	assert.NoError(t, Pipe{}.Run(ctx))
 	yamlFile, err := ioutil.ReadFile(filepath.Join(dist, "foo_amd64", "prime", "meta", "snap.yaml"))
 	assert.NoError(t, err)
@@ -127,16 +153,19 @@ func TestRunPipeWithBinaryInDir(t *testing.T) {
 	var ctx = context.New(config.Project{
 		ProjectName: "testprojectname",
 		Dist:        dist,
-		Snapcraft: config.Snapcraft{
-			NameTemplate: "foo_{{.Arch}}",
-			Name:         "testsnapname",
-			Summary:      "test summary",
-			Description:  "test description",
+		Snapcrafts: []config.Snapcraft{
+			{
+				NameTemplate: "foo_{{.Arch}}",
+				Name:         "testsnapname",
+				Summary:      "test summary",
+				Description:  "test description",
+				Builds:       []string{"foo"},
+			},
 		},
 	})
 	ctx.Git.CurrentTag = "v1.2.3"
 	ctx.Version = "v1.2.3"
-	addBinaries(t, ctx, "testprojectname", dist, "bin/mybin")
+	addBinaries(t, ctx, "foo", dist, "bin/mybin")
 	assert.NoError(t, Pipe{}.Run(ctx))
 	yamlFile, err := ioutil.ReadFile(filepath.Join(dist, "foo_amd64", "prime", "meta", "snap.yaml"))
 	assert.NoError(t, err)
@@ -159,27 +188,30 @@ func TestRunPipeMetadata(t *testing.T) {
 	var ctx = context.New(config.Project{
 		ProjectName: "testprojectname",
 		Dist:        dist,
-		Snapcraft: config.Snapcraft{
-			NameTemplate: "foo_{{.Arch}}",
-			Summary:      "test summary",
-			Description:  "test description",
-			Apps: map[string]config.SnapcraftAppMetadata{
-				"mybin": {
-					Plugs:  []string{"home", "network", "personal-files"},
-					Daemon: "simple",
-					Args:   "--foo --bar",
+		Snapcrafts: []config.Snapcraft{
+			{
+				NameTemplate: "foo_{{.Arch}}",
+				Summary:      "test summary",
+				Description:  "test description",
+				Apps: map[string]config.SnapcraftAppMetadata{
+					"mybin": {
+						Plugs:  []string{"home", "network", "personal-files"},
+						Daemon: "simple",
+						Args:   "--foo --bar",
+					},
 				},
-			},
-			Plugs: map[string]interface{}{
-				"personal-files": map[string]interface{}{
-					"read": []string{"$HOME/test"},
+				Plugs: map[string]interface{}{
+					"personal-files": map[string]interface{}{
+						"read": []string{"$HOME/test"},
+					},
 				},
+				Builds: []string{"foo"},
 			},
 		},
 	})
 	ctx.Git.CurrentTag = "v1.2.3"
 	ctx.Version = "v1.2.3"
-	addBinaries(t, ctx, "mybin", dist, "mybin")
+	addBinaries(t, ctx, "foo", dist, "mybin")
 	assert.NoError(t, Pipe{}.Run(ctx))
 	yamlFile, err := ioutil.ReadFile(filepath.Join(dist, "foo_amd64", "prime", "meta", "snap.yaml"))
 	assert.NoError(t, err)
@@ -202,18 +234,27 @@ func TestNoSnapcraftInPath(t *testing.T) {
 	}()
 	assert.NoError(t, os.Setenv("PATH", ""))
 	var ctx = context.New(config.Project{
-		Snapcraft: config.Snapcraft{
-			Summary:     "dummy",
-			Description: "dummy",
+		Snapcrafts: []config.Snapcraft{
+			{
+				Summary:     "dummy",
+				Description: "dummy",
+			},
 		},
 	})
 	assert.EqualError(t, Pipe{}.Run(ctx), ErrNoSnapcraft.Error())
 }
 
 func TestDefault(t *testing.T) {
-	var ctx = context.New(config.Project{})
+	var ctx = context.New(config.Project{
+		Builds: []config.Build{
+			{
+				ID: "foo",
+			},
+		},
+	})
 	assert.NoError(t, Pipe{}.Default(ctx))
-	assert.Equal(t, defaultNameTemplate, ctx.Config.Snapcraft.NameTemplate)
+	assert.Equal(t, defaultNameTemplate, ctx.Config.Snapcrafts[0].NameTemplate)
+	assert.Equal(t, []string{"foo"}, ctx.Config.Snapcrafts[0].Builds)
 }
 
 func TestPublish(t *testing.T) {
@@ -230,20 +271,32 @@ func TestPublish(t *testing.T) {
 }
 
 func TestDefaultSet(t *testing.T) {
+	var ctx = context.New(config.Project{
+		Snapcrafts: []config.Snapcraft{
+			{
+				NameTemplate: "foo",
+			},
+		},
+	})
+	assert.NoError(t, Pipe{}.Default(ctx))
+	assert.Equal(t, "foo", ctx.Config.Snapcrafts[0].NameTemplate)
+}
+
+func TestDefaultDeprecate(t *testing.T) {
 	var ctx = context.New(config.Project{
 		Snapcraft: config.Snapcraft{
 			NameTemplate: "foo",
 		},
 	})
 	assert.NoError(t, Pipe{}.Default(ctx))
-	assert.Equal(t, "foo", ctx.Config.Snapcraft.NameTemplate)
+	assert.Equal(t, "foo", ctx.Config.Snapcrafts[0].NameTemplate)
 }
 
 func addBinaries(t *testing.T, ctx *context.Context, name, dist, dest string) {
 	for _, goos := range []string{"linux", "darwin"} {
 		for _, goarch := range []string{"amd64", "386", "arm6"} {
 			var folder = goos + goarch
-			assert.NoError(t, os.Mkdir(filepath.Join(dist, folder), 0755))
+			assert.NoError(t, os.MkdirAll(filepath.Join(dist, folder), 0755))
 			var binPath = filepath.Join(dist, folder, name)
 			_, err := os.Create(binPath)
 			assert.NoError(t, err)
@@ -253,6 +306,9 @@ func addBinaries(t *testing.T, ctx *context.Context, name, dist, dest string) {
 				Goarch: goarch,
 				Goos:   goos,
 				Type:   artifact.Binary,
+				Extra: map[string]interface{}{
+					"ID": name,
+				},
 			})
 		}
 	}