mirror of
https://github.com/jimeh/emacs-builds.git
synced 2026-02-19 06:06:41 +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:
174
.github/workflows/build.yml
vendored
174
.github/workflows/build.yml
vendored
@@ -13,74 +13,182 @@ on:
|
||||
Description: "Extra plan args"
|
||||
required: false
|
||||
default: ""
|
||||
extraCheckArgs:
|
||||
Description: "Extra check args"
|
||||
required: false
|
||||
default: ""
|
||||
extraBuildArgs:
|
||||
Description: "Extra build args"
|
||||
required: false
|
||||
default: ""
|
||||
extraPackageArgs:
|
||||
Description: "Extra package args"
|
||||
required: false
|
||||
default: ""
|
||||
extraReleaseArgs:
|
||||
Description: "Extra release args"
|
||||
required: false
|
||||
default: ""
|
||||
|
||||
jobs:
|
||||
build-and-publish:
|
||||
plan:
|
||||
runs-on: macos-10.15
|
||||
outputs:
|
||||
check: "${{ steps.check.outcome }}"
|
||||
steps:
|
||||
- name: Checkout emacs-builds repo
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
path: releaser
|
||||
- name: Checkout build-emacs-for-macos repo
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
repository: jimeh/build-emacs-for-macos
|
||||
ref: "0.4.16"
|
||||
ref: "v0.5.0"
|
||||
path: builder
|
||||
- uses: ruby/setup-ruby@v1
|
||||
with:
|
||||
ruby-version: 2.7
|
||||
- uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.16
|
||||
- name: Compile github-release tool
|
||||
run: >-
|
||||
go build -o ./github-release ./cmd/github-release
|
||||
working-directory: releaser
|
||||
- name: Pre-build emacs-builder tool
|
||||
run: make build
|
||||
working-directory: builder
|
||||
- name: Plan build
|
||||
run: >-
|
||||
./releaser/github-release --plan plan.yml plan
|
||||
--work-dir '${{ github.workspace }}'
|
||||
builder/bin/emacs-builder -l debug plan
|
||||
--output build-plan.yml
|
||||
--output-dir '${{ github.workspace }}/builds'
|
||||
${{ github.event.inputs.extraPlanArgs }}
|
||||
${{ github.event.inputs.gitRef }}
|
||||
'${{ github.event.inputs.gitRef }}'
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Show plan
|
||||
run: >-
|
||||
cat plan.yml
|
||||
run: cat build-plan.yml
|
||||
- name: Check if planned release and asset already exist
|
||||
id: check
|
||||
continue-on-error: true
|
||||
run: >-
|
||||
./releaser/github-release --plan plan.yml check
|
||||
builder/bin/emacs-builder -l debug release --plan build-plan.yml check
|
||||
${{ github.event.inputs.extraCheckArgs }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Upload pre-built emacs-builder artifact
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: emacs-builder
|
||||
path: builder/bin/emacs-builder
|
||||
if-no-files-found: error
|
||||
- name: Upload build-plan.yml artifact
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: build-plan.yml
|
||||
path: build-plan.yml
|
||||
if-no-files-found: error
|
||||
|
||||
build:
|
||||
runs-on: macos-10.15
|
||||
needs: [plan]
|
||||
# Only run if check for existing release and asset failed.
|
||||
if: ${{ needs.plan.outputs.check == 'failure' }}
|
||||
steps:
|
||||
- name: Checkout build-emacs-for-macos repo
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
repository: jimeh/build-emacs-for-macos
|
||||
ref: "v0.5.0"
|
||||
path: builder
|
||||
- uses: ruby/setup-ruby@v1
|
||||
with:
|
||||
ruby-version: 2.7
|
||||
- name: Install dependencies
|
||||
if: steps.check.outcome == 'failure'
|
||||
run: >-
|
||||
brew bundle --file=builder/Brewfile
|
||||
run: make bootstrap-ci
|
||||
working-directory: builder
|
||||
- name: Download pre-built emacs-builder artifact
|
||||
uses: actions/download-artifact@v2
|
||||
id: builder
|
||||
with:
|
||||
name: emacs-builder
|
||||
path: bin
|
||||
- name: Ensure emacs-builder is executable
|
||||
run: chmod +x bin/emacs-builder
|
||||
- name: Download build-plan.yml artifact
|
||||
uses: actions/download-artifact@v2
|
||||
id: plan
|
||||
with:
|
||||
name: build-plan.yml
|
||||
path: ./
|
||||
- name: Build Emacs
|
||||
if: steps.check.outcome == 'failure'
|
||||
run: >-
|
||||
./builder/build-emacs-for-macos --plan=plan.yml
|
||||
--work-dir '${{ github.workspace }}'
|
||||
--native-full-aot
|
||||
${{ github.event.inputs.extraReleaseArgs }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Publish release
|
||||
if: steps.check.outcome == 'failure'
|
||||
run: >-
|
||||
./releaser/github-release --plan plan.yml publish
|
||||
./builder/build-emacs-for-macos --plan build-plan.yml
|
||||
--native-full-aot ${{ github.event.inputs.extraBuildArgs }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Install the Apple signing certificate
|
||||
run: |
|
||||
# create variables
|
||||
CERTIFICATE_PATH="$RUNNER_TEMP/build_certificate.p12"
|
||||
KEYCHAIN_PATH="$RUNNER_TEMP/app-signing.keychain-db"
|
||||
|
||||
# import certificate and provisioning profile from secrets
|
||||
echo -n "$CERT_BASE64" | base64 --decode --output "$CERTIFICATE_PATH"
|
||||
|
||||
# create temporary keychain
|
||||
security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
|
||||
security set-keychain-settings -lut 21600 "$KEYCHAIN_PATH"
|
||||
security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
|
||||
|
||||
# import certificate to keychain
|
||||
security import "$CERTIFICATE_PATH" -P "$CERT_PASSWORD" -A \
|
||||
-t cert -f pkcs12 -k "$KEYCHAIN_PATH"
|
||||
security list-keychain -d user -s "$KEYCHAIN_PATH"
|
||||
env:
|
||||
CERT_BASE64: ${{ secrets.APPLE_DEVELOPER_CERTIFICATE_P12_BASE64 }}
|
||||
CERT_PASSWORD: ${{ secrets.APPLE_DEVELOPER_CERTIFICATE_PASSWORD }}
|
||||
KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }}
|
||||
- name: Sign, package and notarize build
|
||||
run: >-
|
||||
bin/emacs-builder -l debug package -v --plan build-plan.yml
|
||||
--sign --remove-source-dir
|
||||
${{ github.event.inputs.extraPackageArgs }}
|
||||
env:
|
||||
AC_USERNAME: ${{ secrets.AC_USERNAME }}
|
||||
AC_PASSWORD: ${{ secrets.AC_PASSWORD }}
|
||||
AC_PROVIDER: ${{ secrets.AC_PROVIDER }}
|
||||
AC_SIGN_IDENTITY: ${{ secrets.AC_SIGN_IDENTITY }}
|
||||
- name: Upload disk image artifacts
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: dmg
|
||||
path: |
|
||||
builds/*.dmg
|
||||
builds/*.sha*
|
||||
if-no-files-found: error
|
||||
- name: Clean up keychain used for signing certificate
|
||||
if: ${{ always() }}
|
||||
run: |
|
||||
security delete-keychain "$RUNNER_TEMP/app-signing.keychain-db"
|
||||
|
||||
release:
|
||||
runs-on: macos-10.15
|
||||
needs: [build]
|
||||
steps:
|
||||
- name: Download pre-built emacs-builder artifact
|
||||
uses: actions/download-artifact@v2
|
||||
id: builder
|
||||
with:
|
||||
name: emacs-builder
|
||||
path: bin
|
||||
- name: Ensure emacs-builder is executable
|
||||
run: chmod +x bin/emacs-builder
|
||||
- name: Download build-plan.yml artifact
|
||||
uses: actions/download-artifact@v2
|
||||
id: plan
|
||||
with:
|
||||
name: build-plan.yml
|
||||
path: ./
|
||||
- name: Download disk image artifact
|
||||
uses: actions/download-artifact@v2
|
||||
with:
|
||||
name: dmg
|
||||
path: builds
|
||||
- name: Publish disk image to GitHub Release
|
||||
run: >-
|
||||
bin/emacs-builder -l debug release --plan build-plan.yml publish
|
||||
${{ github.event.inputs.extraReleaseArgs }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
186
.github/workflows/test-build.yml
vendored
186
.github/workflows/test-build.yml
vendored
@@ -7,18 +7,13 @@ on:
|
||||
description: "Emacs git ref to build"
|
||||
required: true
|
||||
default: "master"
|
||||
releaseToolGitRef:
|
||||
description: "Git ref to checkout of emacs-builds (github-release)"
|
||||
required: true
|
||||
default: "main"
|
||||
buildScriptGitRef:
|
||||
emacsBuilderGitRef:
|
||||
description: "Git ref to checkout of build-emacs-for-macos"
|
||||
required: true
|
||||
default: "master"
|
||||
testBuildName:
|
||||
description: "Test build name"
|
||||
required: false
|
||||
default: ""
|
||||
required: true
|
||||
testReleaseType:
|
||||
description: "prerelease or draft"
|
||||
required: true
|
||||
@@ -27,77 +22,184 @@ on:
|
||||
Description: "Extra plan args"
|
||||
required: false
|
||||
default: ""
|
||||
extraCheckArgs:
|
||||
Description: "Extra check args"
|
||||
required: false
|
||||
default: ""
|
||||
extraBuildArgs:
|
||||
Description: "Extra build args"
|
||||
required: false
|
||||
default: ""
|
||||
extraPackageArgs:
|
||||
Description: "Extra package args"
|
||||
required: false
|
||||
default: ""
|
||||
extraReleaseArgs:
|
||||
Description: "Extra release args"
|
||||
required: false
|
||||
default: ""
|
||||
|
||||
jobs:
|
||||
build-and-publish:
|
||||
plan:
|
||||
runs-on: macos-10.15
|
||||
outputs:
|
||||
check: "${{ steps.check.outcome }}"
|
||||
steps:
|
||||
- name: Checkout emacs-builds repo
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
ref: ${{ github.event.inputs.releaseToolGitRef }}
|
||||
path: releaser
|
||||
- name: Checkout build-emacs-for-macos repo
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
repository: jimeh/build-emacs-for-macos
|
||||
ref: ${{ github.event.inputs.buildScriptGitRef }}
|
||||
ref: ${{ github.event.inputs.emacsBuilderGitRef }}
|
||||
path: builder
|
||||
- uses: ruby/setup-ruby@v1
|
||||
with:
|
||||
ruby-version: 2.7
|
||||
- uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.16
|
||||
- name: Compile github-release tool
|
||||
run: >-
|
||||
go build -o ./github-release ./cmd/github-release
|
||||
working-directory: releaser
|
||||
- name: Pre-build emacs-builder tool
|
||||
run: make build
|
||||
working-directory: builder
|
||||
- name: Plan build
|
||||
run: >-
|
||||
./releaser/github-release --plan plan.yml plan
|
||||
--work-dir '${{ github.workspace }}'
|
||||
--test-build
|
||||
--test-build-name="${{ github.event.inputs.testBuildName }}"
|
||||
--test-release-type="${{ github.event.inputs.testReleaseType }}"
|
||||
builder/bin/emacs-builder -l debug plan
|
||||
--output build-plan.yml
|
||||
--output-dir '${{ github.workspace }}/builds'
|
||||
--test-build '${{ github.event.inputs.testBuildName }}'
|
||||
--test-release-type '${{ github.event.inputs.testReleaseType }}'
|
||||
${{ github.event.inputs.extraPlanArgs }}
|
||||
${{ github.event.inputs.gitRef }}
|
||||
'${{ github.event.inputs.gitRef }}'
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Show plan
|
||||
run: >-
|
||||
cat plan.yml
|
||||
run: cat build-plan.yml
|
||||
- name: Check if planned release and asset already exist
|
||||
id: check
|
||||
continue-on-error: true
|
||||
run: >-
|
||||
./releaser/github-release --plan plan.yml check
|
||||
builder/bin/emacs-builder -l debug release --plan build-plan.yml check
|
||||
${{ github.event.inputs.extraCheckArgs }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Upload pre-built emacs-builder artifact
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: emacs-builder
|
||||
path: builder/bin/emacs-builder
|
||||
if-no-files-found: error
|
||||
- name: Upload build-plan.yml artifact
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: build-plan.yml
|
||||
path: build-plan.yml
|
||||
if-no-files-found: error
|
||||
|
||||
build:
|
||||
runs-on: macos-10.15
|
||||
needs: [plan]
|
||||
# Only run if check for existing release and asset failed.
|
||||
if: ${{ needs.plan.outputs.check == 'failure' }}
|
||||
steps:
|
||||
- name: Checkout build-emacs-for-macos repo
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
repository: jimeh/build-emacs-for-macos
|
||||
ref: ${{ github.event.inputs.emacsBuilderGitRef }}
|
||||
path: builder
|
||||
- uses: ruby/setup-ruby@v1
|
||||
with:
|
||||
ruby-version: 2.7
|
||||
- name: Install dependencies
|
||||
if: steps.check.outcome == 'failure'
|
||||
run: >-
|
||||
brew bundle --file=builder/Brewfile
|
||||
run: make bootstrap-ci
|
||||
working-directory: builder
|
||||
- name: Download pre-built emacs-builder artifact
|
||||
uses: actions/download-artifact@v2
|
||||
id: builder
|
||||
with:
|
||||
name: emacs-builder
|
||||
path: bin
|
||||
- name: Ensure emacs-builder is executable
|
||||
run: chmod +x bin/emacs-builder
|
||||
- name: Download build-plan.yml artifact
|
||||
uses: actions/download-artifact@v2
|
||||
id: plan
|
||||
with:
|
||||
name: build-plan.yml
|
||||
path: ./
|
||||
- name: Build Emacs
|
||||
if: steps.check.outcome == 'failure'
|
||||
run: >-
|
||||
./builder/build-emacs-for-macos --plan=plan.yml
|
||||
--work-dir '${{ github.workspace }}'
|
||||
--native-full-aot
|
||||
${{ github.event.inputs.extraReleaseArgs }}
|
||||
- name: Publish release
|
||||
if: steps.check.outcome == 'failure'
|
||||
./builder/build-emacs-for-macos --plan build-plan.yml
|
||||
--native-full-aot ${{ github.event.inputs.extraBuildArgs }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Install the Apple signing certificate
|
||||
run: |
|
||||
# create variables
|
||||
CERTIFICATE_PATH="$RUNNER_TEMP/build_certificate.p12"
|
||||
KEYCHAIN_PATH="$RUNNER_TEMP/app-signing.keychain-db"
|
||||
|
||||
# import certificate and provisioning profile from secrets
|
||||
echo -n "$CERT_BASE64" | base64 --decode --output "$CERTIFICATE_PATH"
|
||||
|
||||
# create temporary keychain
|
||||
security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
|
||||
security set-keychain-settings -lut 21600 "$KEYCHAIN_PATH"
|
||||
security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
|
||||
|
||||
# import certificate to keychain
|
||||
security import "$CERTIFICATE_PATH" -P "$CERT_PASSWORD" -A \
|
||||
-t cert -f pkcs12 -k "$KEYCHAIN_PATH"
|
||||
security list-keychain -d user -s "$KEYCHAIN_PATH"
|
||||
env:
|
||||
CERT_BASE64: ${{ secrets.APPLE_DEVELOPER_CERTIFICATE_P12_BASE64 }}
|
||||
CERT_PASSWORD: ${{ secrets.APPLE_DEVELOPER_CERTIFICATE_PASSWORD }}
|
||||
KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }}
|
||||
- name: Sign, package and notarize build
|
||||
run: >-
|
||||
./releaser/github-release --plan plan.yml publish
|
||||
--release-sha="${{ github.event.inputs.releaseToolGitRef }}"
|
||||
bin/emacs-builder -l debug package -v --plan build-plan.yml
|
||||
--sign --remove-source-dir
|
||||
${{ github.event.inputs.extraPackageArgs }}
|
||||
env:
|
||||
AC_USERNAME: ${{ secrets.AC_USERNAME }}
|
||||
AC_PASSWORD: ${{ secrets.AC_PASSWORD }}
|
||||
AC_PROVIDER: ${{ secrets.AC_PROVIDER }}
|
||||
AC_SIGN_IDENTITY: ${{ secrets.AC_SIGN_IDENTITY }}
|
||||
- name: Upload disk image artifacts
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: dmg
|
||||
path: |
|
||||
builds/*.dmg
|
||||
builds/*.sha*
|
||||
if-no-files-found: error
|
||||
- name: Clean up keychain used for signing certificate
|
||||
if: ${{ always() }}
|
||||
run: |
|
||||
security delete-keychain "$RUNNER_TEMP/app-signing.keychain-db"
|
||||
|
||||
release:
|
||||
runs-on: macos-10.15
|
||||
needs: [build]
|
||||
steps:
|
||||
- name: Download pre-built emacs-builder artifact
|
||||
uses: actions/download-artifact@v2
|
||||
id: builder
|
||||
with:
|
||||
name: emacs-builder
|
||||
path: bin
|
||||
- name: Ensure emacs-builder is executable
|
||||
run: chmod +x bin/emacs-builder
|
||||
- name: Download build-plan.yml artifact
|
||||
uses: actions/download-artifact@v2
|
||||
id: plan
|
||||
with:
|
||||
name: build-plan.yml
|
||||
path: ./
|
||||
- name: Download disk image artifact
|
||||
uses: actions/download-artifact@v2
|
||||
with:
|
||||
name: dmg
|
||||
path: builds
|
||||
- name: Publish disk image to GitHub Release
|
||||
run: >-
|
||||
bin/emacs-builder -l debug release --plan build-plan.yml publish
|
||||
${{ github.event.inputs.extraReleaseArgs }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
@@ -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