2018-02-10 14:38:07 +02:00
// Package scoop provides a Pipe that generates a scoop.sh App Manifest and pushes it to a bucket
package scoop
import (
"bytes"
"encoding/json"
"errors"
2021-05-24 19:30:31 +02:00
"fmt"
"os"
2020-04-21 21:11:18 +02:00
"path/filepath"
2020-02-06 03:05:43 +02:00
"strings"
2018-02-10 14:38:07 +02:00
2020-04-28 02:51:05 +02:00
"github.com/apex/log"
2018-02-10 14:38:07 +02:00
"github.com/goreleaser/goreleaser/internal/artifact"
"github.com/goreleaser/goreleaser/internal/client"
2018-09-12 19:18:01 +02:00
"github.com/goreleaser/goreleaser/internal/pipe"
2018-08-21 03:06:55 +02:00
"github.com/goreleaser/goreleaser/internal/tmpl"
2018-08-15 04:50:20 +02:00
"github.com/goreleaser/goreleaser/pkg/context"
2018-02-10 14:38:07 +02:00
)
2020-05-26 05:48:10 +02:00
// ErrNoWindows when there is no build for windows (goos doesn't contain windows).
2018-02-10 14:38:07 +02:00
var ErrNoWindows = errors . New ( "scoop requires a windows build" )
2020-05-26 05:48:10 +02:00
// ErrTokenTypeNotImplementedForScoop indicates that a new token type was not implemented for this pipe.
2019-08-13 20:28:03 +02:00
var ErrTokenTypeNotImplementedForScoop = errors . New ( "token type not implemented for scoop pipe" )
2020-05-26 05:48:10 +02:00
// Pipe for build.
2018-02-10 14:38:07 +02:00
type Pipe struct { }
func ( Pipe ) String ( ) string {
2020-03-06 06:25:09 +02:00
return "scoop manifests"
2018-02-10 14:38:07 +02:00
}
2020-05-26 05:48:10 +02:00
// Publish scoop manifest.
2018-10-10 17:47:31 +02:00
func ( Pipe ) Publish ( ctx * context . Context ) error {
2020-04-29 20:09:00 +02:00
if ctx . SkipPublish {
return pipe . ErrSkipPublishEnabled
}
2020-07-06 22:12:41 +02:00
2019-06-29 16:02:40 +02:00
client , err := client . New ( ctx )
2018-02-10 14:38:07 +02:00
if err != nil {
return err
}
return doRun ( ctx , client )
}
2020-05-26 05:48:10 +02:00
// Default sets the pipe defaults.
2018-02-10 14:38:07 +02:00
func ( Pipe ) Default ( ctx * context . Context ) error {
2018-09-30 16:04:32 +02:00
if ctx . Config . Scoop . Name == "" {
ctx . Config . Scoop . Name = ctx . Config . ProjectName
}
2018-02-10 14:38:07 +02:00
if ctx . Config . Scoop . CommitAuthor . Name == "" {
ctx . Config . Scoop . CommitAuthor . Name = "goreleaserbot"
}
if ctx . Config . Scoop . CommitAuthor . Email == "" {
ctx . Config . Scoop . CommitAuthor . Email = "goreleaser@carlosbecker.com"
}
2019-08-13 20:28:03 +02:00
2020-04-29 22:45:18 +02:00
if ctx . Config . Scoop . CommitMessageTemplate == "" {
ctx . Config . Scoop . CommitMessageTemplate = "Scoop update for {{ .ProjectName }} version {{ .Tag }}"
}
2018-02-10 14:38:07 +02:00
return nil
}
2020-07-06 22:12:41 +02:00
func doRun ( ctx * context . Context , cl client . Client ) error {
scoop := ctx . Config . Scoop
if scoop . Bucket . Name == "" {
2021-04-19 14:31:57 +02:00
return pipe . ErrSkipDisabledPipe
2018-02-10 14:38:07 +02:00
}
2019-08-13 20:28:03 +02:00
2020-07-06 22:12:41 +02:00
if scoop . Bucket . Token != "" {
token , err := tmpl . New ( ctx ) . ApplySingleEnvOnly ( scoop . Bucket . Token )
if err != nil {
return err
}
log . Debug ( "using custom token to publish scoop manifest" )
c , err := client . NewWithToken ( ctx , token )
if err != nil {
return err
}
cl = c
}
2019-08-13 20:28:03 +02:00
2019-12-29 20:02:15 +02:00
// TODO: multiple archives
if ctx . Config . Archives [ 0 ] . Format == "binary" {
2018-09-12 19:18:01 +02:00
return pipe . Skip ( "archive format is binary" )
2018-02-10 14:38:07 +02:00
}
2021-04-19 14:31:57 +02:00
archives := ctx . Artifacts . Filter (
2018-02-10 14:38:07 +02:00
artifact . And (
artifact . ByGoos ( "windows" ) ,
artifact . ByType ( artifact . UploadableArchive ) ,
) ,
) . List ( )
if len ( archives ) == 0 {
return ErrNoWindows
}
2021-04-19 14:31:57 +02:00
path := scoop . Name + ".json"
2018-02-10 14:38:07 +02:00
2020-07-06 22:12:41 +02:00
data , err := dataFor ( ctx , cl , archives )
2020-07-06 15:48:17 +02:00
if err != nil {
return err
}
content , err := doBuildManifest ( data )
2018-02-10 14:38:07 +02:00
if err != nil {
return err
}
2021-05-24 19:30:31 +02:00
filename := filepath . Join ( ctx . Config . Dist , path )
log . WithField ( "manifest" , filename ) . Info ( "writing" )
if err := os . WriteFile ( filename , content . Bytes ( ) , 0 o644 ) ; err != nil {
return fmt . Errorf ( "failed to write scoop manifest: %w" , err )
2020-02-06 03:05:43 +02:00
}
2021-05-24 19:30:31 +02:00
2020-07-06 22:12:41 +02:00
if strings . TrimSpace ( scoop . SkipUpload ) == "true" {
2020-02-06 03:05:43 +02:00
return pipe . Skip ( "scoop.skip_upload is true" )
2020-02-06 03:10:16 +02:00
}
2021-05-24 19:30:31 +02:00
if ctx . SkipPublish {
return pipe . ErrSkipPublishEnabled
}
2020-07-06 22:12:41 +02:00
if strings . TrimSpace ( scoop . SkipUpload ) == "auto" && ctx . Semver . Prerelease != "" {
2020-02-06 03:10:16 +02:00
return pipe . Skip ( "release is prerelease" )
2018-02-10 14:38:07 +02:00
}
if ctx . Config . Release . Draft {
2018-09-12 19:18:01 +02:00
return pipe . Skip ( "release is marked as draft" )
2018-02-10 14:38:07 +02:00
}
2019-08-29 01:34:09 +02:00
if ctx . Config . Release . Disable {
return pipe . Skip ( "release is disabled" )
}
2020-04-29 22:45:18 +02:00
commitMessage , err := tmpl . New ( ctx ) .
2020-07-06 22:12:41 +02:00
Apply ( scoop . CommitMessageTemplate )
2020-04-29 22:45:18 +02:00
if err != nil {
return err
}
2020-07-06 22:12:41 +02:00
repo := client . RepoFromRef ( scoop . Bucket )
return cl . CreateFile (
2018-02-10 14:38:07 +02:00
ctx ,
2020-07-06 22:12:41 +02:00
scoop . CommitAuthor ,
repo ,
2019-06-26 19:12:33 +02:00
content . Bytes ( ) ,
2018-02-15 04:13:57 +02:00
path ,
2020-04-29 22:45:18 +02:00
commitMessage ,
2018-02-15 04:13:57 +02:00
)
2018-02-10 14:38:07 +02:00
}
2020-05-26 05:48:10 +02:00
// Manifest represents a scoop.sh App Manifest.
// more info: https://github.com/lukesampson/scoop/wiki/App-Manifests
2018-02-10 14:38:07 +02:00
type Manifest struct {
2020-12-05 17:16:32 +02:00
Version string ` json:"version" ` // The version of the app that this manifest installs.
Architecture map [ string ] Resource ` json:"architecture" ` // `architecture`: If the app has 32- and 64-bit versions, architecture can be used to wrap the differences.
Homepage string ` json:"homepage,omitempty" ` // `homepage`: The home page for the program.
License string ` json:"license,omitempty" ` // `license`: The software license for the program. For well-known licenses, this will be a string like "MIT" or "GPL2". For custom licenses, this should be the URL of the license.
Description string ` json:"description,omitempty" ` // Description of the app
Persist [ ] string ` json:"persist,omitempty" ` // Persist data between updates
PreInstall [ ] string ` json:"pre_install,omitempty" ` // An array of strings, of the commands to be executed before an application is installed.
PostInstall [ ] string ` json:"post_install,omitempty" ` // An array of strings, of the commands to be executed after an application is installed.
2018-02-10 14:38:07 +02:00
}
2020-05-26 05:48:10 +02:00
// Resource represents a combination of a url and a binary name for an architecture.
2018-02-10 14:38:07 +02:00
type Resource struct {
2019-01-05 16:30:26 +02:00
URL string ` json:"url" ` // URL to the archive
Bin [ ] string ` json:"bin" ` // name of binary inside the archive
Hash string ` json:"hash" ` // the archive checksum
2018-02-10 14:38:07 +02:00
}
2020-07-06 15:48:17 +02:00
func doBuildManifest ( manifest Manifest ) ( bytes . Buffer , error ) {
2018-08-21 03:20:04 +02:00
var result bytes . Buffer
2020-07-06 15:48:17 +02:00
data , err := json . MarshalIndent ( manifest , "" , " " )
if err != nil {
return result , err
}
_ , err = result . Write ( data )
return result , err
}
func dataFor ( ctx * context . Context , cl client . Client , artifacts [ ] * artifact . Artifact ) ( Manifest , error ) {
2021-04-19 14:31:57 +02:00
manifest := Manifest {
2018-02-10 14:38:07 +02:00
Version : ctx . Version ,
2018-12-12 22:24:22 +02:00
Architecture : map [ string ] Resource { } ,
2018-02-10 14:38:07 +02:00
Homepage : ctx . Config . Scoop . Homepage ,
License : ctx . Config . Scoop . License ,
Description : ctx . Config . Scoop . Description ,
2018-08-25 22:24:42 +02:00
Persist : ctx . Config . Scoop . Persist ,
2020-12-05 17:16:32 +02:00
PreInstall : ctx . Config . Scoop . PreInstall ,
PostInstall : ctx . Config . Scoop . PostInstall ,
2018-02-10 14:38:07 +02:00
}
2019-08-13 20:28:03 +02:00
if ctx . Config . Scoop . URLTemplate == "" {
2020-07-06 15:48:17 +02:00
url , err := cl . ReleaseURLTemplate ( ctx )
if err != nil {
if client . IsNotImplementedErr ( err ) {
return manifest , ErrTokenTypeNotImplementedForScoop
}
return manifest , err
2019-08-13 20:28:03 +02:00
}
2020-07-06 15:48:17 +02:00
ctx . Config . Scoop . URLTemplate = url
2019-08-13 20:28:03 +02:00
}
2018-02-10 14:38:07 +02:00
for _ , artifact := range artifacts {
2021-04-18 20:40:23 +02:00
if artifact . Goos != "windows" {
continue
}
var arch string
switch {
case artifact . Goarch == "386" :
2018-02-15 04:13:57 +02:00
arch = "32bit"
2021-04-18 20:40:23 +02:00
case artifact . Goarch == "amd64" :
arch = "64bit"
default :
continue
2018-02-15 04:13:57 +02:00
}
2018-08-21 03:06:55 +02:00
2018-08-21 03:20:04 +02:00
url , err := tmpl . New ( ctx ) .
WithArtifact ( artifact , map [ string ] string { } ) .
Apply ( ctx . Config . Scoop . URLTemplate )
2018-08-21 03:06:55 +02:00
if err != nil {
2020-07-06 15:48:17 +02:00
return manifest , err
2018-08-21 03:06:55 +02:00
}
2019-02-04 21:27:51 +02:00
sum , err := artifact . Checksum ( "sha256" )
2018-09-06 12:20:12 +02:00
if err != nil {
2020-07-06 15:48:17 +02:00
return manifest , err
2018-09-06 12:20:12 +02:00
}
2020-04-28 02:51:05 +02:00
log . WithFields ( log . Fields {
"artifactExtras" : artifact . Extra ,
"fromURLTemplate" : ctx . Config . Scoop . URLTemplate ,
"templatedBrewURL" : url ,
"sum" : sum ,
} ) . Debug ( "scoop url templating" )
2018-02-15 04:13:57 +02:00
manifest . Architecture [ arch ] = Resource {
2018-09-06 12:20:12 +02:00
URL : url ,
2019-01-05 16:30:26 +02:00
Bin : binaries ( artifact ) ,
2018-09-06 12:20:12 +02:00
Hash : sum ,
2018-02-10 14:38:07 +02:00
}
}
2020-07-06 15:48:17 +02:00
return manifest , nil
2018-02-10 14:38:07 +02:00
}
2019-01-05 16:30:26 +02:00
2019-08-12 22:44:48 +02:00
func binaries ( a * artifact . Artifact ) [ ] string {
2019-01-05 16:30:26 +02:00
// nolint: prealloc
var bins [ ] string
2021-04-19 14:31:57 +02:00
wrap := a . ExtraOr ( "WrappedIn" , "" ) . ( string )
2019-08-12 22:44:48 +02:00
for _ , b := range a . ExtraOr ( "Builds" , [ ] * artifact . Artifact { } ) . ( [ ] * artifact . Artifact ) {
2020-04-21 21:11:18 +02:00
bins = append ( bins , filepath . Join ( wrap , b . Name ) )
2019-01-05 16:30:26 +02:00
}
return bins
}