mirror of
https://github.com/jimeh/emacs-builds.git
synced 2026-02-19 13:06:40 +00:00
feat(signing)!: sign, notarize and staple Emacs.app and disk image
Update the build-emacs-for-macos to v0.5.0, which includes a new emacs-builder CLI tool written in Go, which handles signing, dmg packaging, notarizing and stapling of Emacs builds. The main build process is still handled by the old Ruby script for now however. emacs-builder also includes plan and release commands, negating the need for the our custom github-release CLI tool. BREAKING CHANGE: Release assets are now signed *.dmg files instead of *.tbz archives.
This commit is contained in:
@@ -1,57 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
func checkCmd() *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "check",
|
||||
Usage: "Check if GitHub release and asset exists",
|
||||
UsageText: "github-release [global options] check [options]",
|
||||
Action: actionHandler(checkAction),
|
||||
}
|
||||
}
|
||||
|
||||
func checkAction(c *cli.Context, opts *globalOptions) error {
|
||||
gh := opts.gh
|
||||
repo := opts.repo
|
||||
plan, err := LoadPlan(opts.plan)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf(
|
||||
"==> Checking github.com/%s for release: %s\n",
|
||||
repo.String(), plan.Release.Name,
|
||||
)
|
||||
|
||||
release, resp, err := gh.Repositories.GetReleaseByTag(
|
||||
c.Context, repo.Owner, repo.Name, plan.Release.Name,
|
||||
)
|
||||
if err != nil {
|
||||
if resp.StatusCode == http.StatusNotFound {
|
||||
return fmt.Errorf("release %s does not exist", plan.Release.Name)
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Println(" -> Release exists")
|
||||
|
||||
filename := filepath.Base(plan.Archive)
|
||||
|
||||
fmt.Printf("==> Checking release for asset: %s\n", filename)
|
||||
for _, a := range release.Assets {
|
||||
if a.Name != nil && filename == *a.Name {
|
||||
fmt.Println(" -> Asset exists")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf("release does contain asset: %s", filename)
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/google/go-github/v35/github"
|
||||
)
|
||||
|
||||
type Commit struct {
|
||||
Repo *Repo `yaml:"repo"`
|
||||
Ref string `yaml:"ref"`
|
||||
SHA string `yaml:"sha"`
|
||||
Date *time.Time `yaml:"date"`
|
||||
Author string `yaml:"author"`
|
||||
Committer string `yaml:"committer"`
|
||||
Message string `yaml:"message"`
|
||||
}
|
||||
|
||||
func NewCommit(repo *Repo, ref string, rc *github.RepositoryCommit) *Commit {
|
||||
return &Commit{
|
||||
Repo: repo,
|
||||
Ref: ref,
|
||||
SHA: rc.GetSHA(),
|
||||
Date: rc.GetCommit().GetCommitter().Date,
|
||||
Author: fmt.Sprintf(
|
||||
"%s <%s>",
|
||||
rc.GetCommit().GetAuthor().GetName(),
|
||||
rc.GetCommit().GetAuthor().GetEmail(),
|
||||
),
|
||||
Committer: fmt.Sprintf(
|
||||
"%s <%s>",
|
||||
rc.GetCommit().GetCommitter().GetName(),
|
||||
rc.GetCommit().GetCommitter().GetEmail(),
|
||||
),
|
||||
Message: rc.GetCommit().GetMessage(),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Commit) ShortSHA() string {
|
||||
return s.SHA[0:7]
|
||||
}
|
||||
|
||||
func (s *Commit) DateString() string {
|
||||
return s.Date.Format("2006-01-02")
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"github.com/google/go-github/v35/github"
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
func NewGitHubClient(ctx context.Context, token string) *github.Client {
|
||||
var tc *http.Client
|
||||
|
||||
if token != "" {
|
||||
ts := oauth2.StaticTokenSource(
|
||||
&oauth2.Token{AccessToken: token},
|
||||
)
|
||||
tc = oauth2.NewClient(ctx, ts)
|
||||
}
|
||||
|
||||
return github.NewClient(tc)
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/google/go-github/v35/github"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
var app = &cli.App{
|
||||
Name: "github-release",
|
||||
Usage: "manage GitHub releases",
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "github-token",
|
||||
Aliases: []string{"t"},
|
||||
Usage: "GitHub API Token (read from GITHUB_TOKEN if set)",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "release-repo",
|
||||
Aliases: []string{"r"},
|
||||
Usage: "Owner/name of GitHub repo to publish releases to",
|
||||
EnvVars: []string{"GITHUB_REPOSITORY"},
|
||||
Value: "jimeh/emacs-builds",
|
||||
},
|
||||
&cli.PathFlag{
|
||||
Name: "plan",
|
||||
Aliases: []string{"p"},
|
||||
Usage: "Load plan from `FILE`",
|
||||
EnvVars: []string{"BUILD_PLAN"},
|
||||
Required: true,
|
||||
TakesFile: true,
|
||||
},
|
||||
},
|
||||
Commands: []*cli.Command{
|
||||
checkCmd(),
|
||||
publishCmd(),
|
||||
planCmd(),
|
||||
},
|
||||
}
|
||||
|
||||
type globalOptions struct {
|
||||
gh *github.Client
|
||||
repo *Repo
|
||||
plan string
|
||||
}
|
||||
|
||||
func actionHandler(
|
||||
f func(*cli.Context, *globalOptions) error,
|
||||
) func(*cli.Context) error {
|
||||
return func(c *cli.Context) error {
|
||||
token := c.String("github-token")
|
||||
if t := os.Getenv("GITHUB_TOKEN"); t != "" {
|
||||
token = t
|
||||
}
|
||||
|
||||
opts := &globalOptions{
|
||||
gh: NewGitHubClient(c.Context, token),
|
||||
repo: NewRepo(c.String("release-repo")),
|
||||
plan: c.String("plan"),
|
||||
}
|
||||
|
||||
return f(c, opts)
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
err := app.Run(os.Args)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "ERROR: %s\n", err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"os/exec"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type OSInfo struct {
|
||||
Name string `yaml:"name"`
|
||||
Version string `yaml:"version"`
|
||||
Arch string `yaml:"arch"`
|
||||
}
|
||||
|
||||
func NewOSInfo() (*OSInfo, error) {
|
||||
version, err := exec.Command("sw_vers", "-productVersion").CombinedOutput()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
arch, err := exec.Command("uname", "-m").CombinedOutput()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &OSInfo{
|
||||
Name: "macOS",
|
||||
Version: strings.TrimSpace(string(version)),
|
||||
Arch: strings.TrimSpace(string(arch)),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *OSInfo) ShortVersion() string {
|
||||
parts := strings.Split(s.Version, ".")
|
||||
max := len(parts)
|
||||
if max > 2 {
|
||||
max = 2
|
||||
}
|
||||
|
||||
return strings.Join(parts[0:max], ".")
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
type Release struct {
|
||||
Name string `yaml:"name"`
|
||||
Title string `yaml:"title,omitempty"`
|
||||
Draft bool `yaml:"draft,omitempty"`
|
||||
Pre bool `yaml:"prerelease,omitempty"`
|
||||
}
|
||||
|
||||
type Plan struct {
|
||||
Commit *Commit `yaml:"commit"`
|
||||
OS *OSInfo `yaml:"os"`
|
||||
Release *Release `yaml:"release"`
|
||||
Archive string `yaml:"archive"`
|
||||
}
|
||||
|
||||
func LoadPlan(filename string) (*Plan, error) {
|
||||
b, err := os.ReadFile(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
plan := &Plan{}
|
||||
err = yaml.Unmarshal(b, plan)
|
||||
|
||||
return plan, err
|
||||
}
|
||||
@@ -1,142 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
|
||||
"github.com/urfave/cli/v2"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
var nonAlphaNum = regexp.MustCompile(`[^\w-_]+`)
|
||||
|
||||
func planCmd() *cli.Command {
|
||||
wd, err := os.Getwd()
|
||||
if err != nil {
|
||||
wd = ""
|
||||
}
|
||||
|
||||
return &cli.Command{
|
||||
Name: "plan",
|
||||
Usage: "Plan if GitHub release and asset exists",
|
||||
UsageText: "github-release [global options] plan [<branch/tag>]",
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "emacs-mirror-repo",
|
||||
Usage: "Github owner/repo to get Emacs commit info from",
|
||||
Aliases: []string{"e"},
|
||||
EnvVars: []string{"EMACS_MIRROR_REPO"},
|
||||
Value: "emacs-mirror/emacs",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "work-dir",
|
||||
Usage: "Github owner/repo to get Emacs commit info from",
|
||||
Value: wd,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "sha",
|
||||
Usage: "Override commit SHA of specified git branch/tag",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "test-build",
|
||||
Usage: "Plan a test build, which is published to a draft " +
|
||||
"\"test-builds\" release",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "test-build-name",
|
||||
Usage: "Set a unique name to distinguish the ",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "test-release-type",
|
||||
Value: "prerelease",
|
||||
Usage: "Type of release when doing a test-build " +
|
||||
"(prerelease or draft)",
|
||||
},
|
||||
},
|
||||
Action: actionHandler(planAction),
|
||||
}
|
||||
}
|
||||
|
||||
func planAction(c *cli.Context, opts *globalOptions) error {
|
||||
gh := opts.gh
|
||||
planFile := opts.plan
|
||||
repo := NewRepo(c.String("emacs-mirror-repo"))
|
||||
buildsDir := filepath.Join(c.String("work-dir"), "builds")
|
||||
|
||||
ref := c.Args().Get(0)
|
||||
if ref == "" {
|
||||
ref = "master"
|
||||
}
|
||||
|
||||
lookupRef := ref
|
||||
if s := c.String("sha"); s != "" {
|
||||
lookupRef = s
|
||||
}
|
||||
|
||||
repoCommit, _, err := gh.Repositories.GetCommit(
|
||||
c.Context, repo.Owner, repo.Name, lookupRef,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
commit := NewCommit(repo, ref, repoCommit)
|
||||
osInfo, err := NewOSInfo()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cleanRef := sanitizeString(ref)
|
||||
cleanOS := sanitizeString(osInfo.Name + "-" + osInfo.ShortVersion())
|
||||
cleanArch := sanitizeString(osInfo.Arch)
|
||||
|
||||
release := &Release{
|
||||
Name: fmt.Sprintf(
|
||||
"Emacs.%s.%s.%s",
|
||||
commit.DateString(), commit.ShortSHA(), cleanRef,
|
||||
),
|
||||
}
|
||||
archiveName := fmt.Sprintf(
|
||||
"Emacs.%s.%s.%s.%s.%s",
|
||||
commit.DateString(), commit.ShortSHA(), cleanRef, cleanOS, cleanArch,
|
||||
)
|
||||
|
||||
if c.Bool("test-build") {
|
||||
release.Title = "Test Builds"
|
||||
release.Name = "test-builds"
|
||||
if c.String("test-release-type") == "draft" {
|
||||
release.Draft = true
|
||||
} else {
|
||||
release.Pre = true
|
||||
}
|
||||
|
||||
archiveName += ".test"
|
||||
if t := c.String("test-build-name"); t != "" {
|
||||
archiveName += "." + sanitizeString(t)
|
||||
}
|
||||
}
|
||||
|
||||
plan := &Plan{
|
||||
Commit: commit,
|
||||
OS: osInfo,
|
||||
Release: release,
|
||||
Archive: filepath.Join(buildsDir, archiveName+".tbz"),
|
||||
}
|
||||
|
||||
buf := bytes.Buffer{}
|
||||
enc := yaml.NewEncoder(&buf)
|
||||
enc.SetIndent(2)
|
||||
err = enc.Encode(plan)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return os.WriteFile(planFile, buf.Bytes(), 0666)
|
||||
}
|
||||
|
||||
func sanitizeString(s string) string {
|
||||
return nonAlphaNum.ReplaceAllString(s, "-")
|
||||
}
|
||||
@@ -1,196 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/google/go-github/v35/github"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
func publishCmd() *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "publish",
|
||||
Usage: "publish a release",
|
||||
UsageText: "github-release [global-options] publish [options]",
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "release-sha",
|
||||
Aliases: []string{"s"},
|
||||
Usage: "Git SHA of repo to create release on",
|
||||
EnvVars: []string{"GITHUB_SHA"},
|
||||
},
|
||||
},
|
||||
Action: actionHandler(publishAction),
|
||||
}
|
||||
}
|
||||
|
||||
func publishAction(c *cli.Context, opts *globalOptions) error {
|
||||
gh := opts.gh
|
||||
repo := opts.repo
|
||||
plan, err := LoadPlan(opts.plan)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
releaseSHA := c.String("release-sha")
|
||||
|
||||
assetBaseName := filepath.Base(plan.Archive)
|
||||
assetSumFile := plan.Archive + ".sha256"
|
||||
|
||||
if _, err := os.Stat(assetSumFile); os.IsNotExist(err) {
|
||||
fmt.Printf("==> Generating SHA256 sum for %s\n", assetBaseName)
|
||||
assetSum, err := fileSHA256(plan.Archive)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
content := fmt.Sprintf("%s %s", assetSum, assetBaseName)
|
||||
err = os.WriteFile(assetSumFile, []byte(content), 0666)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf(" -> Done: %s\n", assetSum)
|
||||
}
|
||||
|
||||
tagName := plan.Release.Name
|
||||
name := plan.Release.Title
|
||||
|
||||
if name == "" {
|
||||
name = tagName
|
||||
}
|
||||
|
||||
fmt.Printf("==> Checking release %s\n", tagName)
|
||||
|
||||
release, resp, err := gh.Repositories.GetReleaseByTag(
|
||||
c.Context, repo.Owner, repo.Name, plan.Release.Name,
|
||||
)
|
||||
if err != nil {
|
||||
if resp.StatusCode == http.StatusNotFound {
|
||||
fmt.Println(" -> Release not found, creating...")
|
||||
|
||||
prerelease := false
|
||||
if !plan.Release.Draft && plan.Release.Pre {
|
||||
prerelease = true
|
||||
}
|
||||
|
||||
release, _, err = gh.Repositories.CreateRelease(
|
||||
c.Context, repo.Owner, repo.Name, &github.RepositoryRelease{
|
||||
Name: &name,
|
||||
TagName: &tagName,
|
||||
TargetCommitish: &releaseSHA,
|
||||
Prerelease: &prerelease,
|
||||
Draft: &plan.Release.Draft,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if release.GetName() != name {
|
||||
release.Name = &name
|
||||
|
||||
release, _, err = gh.Repositories.EditRelease(
|
||||
c.Context, repo.Owner, repo.Name, release.GetID(), release,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if !plan.Release.Draft && release.GetPrerelease() != plan.Release.Pre {
|
||||
release.Prerelease = &plan.Release.Pre
|
||||
|
||||
release, _, err = gh.Repositories.EditRelease(
|
||||
c.Context, repo.Owner, repo.Name, release.GetID(), release,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
assetFiles := []string{plan.Archive, assetSumFile}
|
||||
|
||||
for _, fileName := range assetFiles {
|
||||
fileIO, err := os.Open(fileName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer fileIO.Close()
|
||||
|
||||
fileInfo, err := fileIO.Stat()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fileBaseName := filepath.Base(fileName)
|
||||
assetExists := false
|
||||
|
||||
fmt.Printf("==> Checking asset %s\n", fileBaseName)
|
||||
|
||||
for _, a := range release.Assets {
|
||||
if a.GetName() != fileBaseName {
|
||||
continue
|
||||
}
|
||||
|
||||
if a.GetSize() == int(fileInfo.Size()) {
|
||||
fmt.Println(" -> Asset already exists")
|
||||
assetExists = true
|
||||
} else {
|
||||
fmt.Println(
|
||||
" -> Asset exists with wrong file size, deleting...",
|
||||
)
|
||||
_, err := gh.Repositories.DeleteReleaseAsset(
|
||||
c.Context, repo.Owner, repo.Name, a.GetID(),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println(" -> Done")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if !assetExists {
|
||||
fmt.Println(" -> Asset missing, uploading...")
|
||||
_, _, err = gh.Repositories.UploadReleaseAsset(
|
||||
c.Context, repo.Owner, repo.Name, release.GetID(),
|
||||
&github.UploadOptions{Name: fileBaseName},
|
||||
fileIO,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println(" -> Done")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fmt.Printf("==> Release available at: %s\n", release.GetHTMLURL())
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func fileSHA256(filename string) (string, error) {
|
||||
f, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
h := sha256.New()
|
||||
if _, err := io.Copy(h, f); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%x", h.Sum(nil)), nil
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Repo struct {
|
||||
URL string `yaml:"url"`
|
||||
Owner string `yaml:"owner"`
|
||||
Name string `yaml:"name"`
|
||||
}
|
||||
|
||||
func NewRepo(ownerAndRepo string) *Repo {
|
||||
parts := strings.SplitN(ownerAndRepo, "/", 2)
|
||||
|
||||
return &Repo{
|
||||
URL: fmt.Sprintf("https://github.com/%s/%s", parts[0], parts[1]),
|
||||
Owner: parts[0],
|
||||
Name: parts[1],
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Repo) String() string {
|
||||
return s.Owner + "/" + s.Name
|
||||
}
|
||||
Reference in New Issue
Block a user