2018-02-10 12:38:07 +00: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 13:30:31 -04:00
"fmt"
"os"
2021-06-26 17:14:42 +00:00
"path"
2020-04-21 16:11:18 -03:00
"path/filepath"
2020-02-05 19:05:43 -06:00
"strings"
2018-02-10 12:38:07 +00:00
2022-06-21 21:11:15 -03:00
"github.com/caarlos0/log"
2018-02-10 12:38:07 +00:00
"github.com/goreleaser/goreleaser/internal/artifact"
"github.com/goreleaser/goreleaser/internal/client"
2022-01-11 09:15:28 -03:00
"github.com/goreleaser/goreleaser/internal/commitauthor"
2018-09-12 14:18:01 -03:00
"github.com/goreleaser/goreleaser/internal/pipe"
2018-08-21 04:06:55 +03:00
"github.com/goreleaser/goreleaser/internal/tmpl"
2021-09-18 10:21:29 -03:00
"github.com/goreleaser/goreleaser/pkg/config"
2018-08-14 23:50:20 -03:00
"github.com/goreleaser/goreleaser/pkg/context"
2018-02-10 12:38:07 +00:00
)
2020-05-26 00:48:10 -03:00
// ErrNoWindows when there is no build for windows (goos doesn't contain windows).
2022-04-11 20:19:58 -03:00
var ErrNoWindows = errors . New ( "scoop requires a windows build and archive" )
2018-02-10 12:38:07 +00:00
2021-09-18 10:21:29 -03:00
const scoopConfigExtra = "ScoopConfig"
// Pipe that builds and publishes scoop manifests.
2018-02-10 12:38:07 +00:00
type Pipe struct { }
2021-09-18 10:21:29 -03:00
func ( Pipe ) String ( ) string { return "scoop manifests" }
func ( Pipe ) Skip ( ctx * context . Context ) bool { return ctx . Config . Scoop . Bucket . Name == "" }
// Run creates the scoop manifest locally.
func ( Pipe ) Run ( ctx * context . Context ) error {
client , err := client . New ( ctx )
if err != nil {
return err
}
return doRun ( ctx , client )
2018-02-10 12:38:07 +00:00
}
2020-05-26 00:48:10 -03:00
// Publish scoop manifest.
2018-10-10 12:47:31 -03:00
func ( Pipe ) Publish ( ctx * context . Context ) error {
2019-06-29 16:02:40 +02:00
client , err := client . New ( ctx )
2018-02-10 12:38:07 +00:00
if err != nil {
return err
}
2021-09-18 10:21:29 -03:00
return doPublish ( ctx , client )
2018-02-10 12:38:07 +00:00
}
2020-05-26 00:48:10 -03:00
// Default sets the pipe defaults.
2018-02-10 12:38:07 +00:00
func ( Pipe ) Default ( ctx * context . Context ) error {
2018-09-30 11:04:32 -03:00
if ctx . Config . Scoop . Name == "" {
ctx . Config . Scoop . Name = ctx . Config . ProjectName
}
2022-01-11 09:15:28 -03:00
ctx . Config . Scoop . CommitAuthor = commitauthor . Default ( ctx . Config . Scoop . CommitAuthor )
2020-04-29 13:45:18 -07:00
if ctx . Config . Scoop . CommitMessageTemplate == "" {
ctx . Config . Scoop . CommitMessageTemplate = "Scoop update for {{ .ProjectName }} version {{ .Tag }}"
}
2022-04-11 22:43:22 -03:00
if ctx . Config . Scoop . Goamd64 == "" {
2022-04-13 21:29:39 -03:00
ctx . Config . Scoop . Goamd64 = "v1"
2022-04-11 22:43:22 -03:00
}
2018-02-10 12:38:07 +00:00
return nil
}
2020-07-06 21:12:41 +01:00
func doRun ( ctx * context . Context , cl client . Client ) error {
scoop := ctx . Config . Scoop
2019-08-13 20:28:03 +02:00
2021-04-19 09:31:57 -03:00
archives := ctx . Artifacts . Filter (
2018-02-10 12:38:07 +00:00
artifact . And (
artifact . ByGoos ( "windows" ) ,
artifact . ByType ( artifact . UploadableArchive ) ,
2022-04-11 22:43:22 -03:00
artifact . Or (
artifact . And (
artifact . ByGoarch ( "amd64" ) ,
artifact . ByGoamd64 ( scoop . Goamd64 ) ,
) ,
artifact . ByGoarch ( "386" ) ,
) ,
2018-02-10 12:38:07 +00:00
) ,
) . List ( )
if len ( archives ) == 0 {
return ErrNoWindows
}
2021-06-26 17:14:42 +00:00
filename := scoop . Name + ".json"
2018-02-10 12:38:07 +00:00
2020-07-06 21:12:41 +01:00
data , err := dataFor ( ctx , cl , archives )
2020-07-06 14:48:17 +01:00
if err != nil {
return err
}
content , err := doBuildManifest ( data )
2018-02-10 12:38:07 +00:00
if err != nil {
return err
}
2021-09-18 10:21:29 -03:00
path := filepath . Join ( ctx . Config . Dist , filename )
log . WithField ( "manifest" , path ) . Info ( "writing" )
if err := os . WriteFile ( path , content . Bytes ( ) , 0 o644 ) ; err != nil {
2021-05-24 13:30:31 -04:00
return fmt . Errorf ( "failed to write scoop manifest: %w" , err )
2020-02-05 19:05:43 -06:00
}
2021-05-24 13:30:31 -04:00
2021-09-18 10:21:29 -03:00
ctx . Artifacts . Add ( & artifact . Artifact {
Name : filename ,
Path : path ,
Type : artifact . ScoopManifest ,
Extra : map [ string ] interface { } {
scoopConfigExtra : scoop ,
} ,
} )
return nil
}
func doPublish ( ctx * context . Context , cl client . Client ) error {
manifests := ctx . Artifacts . Filter ( artifact . ByType ( artifact . ScoopManifest ) ) . List ( )
if len ( manifests ) == 0 { // should never happen
return nil
}
manifest := manifests [ 0 ]
2022-06-23 23:36:19 -03:00
scoop , err := artifact . Extra [ config . Scoop ] ( * manifest , scoopConfigExtra )
if err != nil {
return err
}
2021-09-18 10:21:29 -03:00
cl , err = client . NewIfToken ( ctx , cl , scoop . Bucket . Token )
if err != nil {
return err
}
2020-07-06 21:12:41 +01:00
if strings . TrimSpace ( scoop . SkipUpload ) == "true" {
2020-02-05 19:05:43 -06:00
return pipe . Skip ( "scoop.skip_upload is true" )
2020-02-05 22:10:16 -03:00
}
2020-07-06 21:12:41 +01:00
if strings . TrimSpace ( scoop . SkipUpload ) == "auto" && ctx . Semver . Prerelease != "" {
2020-02-05 22:10:16 -03:00
return pipe . Skip ( "release is prerelease" )
2018-02-10 12:38:07 +00:00
}
if ctx . Config . Release . Draft {
2018-09-12 14:18:01 -03:00
return pipe . Skip ( "release is marked as draft" )
2018-02-10 12:38:07 +00:00
}
2019-08-29 01:34:09 +02:00
if ctx . Config . Release . Disable {
return pipe . Skip ( "release is disabled" )
}
2020-04-29 13:45:18 -07:00
2021-09-01 10:27:31 -03:00
commitMessage , err := tmpl . New ( ctx ) . Apply ( scoop . CommitMessageTemplate )
2020-04-29 13:45:18 -07:00
if err != nil {
return err
}
2022-01-11 09:15:28 -03:00
author , err := commitauthor . Get ( ctx , scoop . CommitAuthor )
if err != nil {
return err
}
2021-09-18 10:21:29 -03:00
content , err := os . ReadFile ( manifest . Path )
if err != nil {
return err
}
2020-07-06 21:12:41 +01:00
repo := client . RepoFromRef ( scoop . Bucket )
return cl . CreateFile (
2018-02-10 12:38:07 +00:00
ctx ,
2022-01-11 09:15:28 -03:00
author ,
2020-07-06 21:12:41 +01:00
repo ,
2021-09-18 10:21:29 -03:00
content ,
path . Join ( scoop . Folder , manifest . Name ) ,
2020-04-29 13:45:18 -07:00
commitMessage ,
2018-02-15 00:13:57 -02:00
)
2018-02-10 12:38:07 +00:00
}
2020-05-26 00:48:10 -03:00
// Manifest represents a scoop.sh App Manifest.
// more info: https://github.com/lukesampson/scoop/wiki/App-Manifests
2018-02-10 12:38:07 +00:00
type Manifest struct {
2020-12-05 07:16:32 -08: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 12:38:07 +00:00
}
2020-05-26 00:48:10 -03:00
// Resource represents a combination of a url and a binary name for an architecture.
2018-02-10 12:38:07 +00:00
type Resource struct {
2019-01-05 12: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 12:38:07 +00:00
}
2020-07-06 14:48:17 +01:00
func doBuildManifest ( manifest Manifest ) ( bytes . Buffer , error ) {
2018-08-20 22:20:04 -03:00
var result bytes . Buffer
2020-07-06 14:48:17 +01: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 09:31:57 -03:00
manifest := Manifest {
2018-02-10 12:38:07 +00:00
Version : ctx . Version ,
2018-12-12 18:24:22 -02:00
Architecture : map [ string ] Resource { } ,
2018-02-10 12:38:07 +00:00
Homepage : ctx . Config . Scoop . Homepage ,
License : ctx . Config . Scoop . License ,
Description : ctx . Config . Scoop . Description ,
2018-08-25 23:24:42 +03:00
Persist : ctx . Config . Scoop . Persist ,
2020-12-05 07:16:32 -08:00
PreInstall : ctx . Config . Scoop . PreInstall ,
PostInstall : ctx . Config . Scoop . PostInstall ,
2018-02-10 12:38:07 +00:00
}
2019-08-13 20:28:03 +02:00
if ctx . Config . Scoop . URLTemplate == "" {
2020-07-06 14:48:17 +01:00
url , err := cl . ReleaseURLTemplate ( ctx )
if err != nil {
return manifest , err
2019-08-13 20:28:03 +02:00
}
2020-07-06 14:48:17 +01:00
ctx . Config . Scoop . URLTemplate = url
2019-08-13 20:28:03 +02:00
}
2018-02-10 12:38:07 +00: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 00: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 00:13:57 -02:00
}
2018-08-21 04:06:55 +03:00
2018-08-20 22:20:04 -03:00
url , err := tmpl . New ( ctx ) .
WithArtifact ( artifact , map [ string ] string { } ) .
Apply ( ctx . Config . Scoop . URLTemplate )
2018-08-21 04:06:55 +03:00
if err != nil {
2020-07-06 14:48:17 +01:00
return manifest , err
2018-08-21 04:06:55 +03:00
}
2019-02-04 17:27:51 -02:00
sum , err := artifact . Checksum ( "sha256" )
2018-09-06 13:20:12 +03:00
if err != nil {
2020-07-06 14:48:17 +01:00
return manifest , err
2018-09-06 13:20:12 +03:00
}
2020-04-27 21:51:05 -03:00
log . WithFields ( log . Fields {
"artifactExtras" : artifact . Extra ,
"fromURLTemplate" : ctx . Config . Scoop . URLTemplate ,
"templatedBrewURL" : url ,
"sum" : sum ,
} ) . Debug ( "scoop url templating" )
2022-06-23 23:36:19 -03:00
binaries , err := binaries ( * artifact )
if err != nil {
return manifest , err
}
2018-02-15 00:13:57 -02:00
manifest . Architecture [ arch ] = Resource {
2018-09-06 13:20:12 +03:00
URL : url ,
2022-06-23 23:36:19 -03:00
Bin : binaries ,
2018-09-06 13:20:12 +03:00
Hash : sum ,
2018-02-10 12:38:07 +00:00
}
}
2020-07-06 14:48:17 +01:00
return manifest , nil
2018-02-10 12:38:07 +00:00
}
2019-01-05 12:30:26 -02:00
2022-06-23 23:36:19 -03:00
func binaries ( a artifact . Artifact ) ( [ ] string , error ) {
2019-01-05 12:30:26 -02:00
// nolint: prealloc
var bins [ ] string
2022-06-23 23:36:19 -03:00
wrap := artifact . ExtraOr ( a , artifact . ExtraWrappedIn , "" )
builds , err := artifact . Extra [ [ ] artifact . Artifact ] ( a , artifact . ExtraBuilds )
if err != nil {
return nil , err
}
for _ , b := range builds {
2020-04-21 16:11:18 -03:00
bins = append ( bins , filepath . Join ( wrap , b . Name ) )
2019-01-05 12:30:26 -02:00
}
2022-06-23 23:36:19 -03:00
return bins , nil
2019-01-05 12:30:26 -02:00
}