github.com/marianogappa/goreleaser@v0.26.2-0.20170715090149-96acd0a9fc46/pipeline/brew/brew.go (about)

     1  // Package brew implements the Pipe, providing formula generation and
     2  // uploading it to a configured repo.
     3  package brew
     4  
     5  import (
     6  	"bytes"
     7  	"errors"
     8  	"path/filepath"
     9  	"strings"
    10  	"text/template"
    11  
    12  	"github.com/apex/log"
    13  	"github.com/goreleaser/goreleaser/checksum"
    14  	"github.com/goreleaser/goreleaser/config"
    15  	"github.com/goreleaser/goreleaser/context"
    16  	"github.com/goreleaser/goreleaser/internal/archiveformat"
    17  	"github.com/goreleaser/goreleaser/internal/client"
    18  )
    19  
    20  // ErrNoDarwin64Build when there is no build for darwin_amd64 (goos doesn't
    21  // contain darwin and/or goarch doesn't contain amd64)
    22  var ErrNoDarwin64Build = errors.New("brew tap requires a darwin amd64 build")
    23  
    24  const platform = "darwinamd64"
    25  
    26  const formula = `class {{ .Name }} < Formula
    27    desc "{{ .Desc }}"
    28    homepage "{{ .Homepage }}"
    29    url "https://github.com/{{ .Repo.Owner }}/{{ .Repo.Name }}/releases/download/{{ .Tag }}/{{ .File }}"
    30    version "{{ .Version }}"
    31    sha256 "{{ .SHA256 }}"
    32  
    33    {{- if .Dependencies }}
    34    {{ range $index, $element := .Dependencies }}
    35    depends_on "{{ . }}"
    36    {{- end }}
    37    {{- end }}
    38  
    39    {{- if .Conflicts }}
    40    {{ range $index, $element := .Conflicts }}
    41    conflicts_with "{{ . }}"
    42    {{- end }}
    43    {{- end }}
    44  
    45    def install
    46      {{- range $index, $element := .Install }}
    47      {{ . -}}
    48      {{- end }}
    49    end
    50  
    51    {{- if .Caveats }}
    52  
    53    def caveats
    54      "{{ .Caveats }}"
    55    end
    56    {{- end }}
    57  
    58    {{- if .Plist }}
    59  
    60    def plist; <<-EOS.undent
    61      {{ .Plist }}
    62  	EOS
    63    end
    64    {{- end }}
    65  end
    66  `
    67  
    68  type templateData struct {
    69  	Name         string
    70  	Desc         string
    71  	Homepage     string
    72  	Repo         config.Repo // FIXME: will not work for anything but github right now.
    73  	Tag          string
    74  	Version      string
    75  	Caveats      string
    76  	File         string
    77  	SHA256       string
    78  	Plist        string
    79  	Install      []string
    80  	Dependencies []string
    81  	Conflicts    []string
    82  }
    83  
    84  // Pipe for brew deployment
    85  type Pipe struct{}
    86  
    87  // Description of the pipe
    88  func (Pipe) Description() string {
    89  	return "Creating homebrew formula"
    90  }
    91  
    92  // Run the pipe
    93  func (Pipe) Run(ctx *context.Context) error {
    94  	return doRun(ctx, client.NewGitHub(ctx))
    95  }
    96  
    97  func doRun(ctx *context.Context, client client.Client) error {
    98  	if !ctx.Publish {
    99  		log.Warn("skipped because --skip-publish is set")
   100  		return nil
   101  	}
   102  	if ctx.Config.Brew.GitHub.Name == "" {
   103  		log.Warn("skipped because brew section is not configured")
   104  		return nil
   105  	}
   106  	if ctx.Config.Release.Draft {
   107  		log.Warn("skipped because release is marked as draft")
   108  		return nil
   109  	}
   110  	if ctx.Config.Archive.Format == "binary" {
   111  		log.Info("skipped because archive format is binary")
   112  		return nil
   113  	}
   114  	var path = filepath.Join(ctx.Config.Brew.Folder, ctx.Config.ProjectName+".rb")
   115  	log.WithField("formula", path).
   116  		WithField("repo", ctx.Config.Brew.GitHub.String()).
   117  		Info("pushing")
   118  	content, err := buildFormula(ctx, client)
   119  	if err != nil {
   120  		return err
   121  	}
   122  	return client.CreateFile(ctx, content, path)
   123  }
   124  
   125  func buildFormula(ctx *context.Context, client client.Client) (bytes.Buffer, error) {
   126  	data, err := dataFor(ctx, client)
   127  	if err != nil {
   128  		return bytes.Buffer{}, err
   129  	}
   130  	return doBuildFormula(data)
   131  }
   132  
   133  func doBuildFormula(data templateData) (bytes.Buffer, error) {
   134  	var out bytes.Buffer
   135  	tmpl, err := template.New(data.Name).Parse(formula)
   136  	if err != nil {
   137  		return out, err
   138  	}
   139  	err = tmpl.Execute(&out, data)
   140  	return out, err
   141  }
   142  
   143  func dataFor(ctx *context.Context, client client.Client) (result templateData, err error) {
   144  	var folder = ctx.Folders[platform]
   145  	if folder == "" {
   146  		return result, ErrNoDarwin64Build
   147  	}
   148  	var file = folder + "." + archiveformat.For(ctx, platform)
   149  	sum, err := checksum.SHA256(filepath.Join(ctx.Config.Dist, file))
   150  	if err != nil {
   151  		return
   152  	}
   153  	return templateData{
   154  		Name:         formulaNameFor(ctx.Config.ProjectName),
   155  		Desc:         ctx.Config.Brew.Description,
   156  		Homepage:     ctx.Config.Brew.Homepage,
   157  		Repo:         ctx.Config.Release.GitHub,
   158  		Tag:          ctx.Git.CurrentTag,
   159  		Version:      ctx.Version,
   160  		Caveats:      ctx.Config.Brew.Caveats,
   161  		File:         file,
   162  		SHA256:       sum,
   163  		Dependencies: ctx.Config.Brew.Dependencies,
   164  		Conflicts:    ctx.Config.Brew.Conflicts,
   165  		Plist:        ctx.Config.Brew.Plist,
   166  		Install:      strings.Split(ctx.Config.Brew.Install, "\n"),
   167  	}, err
   168  }
   169  
   170  func formulaNameFor(name string) string {
   171  	name = strings.Replace(name, "-", " ", -1)
   172  	name = strings.Replace(name, "_", " ", -1)
   173  	return strings.Replace(strings.Title(name), " ", "", -1)
   174  }