Compare commits

..

109 Commits

Author SHA1 Message Date
bc3923c9ca chore(release): 0.6.12 2021-08-03 00:19:21 +01:00
e6b1e5a554 fix(sign): resolve signing issue caused by re-linking shared lib in *.eln files
When shared libs are stored in `Contents/Frameworks`, the re-link path
for `/usr/local/lib/gcc/11/libgcc_s.1.dylib` within bundled *.eln files
becomes `@executable_path/../Frameworks/libgcc_s.1.dylib`, which seems to
not leave enough space in the *.eln binary header to add a code
signature with codesign.

This used to work when shared libraries were bundled into
`Contents/MacOS/lib`, leading to a shorter relink path of
`@executable_path/lib/libgcc_s.1.dylib`, which does leave enough space
to add a code signature to *.eln files.
2021-08-03 00:13:10 +01:00
94625fce38 chore(release): 0.6.11 2021-07-17 13:08:10 +01:00
b03343f506 fix(native-comp): fix re-linking and signing issue with *.eln files
With the recent move of shared libraries and native lisp *.eln files to
Contents/Frameworks, the re-linking paths became longer, causing code
signing to fail with headerpad errors. This change ensures there's
enough space within binary files for longer shared library relink paths,
and code signing payloads.
2021-07-17 13:04:33 +01:00
b1896d4a4f chore(release): 0.6.10 2021-07-17 00:20:42 +01:00
9d32509c61 fix(native-comp): *.eln files were not being found during shared lib embedding 2021-07-16 21:00:24 +01:00
1c2745cd36 docs(readme): mention binary builds repo 2021-07-10 21:19:11 +01:00
80a0d55b24 chore(release): 0.6.9 2021-07-04 23:28:28 +01:00
fd0ec4d772 fix(plan): correctly parse --test-release-type flag
The --test-release-type flag was essentially broken and ignored, always
creating a prerelease release for test builds. Now it can also produce
draft releases.
2021-07-04 23:27:00 +01:00
c0c809a86a style(lint): fix linting complaint 2021-07-03 02:39:01 +01:00
cb63806262 feat(release): add bulk edit command to quickly change multiple GitHub releases 2021-07-03 02:36:15 +01:00
6d7ab95ca2 chore(release): 0.6.8 2021-07-03 00:19:26 +01:00
f4d6e3a56d feat(builds): add support for stable builds
Stable builds are based off of release git tags in Emacs' git
repo. Examples of what release tags look like:

- emacs-26.1
- emacs-26.2
- emacs-26.3
- emacs-27.1
- emacs-27.2

When the specified git ref looks like a stable release, the plan command
will generate a release a different and simpler release name that does
not include the date, git sha or ref. Instead, for "emacs-27.2" for
example, the emacs-builds release name will be "Emacs-27.2".

The "build name", used for naming the disk image, still retains the same
format as the nightly builds.

Also, non-stable releases are now marked as pre-release on GitHub by
default.

The reason for the different release name format for stable builds is
both to separate them, but also to make it easier to keep the version of
the homebrew cask as simply "27.2".
2021-07-03 00:11:59 +01:00
3f1059940d chore(release): 0.6.7 2021-07-02 16:50:25 +01:00
5c722e36c5 feat(bundle): move bundled shared libraries to Contents/Frameworks
The Frameworks folder is the recommended location to store shared
libraries within macOS application bundles. Previously we stored them in
Contents/MacOS/lib.

Latest nightly builds already store all *.eln files under the Frameworks
folder, so it seemed like a good time to make the change with the
library bundler/embedder too.
2021-07-02 16:49:32 +01:00
f52dd8dc6d chore(release): 0.6.6 2021-07-01 23:52:50 +01:00
4cdbaf1ec0 chore(cask): fix cask vs formula terminology
Homebrew casks are their own thing, separate from formulas. Hence remove
all mentions to "formula" to avoid confusion.
2021-07-01 23:50:50 +01:00
f3a289b11c chore(release): 0.6.5 2021-07-01 23:37:21 +01:00
9019e73d60 fix(native_comp): improve handling of *.eln files in .app bundle
Specifically support latest changes in master which places *.eln files
within the .app bundle in "Contents/Frameworks".
2021-07-01 23:33:50 +01:00
28930381a8 chore(release): 0.6.4 2021-06-30 10:41:34 +01:00
df25e54ef7 chore(sign): simplify *.eln locating logic
Instead of only checking very specific paths within the .app bundle,
just check the whole bundle for any and all *.eln files.
2021-06-30 10:20:22 +01:00
6d21d1bef4 chore(release): 0.6.3 2021-06-29 14:40:18 +01:00
99aa76b398 fix(patches): correctly set ref when loading a build plan YAML
The ref was not correctly set when loading a build plan, resulting in
the set of patches being selected were always for Emacs 28.x, preventing
builds of Emacs 27.x and 26.x.
2021-06-29 14:37:43 +01:00
b60ca528f8 chore(release): 0.6.2 2021-06-29 01:30:19 +01:00
23b8236e0a fix(native_comp): patch Emacs.pdmp for customized native-lisp paths
In my initial testing without full native-comp AoT, Emacs seemed to
somehow launch fine. But with a AoT build it complains that it can't
find *.eln files in the original paths that contained dots. But since we
have to customize those folder names removing the dots to make Apple's
codesign happy, we also need to update Emacs.pdmp too.
2021-06-29 01:27:16 +01:00
56d0364099 chore(release): 0.6.1 2021-06-28 22:50:20 +01:00
6af597b427 fix(cask): add missing --force flag to cask update command 2021-06-28 22:49:07 +01:00
a331457e89 chore(release): 0.6.0 2021-06-28 22:23:44 +01:00
a4171555f5 Merge pull request #52 from jimeh/cask-formula-management
feat(cask): add cask update command to manage cask formula
2021-06-28 22:20:39 +01:00
adbcfc6fc4 feat(cask): add cask update command to manage cask formula
This will be used by the jimeh/homebrew-emacs-builds brew tap repository
in combination with brew livecheck to automatically update cask formulas
to the latest nightly builds from the jimeh/emacs-builds repository.
2021-06-28 22:19:10 +01:00
634861beea chore(release): 0.5.2 2021-06-27 12:33:47 +01:00
eeca7b798d fix(native_comp): rename native-lisp folder paths to appease Apple's codesign
Apple's codesign CLI tool will throw an error when signing application
bundles, if any folder within the app's Contents/MacOS folder contains
two dots.

The recent relocation of the native-lisp folder from
Contents/Resources/native-lisp to
Contents/MacOS/lib/emacs/28.0.50/native-lisp is causing code signing to
fail.

The workaround here simply replaces dots (.) with hyphens (-), causing
the following folder renames:

    Contents/MacOS/lib/emacs/28.0.50/native-lisp/28.0.50-852ecda2 --> Contents/MacOS/lib/emacs/28.0.50/native-lisp/28-0-50-852ecda2
    Contents/MacOS/lib/emacs/28.0.50 --> Contents/MacOS/lib/emacs/28-0-50

To ensure Emacs can still find the bundled native-lisp files, we use a
symlink:

    Contents/native-lisp -> MacOS/lib/emacs/28-0-50/native-lisp

This type of fix is not ideal, but its the only way I know of getting
around this issue right now.

And we're already doing a similar thing for the embedded gcc libraries.
2021-06-27 12:25:35 +01:00
e1500cbf53 chore(release): 0.5.1 2021-06-27 02:32:38 +01:00
ca73ab7202 fix(native_comp): symlink creation was missing a conditional check
Latest commits to master as of June 26, 2021 have moved *.eln files back
to: MacOS/lib/emacs/**/native-lisp

Even though the symlinks are most likely no longer needed, I don't know
of an easy way to determine if they're required or not, so for now,
they'll be created to ensure older native-comp branch SHAs can still be
built.
2021-06-27 02:27:24 +01:00
fd6cd42d5c chore(release): 0.5.0 2021-06-22 00:23:43 +01:00
b719437bee chore(release)!: add v prefix to git version tags
BREAKING CHANGE: Add v prefix to git version tags
2021-06-22 00:21:34 +01:00
368b9271ff Merge pull request #44 from jimeh/sign-package-and-notarize
Sign, Package and Notarize
2021-06-22 00:16:50 +01:00
87d2d67575 chore(makefile): add bootstrap targets for normal use and CI environments 2021-06-22 00:13:25 +01:00
72ca9ce2b6 feat(release): add release publish command 2021-06-22 00:13:25 +01:00
276a9da5ee feat(release): add release check command 2021-06-22 00:13:24 +01:00
87ecfbcec0 feat(package): add package command to create a styled *.dmg for Emacs.app 2021-06-22 00:13:21 +01:00
55f35e1146 chore(package): add dmgbuild helper package to execute dmgbuild 2021-06-22 00:08:42 +01:00
72d0254772 feat(notarize): add notarize command to notarize and staple *.dmg files 2021-06-22 00:08:42 +01:00
698756ac55 feat(sign): add sign command to sign Emacs.app bundles with codesign
The sign command signs Emacs.app application bundles with Apple's
codesign utility.

It does a few things outside of just executing codesign:

- Is aware of *.eln native-compilation files, which need to be
  explicitly searched for on disk and passed to codesign, as they are
  not detected when using the "--deep" option.
- Is aware of Contents/MacOS/bin/emacs CLI helper tool which we add into
  the application bundle, and specifically passed it to codesign as
  well.
- By default provides a set of entitlements which are relevant for Emacs
  when running codesign.
2021-06-22 00:08:36 +01:00
1ffd735c23 feat(plan): add plan command to create build plans 2021-06-20 18:38:36 +01:00
8d87c01db7 feat(cli): add basis for new "emacs-builder" CLI tool written in Go
This will eventually replace the build-emacs-for-macos script that's
written in Ruby. The migration of functionality will happen bit by bit
over time.
2021-06-20 18:38:36 +01:00
3e1eb01e24 feat(build)!: add ability to output as directory and/or archive
This changes the filename pattern of the resulting archive, for example:

Old:

    Emacs.app-[master][2021-05-19][6ae3f7e][macOS-11.3][x86_64].tbz
New:

    Emacs.2021-05-19.6ae3f7e.master.macOS-11-3.x86_64.tbz

Emacs.app also resides within a folder in the archive now instead of the
in the root. For the above example, the path to Emacs.app within the
archive would be:

    Emacs.2021-05-19.6ae3f7e.master.macOS-11-3.x86_64/Emacs.app

Archive creation can also be skipped by passing in --no-archive, which
will instead leave a build folder with the same name as the archive.

If you want to keep the build directory, and also a create archive, pass
in --archive-keep-build-dir.

BREAKING CHANGE: New archive naming convention, and folder structure
within archive.
2021-06-20 18:38:36 +01:00
d7c59ba4b2 docs(readme): update CLI usage section 2021-06-20 18:38:10 +01:00
8f5ae8a6c2 chore(release): 0.4.17 2021-06-20 18:26:37 +01:00
707bc9e0d1 fix(download): don't use GitHub API to get tarball URL
The API tarball endpoint is subject to API rate limits, while the
non-API tarball endpoint is not. This should reduce the risk of rate
limit errors for people who don't have a GITHUB_TOKEN environment
variable set.
2021-06-20 17:52:17 +01:00
c55bf68e61 chore(release): 0.4.16 2021-06-07 23:42:18 +01:00
26ce446226 Merge pull request #49 from jimeh/fix-portability
fix: improve portability of builds
2021-06-07 23:31:30 +01:00
ca2d4c38f6 fix(compiling): improve portability of builds
This makes the -march=native CFLAG optional, and disabled by default,
but still available through a new --native-march flag.

It should make builds more portable between machines, as previously it
was very common to get a CPU architecture error on launch if you moved
the build to a different machine running a different generation of a
Intel CPU.

From what I've understood, when using the -march=native CFLAG clang will
make as many optimizations possible based on the exact set of CPU
instructions available on the specific CPU it's compiling on.

In theory this leads to a more optimized build, though I haven't
personally noticed any difference. But it also leads to less portable
builds, for example builds from a Intel-based 2020 MacBook Pro just
crash with a unsupported CPU architecture error when run on a
Intel-based 2016 MacBook Pro.
2021-06-07 23:25:42 +01:00
bd81870659 fix(native_comp): crash on launch when gcc homebrew package was not installed
It turns out all *.eln files link against the libgcc dylib in homebrew:

    /usr/local/lib/gcc/11/libgcc_s.1.dylib

So here we find all *.eln files in all relevant paths they may be
depending on how old of a native-comp source tree we're building, and
copy in any shared libs that resides within the homebrew prefix. Just
like we do for all other binaries that we copy libs for.
2021-06-07 23:21:29 +01:00
5cd9d1c5fa chore(release): 0.4.15 2021-05-31 23:25:03 +01:00
6ae14b597c Merge pull request #48 from jimeh/github-auth
feat(github): perform authenticated GitHub API requests when GITHUB_TOKEN env var is set
2021-05-31 23:23:24 +01:00
deda28e5ad feat(github): perform authenticated GitHub API requests when GITHUB_TOKEN env var is set
This should let people on shared connections use the script if they
have a GitHub Personal Access Token available in the GITHUB_TOKEN
environment variable.

When making unauthenticated API requests to GitHub, requests are rate
limited to 60 requests per hour based on source IP address. Hence on
shared connections the rate limit may easily be exceeded.

When making authenticated API requests to GitHub, up to 5000 requests
per hour is allowed, based on the authenticated user rather than source
IP address.
2021-05-31 23:14:54 +01:00
3d6caac962 chore(release): 0.4.14 2021-05-22 21:09:33 +01:00
11de27358d Merge pull request #43 from jimeh/improve-cli-integration
feat(cli): add "emacs" CLI launcher script to Emacs.app/Conents/MacOS/bin
2021-05-22 20:56:41 +01:00
8237aa9272 feat(cli): add "emacs" CLI launcher script to Emacs.app/Conents/MacOS/bin
This makes setting up a "emacs" terminal command that works with the
self-contained Emacs.app bundle much simpler, as you just need to add
Emacs.app/Conents/MacOS/bin to your PATH.

For example, if you place Emacs.app in /Applications, add this to your
shell setup:

    if [ -d "/Applications/Emacs.app/Contents/MacOS/bin" ]; then
      export PATH="/Applications/Emacs.app/Contents/MacOS/bin:$PATH"
      alias emacs="emacs -nw" # Always launch "emacs" in terminal mode.
    fi

The launcher script works by figuring out it's own absolute path on
disk, even if you are using a symlink to the script, it will resolve to
correct real path. This allows it to execute the main
Emacs.app/Contents/MacOS/Emacs executable via the correct path, so it
can correctly pick up its dependencies from within the Emacs.app bundle.

Fixes #41
2021-05-22 20:55:42 +01:00
3ffe83c94e docs(native_comp): update readme to reflect recent changes to native-comp 2021-05-22 20:32:51 +01:00
9f6ec5954f chore(release): 0.4.13 2021-05-22 18:52:45 +01:00
9ea79670d8 Merge pull request #42 from jimeh/prepare-for-codesign
feat(codesign): minor internal fix to output Emacs.app bundle for compatibility with codesign
2021-05-22 18:51:09 +01:00
72e40248e1 chore(cli): remove deprecated flags and update readme usage 2021-05-22 18:33:16 +01:00
7259111478 fix(codesign): prevent "bundle format unrecognized" error from codesign
Apple's codesign CLI toolthrows a "bundle format unrecognized" error if
there are any folders within the application that contain two dots in
their name.

Hence we need to get rid of the one instance of that we end up with from
GCC, and update the native-comp patch accordingly.

As of writing, this means renaming:
Emacs.app/Contents/MacOS/lib/gcc/11/gcc/x86_64-apple-darwin20/11.1.0

To:
Emacs.app/Contents/MacOS/lib/gcc/11/gcc/x86_64-apple-darwin20/11
2021-05-20 01:55:41 +01:00
bb45cda023 fix(embed): avoid potential error caused by trying to set duplicate rpath 2021-05-20 01:55:41 +01:00
ab872202df chore: minor improvement 2021-05-20 01:55:41 +01:00
2d1c5d47d9 chore(release): 0.4.12 2021-05-17 23:14:50 +01:00
30a71c423e Merge pull request #40 from jimeh/fix-svg
fix(svg): fix SVG rendering crash and enable SVG support by default
2021-05-17 23:12:44 +01:00
5c48445397 chore(cli): improve CLI help output 2021-05-17 23:12:12 +01:00
bf7c4d5deb fix(svg): enable SVG by default via librsvg 2021-05-17 23:12:12 +01:00
3ffeb4854c chore(shared-libraries): add @executable_path/lib to @rpath for good measure 2021-05-17 23:12:12 +01:00
0a22d8393c fix(shared-libraries): stop aggressive dylib re-linking
This seems to be the cause of SVG rendering crashing, as it re-links
libiconv.2.dylib from /usr/lib/libiconv.2.dylib to
@executable_path/lib/lib/libiconv.2.dylib within libintl.8.dylib. When
this re-linking does not happen, SVG rendering works without crashing
Emacs.

Some further testing is needed by installing brew dependencies by
building them from source, in an attempt to get various libraries all
linking to homebrew-built versions, to get as many shared libraries as
possible embedded into the application bundle.

Fixes #12
2021-05-17 23:11:51 +01:00
ac8348323d chore(release): 0.4.11 2021-05-08 19:56:42 +01:00
59f52b65ee Merge pull request #39 from jimeh/emacs-builds
feat(builds): prepare for automated builds in jimeh/emacs-builds repo
2021-05-08 19:51:26 +01:00
81a96f4d60 chore(builds): remove github-release tool
This now lives in the jimeh/emacs-builds repo, which focuses on building
and publishing binary releases, using the build-emacs-for-macos script.
2021-05-08 19:10:46 +01:00
1df39fafe6 feat(builds): update build script for new plan.yml format 2021-05-08 19:07:13 +01:00
14a8d1aaaf chore(release): enable full native-compilation AoT 2021-05-07 09:15:14 +01:00
272a3000a1 fix(release): attempt to fix issue with talking to GitHub API 2021-05-07 01:12:57 +01:00
d684cf560f ci(build): fix typo 2021-05-06 23:39:16 +01:00
ea189a6713 Merge pull request #38 from jimeh/automatic-builds
feat(release): automatic builds
2021-05-06 23:37:20 +01:00
63289216d7 feat(release): initial attempt at providing automatic builds 2021-05-06 23:36:12 +01:00
2054c8c0aa chore(release): 0.4.10 2021-04-26 00:07:30 +01:00
ca09d1a95f docs(readme): update readme about native-comp being merged to master 2021-04-26 00:07:07 +01:00
f1e60e31d9 chore(makefile): add next-version target to preview new-version 2021-04-25 23:09:18 +01:00
8d197aea73 chore: ensure file mode is restored in case of error 2021-04-25 23:07:54 +01:00
844df73c8f fix(cli): correctly default to master branch if no git ref is given
Previously it would build master, but not include "master" in the
final output archive's file name.
2021-04-25 23:04:03 +01:00
f1fc68c8f5 chore(release): 0.4.9 2021-04-08 12:03:41 +01:00
e19a6a7bc2 fix(cli): default to "master" if no git ref is given
Fixes #35
2021-04-08 12:02:45 +01:00
1000999eb2 fix(native_comp): skip symlink creation for recent builds which do not need symlinks
Recent builds places the native-lisp cache folder within
Contents/Resources on macOS, and correctly deals with finding them. This
means the Contents/lisp and Contents/native-lisp symlinks are no longer
needed.

Hence we skip their creation altogether if we find any
Contents/Resources/native-lisp/**/*.eln files.
2021-04-08 11:54:01 +01:00
a75047fb3a chore(release): 0.4.8 2021-02-27 23:09:58 +00:00
a1641946e4 chore(native_comp): don't set full AOT env vars if not using native-comp 2021-02-27 23:08:15 +00:00
581594da3c fix(native_comp): add support for new --with-native-compilation flag 2021-02-27 23:07:22 +00:00
bdad382e7f chore(release): 0.4.7 2021-02-21 15:24:31 +00:00
e25ceaa7e2 fix(native_comp): add libgccjit include dir during build stage
Also used existing `CFLAGS` and `LDFLAGS` environment variable values,
so a user can easily set these when running the build script to add more
paths. Previously they were set to explicit values ignoring any existing
value.

This might help resolve issues where libgccjit.h is not found as
reported in issue #20. Though I have not been able to reproduce this
myself, it seems adding the libgccjit's include dir has solved the issue
for some.
2021-02-21 15:22:07 +00:00
03ae8750b8 chore(release): 0.4.6 2021-02-15 11:43:58 +00:00
269dbdb1dd chore(build): use File.join wherever relevant
Ensures paths are correctly joined and no double and/or missing slash
errors can occur.
2021-02-15 11:42:49 +00:00
713c970da4 Merge pull request #30 from jimeh/improve-native-comp-env-setup-patch
fix(native_comp): improve env setup patch fixing potential issues
2021-02-15 11:41:52 +00:00
dca023daec fix(native_comp): improve env setup patch fixing potential issues
The old patch would dynamically glob within
Emacs.app/Contents/MacOS/lib/gcc using the full absolute path to
Emacs.app. If there are obsure characters like "[]" and others in the
absolute path, it can cause glob search within the
native-compile-setup-environment-variables function to fail, in turn
preventing native-comp from working.

The fix is to hard-code the relative paths from Emacs'
invocation-directory (**/Emacs.app/Contents/MacOS) into the environment
setup function itself, making it very simple and effectively just
joining a few strings and setting an environment variable.

It did require a little bit of cleanup and better organization of the
GCC/libgccjit releated code in the build script, creating a new GccInfo
class which is the central place for determining various paths and
information about GCC and libgccjit which the build will be using.
2021-02-15 09:49:47 +00:00
e56c26d06f docs(readme): update status section 2021-01-15 02:01:13 +00:00
b2860f22c3 chore(deps): add rubocop-daemon and solargraph to Gemfile 2021-01-15 02:00:46 +00:00
d1c5e7afb1 chore(release): 0.4.5 2021-01-06 20:32:30 +00:00
ab55f5421c fix(cli): remove defunct --[no-]native-comp-macos-fixes option
The underlying patching code was removed in v0.4.1 (commit
70bf6b05d5), as it was no longer needed,
but the related CLI flag and README info was mistakenly left in place.
2021-01-06 20:31:31 +00:00
eb09d5fa49 chore(release): 0.4.4 2021-01-02 14:01:37 +00:00
a47d3e0c6a fix(deps): add autoconf to Brewfile
On a fresh install of Big Sur with only the Xcode CLI tools installed,
autoconf is not available. Hence it needs to be installed from homebrew
instead.
2021-01-02 14:00:49 +00:00
5a61a72a73 chore(release): 0.4.3 2020-12-28 11:47:11 +00:00
9cdf67e71b Merge pull request #26 from jimeh/fix-big-sur
fix(big-sur): resolve issues with building and native-comp on Big Sur
2020-12-28 11:42:45 +00:00
946856e9c2 fix(big-sur): add Xcode CLI tools lib directory to runtime LIBRARY_PATH 2020-12-25 15:59:53 +00:00
claford-v-lawrence
2247158051 fix(big-sur): added support for building on Big Sur
Curtsy of https://apple.stackexchange.com/questions/408999/gfortran-compiler-error-on-mac-os-big-sur
2020-12-25 15:59:47 +00:00
60 changed files with 6412 additions and 284 deletions

52
.github/workflows/ci.yml vendored Normal file
View File

@@ -0,0 +1,52 @@
---
name: CI
on: [push]
jobs:
lint:
name: Lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: golangci-lint
uses: golangci/golangci-lint-action@v2
with:
version: v1.40
env:
VERBOSE: "true"
tidy:
name: Tidy
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-go@v2
with:
go-version: 1.16
- uses: actions/cache@v2
with:
path: ~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-
- name: Check if mods are tidy
run: make check-tidy
test:
name: Test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-go@v2
with:
go-version: 1.16
- uses: actions/cache@v2
with:
path: ~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-
- name: Run tests
run: make test
env:
VERBOSE: "true"

2
.gitignore vendored
View File

@@ -1,6 +1,8 @@
.DS_Store
.envrc
Formula/*
Gemfile.lock
bin
builds
sources
tarballs

86
.golangci.yml Normal file
View File

@@ -0,0 +1,86 @@
linters-settings:
funlen:
lines: 100
statements: 150
goconst:
min-occurrences: 5
gocyclo:
min-complexity: 20
govet:
check-shadowing: true
enable-all: true
disable:
- fieldalignment
lll:
line-length: 80
tab-width: 4
maligned:
suggest-new: true
misspell:
locale: US
linters:
disable-all: true
enable:
- bodyclose
- deadcode
- depguard
- dupl
- errcheck
- exportloopref
- funlen
- gochecknoinits
- goconst
- gocritic
- gocyclo
- gofumpt
- goimports
- goprintffuncname
- goprintffuncname
- gosec
- gosimple
- govet
- ineffassign
- lll
- misspell
- nakedret
- nlreturn
- noctx
- nolintlint
- revive
- sqlclosecheck
- staticcheck
- structcheck
- typecheck
- unconvert
- unused
- varcheck
- whitespace
issues:
include:
# - EXC0002 # disable excluding of issues about comments from golint
exclude:
- Using the variable on range scope `tt` in function literal
- Using the variable on range scope `tc` in function literal
exclude-rules:
- path: "_test\\.go"
linters:
- funlen
- dupl
- goconst
- source: "^//go:generate "
linters:
- lll
- source: "`json:"
linters:
- lll
run:
skip-dirs:
- builds
- sources
- tarballs
timeout: 2m
allow-parallel-runners: true
modules-download-mode: readonly

View File

@@ -1,3 +1 @@
module.exports = {
tagPrefix: "",
};
module.exports = {};

View File

@@ -1,5 +1,6 @@
# frozen_string_literal: true
brew 'autoconf'
brew 'coreutils'
brew 'curl'
brew 'expat'

3
Brewfile.ci Normal file
View File

@@ -0,0 +1,3 @@
# frozen_string_literal: true
brew 'python'

View File

@@ -2,6 +2,244 @@
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
### [0.6.12](https://github.com/jimeh/build-emacs-for-macos/compare/v0.6.11...v0.6.12) (2021-08-02)
### Bug Fixes
* **sign:** resolve signing issue caused by re-linking shared lib in *.eln files ([e6b1e5a](https://github.com/jimeh/build-emacs-for-macos/commit/e6b1e5a554fd0f776bd01c17cfb1ebbbdf7a7831))
### [0.6.11](https://github.com/jimeh/build-emacs-for-macos/compare/v0.6.10...v0.6.11) (2021-07-17)
### Bug Fixes
* **native-comp:** fix re-linking and signing issue with *.eln files ([b03343f](https://github.com/jimeh/build-emacs-for-macos/commit/b03343f506aa3ceabdfa03f8a2916b2db4873f3f))
### [0.6.10](https://github.com/jimeh/build-emacs-for-macos/compare/v0.6.9...v0.6.10) (2021-07-16)
### Bug Fixes
* **native-comp:** *.eln files were not being found during shared lib embedding ([9d32509](https://github.com/jimeh/build-emacs-for-macos/commit/9d32509c615076618957cc47c82f6e9d8f972fe7))
### [0.6.9](https://github.com/jimeh/build-emacs-for-macos/compare/v0.6.8...v0.6.9) (2021-07-04)
### Features
* **release:** add bulk edit command to quickly change multiple GitHub releases ([cb63806](https://github.com/jimeh/build-emacs-for-macos/commit/cb638062625d9bc3eee12515067fb09e05a08414))
### Bug Fixes
* **plan:** correctly parse --test-release-type flag ([fd0ec4d](https://github.com/jimeh/build-emacs-for-macos/commit/fd0ec4d772dd3da93afc234fb3024220b2099c88))
### [0.6.8](https://github.com/jimeh/build-emacs-for-macos/compare/v0.6.7...v0.6.8) (2021-07-02)
### Features
* **builds:** add support for stable builds ([f4d6e3a](https://github.com/jimeh/build-emacs-for-macos/commit/f4d6e3a56d2c15b0c86af18e8d16bebbeb92a8ab))
### [0.6.7](https://github.com/jimeh/build-emacs-for-macos/compare/v0.6.6...v0.6.7) (2021-07-02)
### Features
* **bundle:** move bundled shared libraries to Contents/Frameworks ([5c722e3](https://github.com/jimeh/build-emacs-for-macos/commit/5c722e36c571aa7bf558b7f210c011f12d8d8a1c))
### [0.6.6](https://github.com/jimeh/build-emacs-for-macos/compare/v0.6.5...v0.6.6) (2021-07-01)
### [0.6.5](https://github.com/jimeh/build-emacs-for-macos/compare/v0.6.4...v0.6.5) (2021-07-01)
### Bug Fixes
* **native_comp:** improve handling of *.eln files in .app bundle ([9019e73](https://github.com/jimeh/build-emacs-for-macos/commit/9019e73d606f0379f988f46d6008770f8f3f7a51))
### [0.6.4](https://github.com/jimeh/build-emacs-for-macos/compare/v0.6.3...v0.6.4) (2021-06-30)
### [0.6.3](https://github.com/jimeh/build-emacs-for-macos/compare/v0.6.2...v0.6.3) (2021-06-29)
### Bug Fixes
* **patches:** correctly set ref when loading a build plan YAML ([99aa76b](https://github.com/jimeh/build-emacs-for-macos/commit/99aa76b3985195c310a20bafa19a8c7a4c8558fd))
### [0.6.2](https://github.com/jimeh/build-emacs-for-macos/compare/v0.6.1...v0.6.2) (2021-06-29)
### Bug Fixes
* **native_comp:** patch Emacs.pdmp for customized native-lisp paths ([23b8236](https://github.com/jimeh/build-emacs-for-macos/commit/23b8236e0a66fb09810e8422bedf02f7192a53e4))
### [0.6.1](https://github.com/jimeh/build-emacs-for-macos/compare/v0.6.0...v0.6.1) (2021-06-28)
### Bug Fixes
* **cask:** add missing --force flag to cask update command ([6af597b](https://github.com/jimeh/build-emacs-for-macos/commit/6af597b4271341f9796c3d9c356de9918e0f6f85))
## [0.6.0](https://github.com/jimeh/build-emacs-for-macos/compare/v0.5.2...v0.6.0) (2021-06-28)
### Features
* **cask:** add cask update command to manage cask formula ([adbcfc6](https://github.com/jimeh/build-emacs-for-macos/commit/adbcfc6fc433fcc99b10dc5ccb51ba458333fa9c))
### [0.5.2](https://github.com/jimeh/build-emacs-for-macos/compare/v0.5.1...v0.5.2) (2021-06-27)
### Bug Fixes
* **native_comp:** rename native-lisp folder paths to appease Apple's codesign ([eeca7b7](https://github.com/jimeh/build-emacs-for-macos/commit/eeca7b798de236a3ffc1ab04b0f7735a37ce5af4))
### [0.5.1](https://github.com/jimeh/build-emacs-for-macos/compare/v0.5.0...v0.5.1) (2021-06-27)
### Bug Fixes
* **native_comp:** symlink creation was missing a conditional check ([ca73ab7](https://github.com/jimeh/build-emacs-for-macos/commit/ca73ab7202877acefd97289f3d28e7c025e36b9d))
## [0.5.0](https://github.com/jimeh/build-emacs-for-macos/compare/v0.4.17...v0.5.0) (2021-06-21)
### ⚠ BREAKING CHANGES
* **release:** Add v prefix to git version tags
* **build:** New archive naming convention, and folder structure within archive.
### Features
* **build:** add ability to output as directory and/or archive ([3e1eb01](https://github.com/jimeh/build-emacs-for-macos/commit/3e1eb01e248ebbc314b8b9f50bbc371ac8df666b))
* **cli:** add basis for new "emacs-builder" CLI tool written in Go ([8d87c01](https://github.com/jimeh/build-emacs-for-macos/commit/8d87c01db79201182fbcd1a210b1b19df9209aeb))
* **notarize:** add notarize command to notarize and staple *.dmg files ([72d0254](https://github.com/jimeh/build-emacs-for-macos/commit/72d0254772bf7d0937b41634e9a4bfcf87f60fb6))
* **package:** add package command to create a styled *.dmg for Emacs.app ([87ecfbc](https://github.com/jimeh/build-emacs-for-macos/commit/87ecfbcec05b46d7a30202269474612834b648f3))
* **plan:** add plan command to create build plans ([1ffd735](https://github.com/jimeh/build-emacs-for-macos/commit/1ffd735c23e375479ea6bb2c771553ce4cac902b))
* **release:** add release check command ([276a9da](https://github.com/jimeh/build-emacs-for-macos/commit/276a9da5eed618322e09fba11a486ae0d9925fdd))
* **release:** add release publish command ([72ca9ce](https://github.com/jimeh/build-emacs-for-macos/commit/72ca9ce2b64505a8bbc50b3139c0f84fb24813fd))
* **sign:** add sign command to sign Emacs.app bundles with codesign ([698756a](https://github.com/jimeh/build-emacs-for-macos/commit/698756ac5597d3dc7b69f28bc209093fc8c11f30))
* **release:** add v prefix to git version tags ([b719437](https://github.com/jimeh/build-emacs-for-macos/commit/b719437bee9acf28d5d352eb44cbf4d3a17107d7))
### [0.4.17](https://github.com/jimeh/build-emacs-for-macos/compare/0.4.16...0.4.17) (2021-06-20)
### Bug Fixes
* **download:** don't use GitHub API to get tarball URL ([707bc9e](https://github.com/jimeh/build-emacs-for-macos/commit/707bc9e0d13246b7cfb8d27da859a101d4a3c166))
### [0.4.16](https://github.com/jimeh/build-emacs-for-macos/compare/0.4.15...0.4.16) (2021-06-07)
### Bug Fixes
* **compiling:** improve portability of builds ([ca2d4c3](https://github.com/jimeh/build-emacs-for-macos/commit/ca2d4c38f69c434c77c266594104bfbf34ad5221))
* **native_comp:** crash on launch when gcc homebrew package was not installed ([bd81870](https://github.com/jimeh/build-emacs-for-macos/commit/bd8187065928b9f79de8b14222c98f8dc34bfe5f))
### [0.4.15](https://github.com/jimeh/build-emacs-for-macos/compare/0.4.14...0.4.15) (2021-05-31)
### Features
* **github:** perform authenticated GitHub API requests when GITHUB_TOKEN env var is set ([deda28e](https://github.com/jimeh/build-emacs-for-macos/commit/deda28e5aded2817bcc7956f377378576372816f))
### [0.4.14](https://github.com/jimeh/build-emacs-for-macos/compare/0.4.13...0.4.14) (2021-05-22)
### Features
* **cli:** add "emacs" CLI launcher script to Emacs.app/Conents/MacOS/bin ([8237aa9](https://github.com/jimeh/build-emacs-for-macos/commit/8237aa9272ce1d13a412b2495cbaa90df38d928b)), closes [#41](https://github.com/jimeh/build-emacs-for-macos/issues/41)
### [0.4.13](https://github.com/jimeh/build-emacs-for-macos/compare/0.4.12...0.4.13) (2021-05-22)
### Bug Fixes
* **codesign:** prevent "bundle format unrecognized" error from codesign ([7259111](https://github.com/jimeh/build-emacs-for-macos/commit/7259111478ecb838dea9c8f50ea39eafdf47ed5a))
* **embed:** avoid potential error caused by trying to set duplicate rpath ([bb45cda](https://github.com/jimeh/build-emacs-for-macos/commit/bb45cda0231e99618571dc835348cf5c3345e277))
### [0.4.12](https://github.com/jimeh/build-emacs-for-macos/compare/0.4.11...0.4.12) (2021-05-17)
### Bug Fixes
* **shared-libraries:** stop aggressive dylib re-linking ([0a22d83](https://github.com/jimeh/build-emacs-for-macos/commit/0a22d8393c53305354c4c6d8e784e7d59caa039a)), closes [#12](https://github.com/jimeh/build-emacs-for-macos/issues/12)
* **svg:** enable SVG by default via librsvg ([bf7c4d5](https://github.com/jimeh/build-emacs-for-macos/commit/bf7c4d5debf32980dbbabc1ea99b58b266390011))
### [0.4.11](https://github.com/jimeh/build-emacs-for-macos/compare/0.4.10...0.4.11) (2021-05-08)
### Features
* **builds:** update build script for new plan.yml format ([1df39fa](https://github.com/jimeh/build-emacs-for-macos/commit/1df39fafe62ada385aa1d92e6b7f591c16c0a80c))
* **release:** initial attempt at providing automatic builds ([6328921](https://github.com/jimeh/build-emacs-for-macos/commit/63289216d70e496d664a7e3078dea5a82eb8f65d))
### Bug Fixes
* **release:** attempt to fix issue with talking to GitHub API ([272a300](https://github.com/jimeh/build-emacs-for-macos/commit/272a3000a1f96d8f131e684736127b923513a205))
### [0.4.10](https://github.com/jimeh/build-emacs-for-macos/compare/0.4.9...0.4.10) (2021-04-25)
### Bug Fixes
* **cli:** correctly default to master branch if no git ref is given ([844df73](https://github.com/jimeh/build-emacs-for-macos/commit/844df73c8fa8440e657f7900ec89cdedb7c4c312))
### [0.4.9](https://github.com/jimeh/build-emacs-for-macos/compare/0.4.8...0.4.9) (2021-04-08)
### Bug Fixes
* **cli:** default to "master" if no git ref is given ([e19a6a7](https://github.com/jimeh/build-emacs-for-macos/commit/e19a6a7bc24379292ee06ae4c805b8c5365f2d97)), closes [#35](https://github.com/jimeh/build-emacs-for-macos/issues/35)
* **native_comp:** skip symlink creation for recent builds which do not need symlinks ([1000999](https://github.com/jimeh/build-emacs-for-macos/commit/1000999eb2673dc207a390ff3f902b9987b99173))
### [0.4.8](https://github.com/jimeh/build-emacs-for-macos/compare/0.4.7...0.4.8) (2021-02-27)
### Bug Fixes
* **native_comp:** add support for new --with-native-compilation flag ([581594d](https://github.com/jimeh/build-emacs-for-macos/commit/581594da3cfbf1dd2fa28e91710b767e21ff75d2))
### [0.4.7](https://github.com/jimeh/build-emacs-for-macos/compare/0.4.6...0.4.7) (2021-02-21)
### Bug Fixes
* **native_comp:** add libgccjit include dir during build stage ([e25ceaa](https://github.com/jimeh/build-emacs-for-macos/commit/e25ceaa7e25b0e1b9947401597845b5ba43e6cd1)), closes [#20](https://github.com/jimeh/build-emacs-for-macos/issues/20)
### [0.4.6](https://github.com/jimeh/build-emacs-for-macos/compare/0.4.5...0.4.6) (2021-02-15)
### Bug Fixes
* **native_comp:** improve env setup patch fixing potential issues ([dca023d](https://github.com/jimeh/build-emacs-for-macos/commit/dca023daecd8704f197cbc391380aa194bc47d62))
### [0.4.5](https://github.com/jimeh/build-emacs-for-macos/compare/0.4.4...0.4.5) (2021-01-06)
### Bug Fixes
* **cli:** remove defunct --[no-]native-comp-macos-fixes option ([ab55f54](https://github.com/jimeh/build-emacs-for-macos/commit/ab55f5421c81dc629e487bf4b8bb402657cb1bc4))
### [0.4.4](https://github.com/jimeh/build-emacs-for-macos/compare/0.4.3...0.4.4) (2021-01-02)
### Bug Fixes
* **deps:** add autoconf to Brewfile ([a47d3e0](https://github.com/jimeh/build-emacs-for-macos/commit/a47d3e0c6a8ea8161a3bad0eafdac2401cf53129))
### [0.4.3](https://github.com/jimeh/build-emacs-for-macos/compare/0.4.2...0.4.3) (2020-12-28)
### Bug Fixes
* **big-sur:** add Xcode CLI tools lib directory to runtime LIBRARY_PATH ([946856e](https://github.com/jimeh/build-emacs-for-macos/commit/946856e9c242d4a6fb5f839d8cae0acfafecdfc6))
* **big-sur:** added support for building on Big Sur ([2247158](https://github.com/jimeh/build-emacs-for-macos/commit/2247158051d0f59933569b6974b2b5269f13c79e))
### [0.4.2](https://github.com/jimeh/build-emacs-for-macos/compare/0.4.1...0.4.2) (2020-12-09)

View File

@@ -5,4 +5,6 @@ source 'http://rubygems.org/'
group :development do
gem 'byebug'
gem 'rubocop'
gem 'rubocop-daemon'
gem 'solargraph', '~> 0.39.17'
end

201
Makefile
View File

@@ -1,9 +1,196 @@
.PHONY: new-version
new-version:
$(if $(shell which npx),,\
$(error No npx found in PATH, please install NodeJS))
$(if $(shell which standard-version),,\
$(error No standard-version found in PATH, install with: \
npm install -g standard-version))
PIP := $(shell command -v pip3 || command -v pip)
SOURCES := $(shell \
find * \
-not -path 'sources/*' \
-not -path 'builds/*' \( \
-name "*.go" -or \
-name "go.mod" -or \
-name "go.sum" -or \
-name "Makefile" -or \
-type f -path 'internal/*' -or \
-type f -path 'cmd/*' -or \
-type f -path 'pkg/*' \
\) | grep -v '.DS_Store' \
)
#
# Environment
#
# Verbose output
ifdef VERBOSE
V = -v
endif
BINDIR := bin
TOOLDIR := $(BINDIR)/tools
# Global environment variables for all targets
SHELL ?= /bin/bash
SHELL := env \
GO111MODULE=on \
GOBIN=$(CURDIR)/$(BINDIR) \
CGO_ENABLED=0 \
PATH='$(CURDIR)/$(BINDIR):$(CURDIR)/$(TOOLDIR):$(PATH)' \
$(SHELL)
#
# Defaults
#
# Default target
.DEFAULT_GOAL := build
#
# Bootstrap
#
bootstrap: bootstrap-brew
bootstrap-ci: bootstrap-brew bootstrap-brew-ci bootstrap-pip
bootstrap-brew:
brew bundle
bootstrap-brew-ci:
brew bundle --file Brewfile.ci
bootstrap-pip:
$(PIP) install -r requirements-ci.txt
#
# Tools
#
# external tool
define tool # 1: binary-name, 2: go-import-path
TOOLS += $(TOOLDIR)/$(1)
$(TOOLDIR)/$(1): Makefile
GOBIN="$(CURDIR)/$(TOOLDIR)" go install "$(2)"
endef
$(eval $(call tool,gofumpt,mvdan.cc/gofumpt@latest))
$(eval $(call tool,golangci-lint,github.com/golangci/golangci-lint/cmd/golangci-lint@v1.40))
$(eval $(call tool,gomod,github.com/Helcaraxan/gomod@latest))
.PHONY: tools
tools: $(TOOLS)
#
# Build
#
LDFLAGS := -w -s
VERSION ?= $(shell git describe --tags --exact 2>/dev/null)
COMMIT ?= $(shell git rev-parse HEAD 2>/dev/null)
DATE ?= $(shell date '+%FT%T%z')
ifeq ($(VERSION),)
VERSION = 0.0.0-dev
endif
CMDDIR := cmd
BINS := $(shell test -d "$(CMDDIR)" && cd "$(CMDDIR)" && \
find * -maxdepth 0 -type d -exec echo $(BINDIR)/{} \;)
.PHONY: build
build: $(BINS)
$(BINS): $(BINDIR)/%: $(SOURCES)
mkdir -p "$(BINDIR)"
cd "$(CMDDIR)/$*" && go build -a $(V) \
-o "$(CURDIR)/$(BINDIR)/$*" \
-ldflags "$(LDFLAGS) \
-X main.version=$(VERSION) \
-X main.commit=$(COMMIT) \
-X main.date=$(DATE)"
#
# Development
#
TEST ?= $$(go list ./... | grep -v 'sources/' | grep -v 'builds/')
.PHONY: clean
clean:
rm -rf $(BINARY) $(TOOLS)
rm -f ./go.mod.tidy-check ./go.sum.tidy-check
.PHONY: test
test:
CGO_ENABLED=1 go test $(V) -count=1 -race $(TESTARGS) $(TEST)
.PHONY: lint
lint: $(TOOLDIR)/golangci-lint
golangci-lint $(V) run
.PHONY: format
format: $(TOOLDIR)/gofumpt
gofumpt -w .
.PHONY: gen
gen:
go generate $$(go list ./... | grep -v 'sources/' | grep -v 'builds/')
#
# Dependencies
#
.PHONY: deps
deps:
$(info Downloading dependencies)
go mod download
.PHONY: deps-update
deps-update:
$(info Downloading dependencies)
go get -u -t ./...
.PHONY: deps-analyze
deps-analyze: $(TOOLDIR)/gomod
gomod analyze
.PHONY: tidy
tidy:
go mod tidy $(V)
.PHONY: verify
verify:
go mod verify
.SILENT: check-tidy
.PHONY: check-tidy
check-tidy:
cp go.mod go.mod.tidy-check
cp go.sum go.sum.tidy-check
go mod tidy
( \
diff go.mod go.mod.tidy-check && \
diff go.sum go.sum.tidy-check && \
rm -f go.mod go.sum && \
mv go.mod.tidy-check go.mod && \
mv go.sum.tidy-check go.sum \
) || ( \
rm -f go.mod go.sum && \
mv go.mod.tidy-check go.mod && \
mv go.sum.tidy-check go.sum; \
exit 1 \
)
#
# Release
#
.PHONY: new-version
new-version: check-npx
npx standard-version
.PHONY: next-version
next-version: check-npx
npx standard-version --dry-run
.PHONY: check-npx
check-npx:
$(if $(shell which npx),,\
$(error No npx execuable found in PATH, please install NodeJS))

125
README.md
View File

@@ -1,7 +1,8 @@
# build-emacs-for-macos
My personal hacked together script for building a completely self-contained
Emacs.app application on macOS, from any git branch, tag, or ref.
Emacs.app application on macOS, from any git branch, tag, or ref. With support
for native-compilation.
Use this script at your own risk.
@@ -18,7 +19,7 @@ Use this script at your own risk.
## Status
As of writing (2020-10-04) it works for me on my machine. Your luck may vary.
As of writing (2021-04-25) it works for me on my machine. Your luck may vary.
I have successfully built:
@@ -29,8 +30,8 @@ I have successfully built:
For reference, my machine is:
- 13-inch MacBook Pro (2020), 10th-gen 2.3 GHz Quad-Core Intel Core i7 (4c/8t)
- macOS 10.15.6 (19G2021)
- Xcode 12.0
- macOS Big Sur 11.2.3 (20D91)
- Xcode 12.4 (12D4e)
## Limitations
@@ -44,6 +45,11 @@ The build produced does have some limitations:
that built the application will yield warnings. If you want to make a signed
Emacs.app, google is you friend for finding signing instructions.
## Binary Builds
Nightly and stable binary builds produced with this build script are available
from [jimeh/emacs-builds](https://github.com/jimeh/emacs-builds).
## Requirements
- [Xcode](https://apps.apple.com/gb/app/xcode/id497799835?mt=12)
@@ -71,16 +77,21 @@ available here: https://github.com/emacs-mirror/emacs
Options:
-j, --parallel COUNT Compile using COUNT parallel processes (detected: 8)
--git-sha SHA Override detected git SHA of specified branch allowing builds of old commits
--[no-]xwidgets Enable/disable XWidgets (default: enabled if supported)
--[no-]xwidgets Enable/disable XWidgets if supported (default: enabled)
--[no-]native-comp Enable/disable native-comp (default: enabled if supported)
--[no-]native-march Enable/disable -march=native CFLAG(default: disabled)
--[no-]native-full-aot Enable/disable NATIVE_FULL_AOT / Ahead of Time compilation (default: disabled)
--[no-]native-comp-macos-fixes
Enable/disable fix based on feature/native-comp-macos-fixes branch (default: enabled if native-comp supported)
--rsvg Enable SVG image support via librsvg, can yield a unstable build (default: disabled)
--[no-]rsvg Enable/disable SVG image support via librsvg (default: enabled)
--no-titlebar Apply no-titlebar patch (default: disabled)
--no-frame-refocus Apply no-frame-refocus patch (default: disabled)
--[no-]native-fast-boot DEPRECATED: use --[no-]native-full-aot instead
--[no-]launcher DEPRECATED: Launcher script is no longer used.
--[no-]github-auth Make authenticated GitHub API requests if GITHUB_TOKEN environment variable is set.(default: enabled)
--work-dir DIR Specify a working directory where tarballs, sources, and builds will be stored and worked with
-o, --output DIR Output directory for finished builds (default: <work-dir>/builds)
--build-name NAME Override generated build name
--dist-include x,y,z List of extra files to copy from Emacs source into build folder/archive (default: COPYING)
--[no-]archive Enable/disable creating *.tbz archive (default: enabled)
--[no-]archive-keep Enable/disable keeping source folder for archive (default: disabled)
--plan FILE Follow given plan file, instead of using given git ref/sha
```
Resulting applications are saved to the `builds` directory in a bzip2 compressed
@@ -94,8 +105,8 @@ trash the corresponding directory from the `sources` directory.
### Examples
To download a tarball of the `master` branch (Emacs 28.x as of writing) and
build Emacs.app from it:
To download a tarball of the `master` branch (Emacs 28.x with native-compilation
as of writing) and build Emacs.app from it:
```
./build-emacs-for-macos
@@ -112,77 +123,79 @@ All sources as downloaded as tarballs from the
to get a list of tags/branches available to install, simply check said
repository.
## Use Self-Contained Emacs.app as `emacs` CLI Tool
## Use Emacs.app as `emacs` CLI Tool
As the application bundle is self-contained, the main executable needs to be run
from within the application bundle. This means a simple symlink to
`Emacs.app/Contents/MacOS/Emacs` will not work. Instead the best approach is to
create a shell alias called `emacs` pointing to the right place.
Builds come with a custom `emacs` shell script launcher for use from the command
line, located next to `emacsclient` in `Emacs.app/Contents/MacOS/bin`.
Personally I use something similar to this:
The custom `emacs` script makes sure to use the main
`Emacs.app/Contents/MacOS/Emacs` executable from the correct path, ensuring it
finds all the relevant dependencies within the Emacs.app bundle, regardless of
it it's exposed via `PATH` or symlinked to from elsewhere.
To use it, simply add `Emacs.app/Contents/MacOS/bin` to your `PATH`. For
example, if you place Emacs.app in `/Applications`:
```bash
if [ -f "/Applications/Emacs.app/Contents/MacOS/Emacs" ]; then
export EMACS="/Applications/Emacs.app/Contents/MacOS/Emacs"
alias emacs="$EMACS -nw"
fi
if [ -f "/Applications/Emacs.app/Contents/MacOS/bin/emacsclient" ]; then
alias emacsclient="/Applications/Emacs.app/Contents/MacOS/bin/emacsclient"
if [ -d "/Applications/Emacs.app/Contents/MacOS/bin" ]; then
export PATH="/Applications/Emacs.app/Contents/MacOS/bin:$PATH"
alias emacs="emacs -nw" # Always launch "emacs" in terminal mode.
fi
```
Setting the `EMACS` variable to the binary path seems to be a good idea, as some
tools seems to use it to figure out the path to Emacs' executable, including
[doom-emacs](https://github.com/hlissner/doom-emacs)' `doom` CLI tool.
If you want `emacs` in your terminal to launch a GUI instance of Emacs, don't
use the alias from the above example.
## Native-Comp
Building a Emacs.app with native-comp support
([gccemacs](https://akrl.sdf.org/gccemacs.html)) from the `feature/native-comp`
branch is now supported without much hassle thanks to the newly released
`libgccjit` Homebrew formula.
_Note: On 2021-04-25 the `feature/native-comp` branch was
[merged](http://git.savannah.gnu.org/cgit/emacs.git/commit/?id=289000eee729689b0cf362a21baa40ac7f9506f6)
into `master`._
Changes from the `feature/native-comp-macos-fixes` branch are also applied
through a custom patch process which should be more future-proof compared to a
regular git diff patch.
To build a Emacs.app with native compilation enabled, simply run:
```
./build-emacs-for-macos feature/native-comp
```
The build script will automatically detect if the source tree being built
supports native-compilation, and enable it if available. You can override the
auto-detection logic to force enable or force disable native-compilation by
passing `--native-comp` or `--no-native-comp` respectfully.
By default `NATIVE_FULL_AOT` is disabled which ensures a fast build by native
compiling as few lisp source files as possible to build the app. Any remaining
lisp files will be dynamically compiled in the background the first time you use
them. To enable native full AoT, pass in the `--native-full-aot` option.
compiling as few elisp source files as possible to build Emacs itself. Any
remaining elisp files will be dynamically compiled in the background the first
time they are used.
On my machine it takes around 10 minutes to build Emacs.app with
`NATIVE_FULL_AOT` disabled. With it enabled it takes around 20-25 minutes.
To enable native full Ahead-of-Time compilation, pass in the `--native-full-aot`
option, which will native-compile all of Emacs' elisp at built-time. On my
machine it takes around 10 minutes to build Emacs.app with `NATIVE_FULL_AOT`
disabled, and around 20-25 minutes with it enabled.
### Configuration
Add the following near the top of your `early-init.el` or `init.el`:
```elisp
(setq comp-speed 2)
```
#### Native-Lisp Cache Directory
By default natively compiled `*.eln` files will be cached in
`~/.emacs.d/eln-cache/`. If you want to customize that, simply set a new path as
the first element of the `comp-eln-load-path` variable. The path string must end
with a `/`.
the first element of the `native-comp-eln-load-path` variable. The path string
must end with a `/`.
Below is an example which stores all compiled `*.eln` files in `cache/eln-cache`
within your Emacs configuration directory:
```elisp
(when (boundp 'comp-eln-load-path)
(setcar comp-eln-load-path
(when (boundp 'native-comp-eln-load-path)
(setcar native-comp-eln-load-path
(expand-file-name "cache/eln-cache/" user-emacs-directory)))
```
#### Compilation Warnings
By default any warnings encountered during async native compilation will pop up
a warnings buffer. As this tends to happen rather frequently with a lot of
packages, it can get annoying. You can disable showing these warnings by setting
`native-comp-async-report-warnings-errors` to `nil`:
```elisp
(setq native-comp-async-report-warnings-errors nil)
```
### Issues
Please see all issues with the
@@ -193,7 +206,7 @@ types of issues and or behavior you can expect.
### Known Good Commits/Builds
A list of known "good" commits which produce working builds is tracked in:
[#6 Known good commits of feature/native-comp branch](https://github.com/jimeh/build-emacs-for-macos/issues/6)
[#6 Known good commits for native-comp](https://github.com/jimeh/build-emacs-for-macos/issues/6)
## Credits

File diff suppressed because it is too large Load Diff

24
cmd/emacs-builder/main.go Normal file
View File

@@ -0,0 +1,24 @@
package main
import (
"fmt"
"os"
"github.com/jimeh/build-emacs-for-macos/pkg/cli"
)
var (
version string
commit string
date string
)
func main() {
cliInstance := cli.New(version, commit, date)
err := cliInstance.Run(os.Args)
if err != nil {
fmt.Fprintf(os.Stderr, "ERROR: %s\n", err.Error())
os.Exit(1)
}
}

30
go.mod Normal file
View File

@@ -0,0 +1,30 @@
module github.com/jimeh/build-emacs-for-macos
go 1.16
require (
github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect
github.com/fatih/color v1.12.0 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/google/go-github/v35 v35.3.0
github.com/google/go-querystring v1.1.0 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-hclog v0.16.1
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/hashicorp/go-retryablehttp v0.7.0 // indirect
github.com/hexops/gotextdiff v1.0.3
github.com/jimeh/undent v1.1.0
github.com/mattn/go-isatty v0.0.13 // indirect
github.com/mitchellh/gon v0.2.3
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/stretchr/testify v1.7.0
github.com/urfave/cli/v2 v2.3.0
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e // indirect
golang.org/x/net v0.0.0-20210614182718-04defd469f4e // indirect
golang.org/x/oauth2 v0.0.0-20210615190721-d04028783cf1
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22 // indirect
google.golang.org/appengine v1.6.7 // indirect
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
howett.net/plist v0.0.0-20201203080718-1454fab16a06 // indirect
)

470
go.sum Normal file
View File

@@ -0,0 +1,470 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/agext/levenshtein v1.2.1/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558=
github.com/apparentlymart/go-dump v0.0.0-20180507223929-23540a00eaa3/go.mod h1:oL81AME2rN47vu18xqj1S1jPIPuN7afo62yKTNn3XMM=
github.com/apparentlymart/go-textseg v1.0.0/go.mod h1:z96Txxhf3xSFMPmb5X/1W05FF/Nj9VFpLOpjS5yuumk=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM=
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fatih/color v1.12.0 h1:mRhaKNwANqRgUBGKmnI5ZxEk7QXmjQeCcuYFMX2bfcc=
github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-github/v35 v35.3.0 h1:fU+WBzuukn0VssbayTT+Zo3/ESKX9JYWjbZTLOTEyho=
github.com/google/go-github/v35 v35.3.0/go.mod h1:yWB7uCcVWaUbUP74Aq3whuMySRMatyRmq5U9FTNlbio=
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
github.com/hashicorp/go-hclog v0.9.3-0.20191025211905-234833755cb2/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
github.com/hashicorp/go-hclog v0.16.1 h1:IVQwpTGNRRIHafnTs2dQLIk4ENtneRIEEJWOVDqz99o=
github.com/hashicorp/go-hclog v0.16.1/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
github.com/hashicorp/go-retryablehttp v0.6.3/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY=
github.com/hashicorp/go-retryablehttp v0.7.0 h1:eu1EI/mbirUgP5C8hVsTNaGZreBDlYiwC1FZWkvQPQ4=
github.com/hashicorp/go-retryablehttp v0.7.0/go.mod h1:vAew36LZh98gCBJNLH42IQ1ER/9wtLZZ8meHqQvEYWY=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/hcl/v2 v2.0.0/go.mod h1:oVVDG71tEinNGYCxinCYadcmKU9bglqW9pV3txagJ90=
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/jimeh/undent v1.1.0 h1:Cge7P4Ws6buy0SVuHBluY/aOKdFuJUMzoJswfAHZ4zE=
github.com/jimeh/undent v1.1.0/go.mod h1:oxYCIzdbyQNy8GXnCnjRJ2NS6Uq4p4yWoeawiGFqoHI=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k=
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8=
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.13 h1:qdl+GuBjcsKKDco5BsxPJlId98mSWNKqYA+Co0SC1yA=
github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
github.com/mitchellh/gon v0.2.3 h1:fObN7hD14VacGG++t27GzTW6opP0lwI7TsgTPL55wBo=
github.com/mitchellh/gon v0.2.3/go.mod h1:Ua18ZhqjZHg8VyqZo8kNHAY331ntV6nNJ9mT3s2mIo8=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/rhysd/go-fakeio v1.0.0 h1:+TjiKCOs32dONY7DaoVz/VPOdvRkPfBkEyUDIpM8FQY=
github.com/rhysd/go-fakeio v1.0.0/go.mod h1:joYxF906trVwp2JLrE4jlN7A0z6wrz8O6o1UjarbFzE=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sebdah/goldie v1.0.0 h1:9GNhIat69MSlz/ndaBg48vl9dF5fI+NBB6kfOxgfkMc=
github.com/sebdah/goldie v1.0.0/go.mod h1:jXP4hmWywNEwZzhMuv2ccnqTSFpuq8iyQhtQdkkZBH4=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/spf13/pflag v1.0.2/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/urfave/cli/v2 v2.3.0 h1:qph92Y649prgesehzOrQjdWyxFOp/QVM+6imKHad91M=
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
github.com/vmihailenco/msgpack v3.3.3+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/zclconf/go-cty v1.1.0/go.mod h1:xnAOWiHeOqg2nWS62VtQ7pbOu17FtxJNW8RLEih+O3s=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e h1:gsTQYXdTw2Gq7RBsWvlQ91b+aEQ6bXFUngBGuR8sPpI=
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180811021610-c39426892332/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210614182718-04defd469f4e h1:XpT3nA5TvE525Ne3hInMh6+GETgn27Zfm9dxsThnX2Q=
golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20210615190721-d04028783cf1 h1:x622Z2o4hgCr/4CiKWc51jHVKaWdtVpBNmEI8wI9Qns=
golang.org/x/oauth2 v0.0.0-20210615190721-d04028783cf1/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502175342-a43fa875dd82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22 h1:RqytpXGR1iVNX7psjB3ff8y7sNFinVFvkx1c8SjBkio=
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
howett.net/plist v0.0.0-20181124034731-591f970eefbb/go.mod h1:vMygbs4qMhSZSc4lCUl2OEE+rDiIIJAIdR4m7MiMcm0=
howett.net/plist v0.0.0-20201203080718-1454fab16a06 h1:QDxUo/w2COstK1wIBYpzQlHX/NqaQTcf9jyz347nI58=
howett.net/plist v0.0.0-20201203080718-1454fab16a06/go.mod h1:vMygbs4qMhSZSc4lCUl2OEE+rDiIIJAIdR4m7MiMcm0=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=

23
helper/emacs-cli.bash Executable file
View File

@@ -0,0 +1,23 @@
#!/usr/bin/env bash
resolve_link() {
"$(type -p greadlink readlink | head -1)" "$1"
}
abs_dirname() {
local path="$1"
local name
local cwd
cwd="$(pwd)"
while [ -n "$path" ]; do
cd "${path%/*}" || exit 1
name="${path##*/}"
path="$(resolve_link "$name" || true)"
done
pwd
cd "$cwd" || exit 1
}
exec "$(dirname "$(abs_dirname "$0")")/Emacs" "$@"

View File

@@ -1,10 +1,10 @@
diff --git a/lisp/emacs-lisp/comp.el b/lisp/emacs-lisp/comp.el
index 25e2de9..bcedd31 100644
index 638d4b274c..2599211936 100644
--- a/lisp/emacs-lisp/comp.el
+++ b/lisp/emacs-lisp/comp.el
@@ -2801,6 +2801,53 @@ queued with LOAD %"
(comp-run-async-workers)
(message "Compilation started."))))
@@ -4224,6 +4224,52 @@ native-compile-async
(let ((load (not (not load))))
(native--compile-async files recursively load selector)))
+;;;###autoload
+(defun native-compile-setup-environment-variables (&rest _args)
@@ -13,25 +13,24 @@ index 25e2de9..bcedd31 100644
+ (string-match-p "\.app\/Contents\/MacOS\/?$"
+ invocation-directory))
+ (let* ((library-path-env (getenv "LIBRARY_PATH"))
+ (gcc-base-dir (concat invocation-directory "lib/gcc"))
+ (gcc-version (car (seq-filter
+ (lambda (dir) (string-match-p "^[0-9]+$" dir))
+ (directory-files gcc-base-dir))))
+ (gcc-dir (concat gcc-base-dir "/" gcc-version))
+ (darwin-base-dir (car (file-expand-wildcards
+ (concat gcc-dir "/gcc/*apple-darwin*"))))
+ (darwin-version (car (seq-filter
+ (lambda (dir)
+ (string-match-p
+ "^[0-9]+\\(\.[0-9]+\\(\.[0-9]+\\)?\\)?$" dir))
+ (directory-files darwin-base-dir))))
+ (darwin-dir (concat darwin-base-dir "/" darwin-version))
+ (lib-paths (append
+ (list gcc-dir darwin-dir)
+ (if library-path-env (list library-path-env) (list)))))
+ (devtools-dir
+ "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/lib")
+ (gcc-dir (expand-file-name
+ "<%= app_bundle_relative_lib_dir %>"
+ invocation-directory))
+ (darwin-dir (expand-file-name
+ "<%= app_bundle_relative_darwin_lib_dir %>"
+ invocation-directory))
+ (lib-paths (list)))
+
+ (when (and gcc-dir darwin-dir)
+ (setenv "LIBRARY_PATH" (mapconcat 'identity lib-paths ":")))))
+ (if library-path-env
+ (push library-path-env lib-paths))
+ (if (file-directory-p devtools-dir)
+ (push devtools-dir lib-paths))
+ (push darwin-dir lib-paths)
+ (push gcc-dir lib-paths)
+
+ (setenv "LIBRARY_PATH" (mapconcat 'identity lib-paths ":"))))
+
+ ;; Remove advice, as it only needs to run once.
+ (advice-remove 'native-compile
@@ -55,4 +54,4 @@ index 25e2de9..bcedd31 100644
+
(provide 'comp)
;;; comp.el ends here
;; LocalWords: limplified limplified limplification limplify Limple LIMPLE libgccjit elc eln

13
pkg/cask/live_check.go Normal file
View File

@@ -0,0 +1,13 @@
package cask
type LiveCheck struct {
Cask string `json:"cask"`
Version LiveCheckVersion `json:"version"`
}
type LiveCheckVersion struct {
Current string `json:"current"`
Latest string `json:"latest"`
Outdated bool `json:"outdated"`
NewerThanUpstream bool `json:"newer_than_upstream"`
}

60
pkg/cask/release_info.go Normal file
View File

@@ -0,0 +1,60 @@
package cask
import (
"sort"
"strings"
)
type ReleaseInfo struct {
Name string
Version string
Assets map[string]*ReleaseAsset
}
func (s *ReleaseInfo) Asset(nameMatch string) *ReleaseAsset {
if a, ok := s.Assets[nameMatch]; ok {
return a
}
// Dirty and inefficient way to ensure assets are searched in a predictable
// order.
var assets []*ReleaseAsset
for _, a := range s.Assets {
assets = append(assets, a)
}
sort.SliceStable(assets, func(i, j int) bool {
return assets[i].Filename < assets[j].Filename
})
for _, a := range assets {
if strings.Contains(a.Filename, nameMatch) {
return a
}
}
return nil
}
func (s *ReleaseInfo) DownloadURL(nameMatch string) string {
a := s.Asset(nameMatch)
if a == nil {
return ""
}
return a.DownloadURL
}
func (s *ReleaseInfo) SHA256(nameMatch string) string {
a := s.Asset(nameMatch)
if a == nil {
return ""
}
return a.SHA256
}
type ReleaseAsset struct {
Filename string
DownloadURL string
SHA256 string
}

493
pkg/cask/update.go Normal file
View File

@@ -0,0 +1,493 @@
package cask
import (
"bytes"
"context"
"errors"
"fmt"
"io"
"net/http"
"os"
"path/filepath"
"strings"
"text/template"
"time"
"github.com/google/go-github/v35/github"
"github.com/hashicorp/go-hclog"
"github.com/hexops/gotextdiff"
"github.com/hexops/gotextdiff/myers"
"github.com/hexops/gotextdiff/span"
"github.com/jimeh/build-emacs-for-macos/pkg/gh"
"github.com/jimeh/build-emacs-for-macos/pkg/release"
"github.com/jimeh/build-emacs-for-macos/pkg/repository"
)
// Error vars
var (
Err = errors.New("cask")
ErrReleaseNotFound = fmt.Errorf("%w: release not found", Err)
ErrFailedSHA256Parse = fmt.Errorf(
"%w: failed to parse SHA256 from asset", Err,
)
ErrFailedSHA256Download = fmt.Errorf(
"%w: failed to download SHA256 asset", Err,
)
ErrNoTapOrOutput = fmt.Errorf(
"%w: no tap repository or output directory specified", Err,
)
)
type UpdateOptions struct {
// BuildsRepo is the GitHub repository containing binary releases.
BuildsRepo *repository.Repository
// TapRepo is the GitHub repository to update the casks in.
TapRepo *repository.Repository
// Ref is the git ref to apply cask updates on top of. Default branch will
// be used if empty.
Ref string
// OutputDir specifies a directory to write cask files to. When set, tap
// repository is ignored and no changes will be committed directly against
// any specified tap repository.
OutputDir string
// Force update will ignore the outdated live check flag, and process all
// casks regardless. But it will only update the cask in question if the
// resulting output cask is different.
Force bool
// TemplatesDir is the directory where cask templates are located.
TemplatesDir string
LiveChecks []*LiveCheck
GithubToken string
}
type Updater struct {
BuildsRepo *repository.Repository
TapRepo *repository.Repository
Ref string
OutputDir string
TemplatesDir string
logger hclog.Logger
gh *github.Client
}
func Update(ctx context.Context, opts *UpdateOptions) error {
updater := &Updater{
BuildsRepo: opts.BuildsRepo,
TapRepo: opts.TapRepo,
Ref: opts.Ref,
OutputDir: opts.OutputDir,
TemplatesDir: opts.TemplatesDir,
logger: hclog.FromContext(ctx).Named("cask"),
gh: gh.New(ctx, opts.GithubToken),
}
for _, chk := range opts.LiveChecks {
err := updater.Update(ctx, chk, opts.Force)
if err != nil {
return err
}
}
return nil
}
func (s *Updater) Update(
ctx context.Context,
chk *LiveCheck,
force bool,
) error {
if s.TapRepo == nil && s.OutputDir == "" {
return ErrNoTapOrOutput
}
if !force && !chk.Version.Outdated {
s.logger.Info("skipping", "cask", chk.Cask, "reason", "up to date")
return nil
}
newCaskContent, err := s.renderCask(ctx, chk)
if err != nil {
return err
}
caskFile := chk.Cask + ".rb"
if s.OutputDir != "" {
_, err = s.putFile(
ctx, chk, filepath.Join(s.OutputDir, caskFile), newCaskContent,
)
if err != nil {
return err
}
return nil
}
_, err = s.putRepoFile(
ctx, s.TapRepo, s.Ref, chk,
filepath.Join("Casks", caskFile), newCaskContent,
)
if err != nil {
return err
}
return nil
}
func (s *Updater) putFile(
ctx context.Context,
chk *LiveCheck,
filename string,
content []byte,
) (bool, error) {
parent := filepath.Dir(filename)
s.logger.Info("processing cask update",
"output-directory", parent, "cask", chk.Cask, "file", filename,
)
err := os.MkdirAll(parent, 0o755)
if err != nil {
return false, err
}
existingContent, err := os.ReadFile(filename)
if err != nil && !os.IsNotExist(err) {
return false, err
}
infoMsg := "creating cask"
if !os.IsNotExist(err) {
infoMsg = "updating cask"
if bytes.Equal(existingContent, content) {
s.logger.Info(
"skip update: no change to cask content",
"cask", chk.Cask, "file", filename,
)
s.logger.Debug(
"cask content",
"file", filename, "content", string(content),
)
return false, nil
}
}
existing := string(existingContent)
edits := myers.ComputeEdits(
span.URIFromPath(filename), existing, string(content),
)
diff := fmt.Sprint(gotextdiff.ToUnified(
filename, filename, existing, edits,
))
s.logger.Info(
infoMsg,
"cask", chk.Cask, "version", chk.Version.Latest, "file", filename,
"diff", diff,
)
s.logger.Debug(
"cask content",
"file", filename, "content", string(content),
)
err = os.WriteFile(filename, content, 0o644) //nolint:gosec
if err != nil {
return false, err
}
return true, nil
}
func (s *Updater) putRepoFile(
ctx context.Context,
repo *repository.Repository,
ref string,
chk *LiveCheck,
filename string,
content []byte,
) (bool, error) {
s.logger.Info("processing cask update",
"tap-repo", repo.Source, "cask", chk.Cask, "file", filename,
)
repoContent, _, resp, err := s.gh.Repositories.GetContents(
ctx, repo.Owner(), repo.Name(), filename,
&github.RepositoryContentGetOptions{Ref: ref},
)
if err != nil && resp.StatusCode != http.StatusNotFound {
return false, err
}
if resp.StatusCode == http.StatusNotFound {
err := s.createRepoFile(ctx, repo, chk, filename, content)
if err != nil {
return false, err
}
} else {
_, err := s.updateRepoFile(
ctx, repo, repoContent, chk, filename, content,
)
if err != nil {
return false, err
}
}
return true, nil
}
func (s *Updater) createRepoFile(
ctx context.Context,
repo *repository.Repository,
chk *LiveCheck,
filename string,
content []byte,
) error {
commitMsg := fmt.Sprintf(
"feat(cask): create %s with version %s",
chk.Cask, chk.Version.Latest,
)
edits := myers.ComputeEdits(
span.URIFromPath(filename), "", string(content),
)
diff := fmt.Sprint(gotextdiff.ToUnified(filename, filename, "", edits))
s.logger.Info(
"creating cask",
"cask", chk.Cask, "version", chk.Version.Latest, "file", filename,
"diff", diff,
)
s.logger.Debug(
"cask content",
"file", filename, "content", string(content),
)
contResp, _, err := s.gh.Repositories.CreateFile(
ctx, repo.Owner(), repo.Name(), filename,
&github.RepositoryContentFileOptions{
Message: &commitMsg,
Content: content,
},
)
if err != nil {
return err
}
s.logger.Info(
"new commit created",
"commit", contResp.GetSHA(), "message", contResp.GetMessage(),
"url", contResp.Commit.GetHTMLURL(),
)
return nil
}
func (s *Updater) updateRepoFile(
ctx context.Context,
repo *repository.Repository,
repoContent *github.RepositoryContent,
chk *LiveCheck,
filename string,
content []byte,
) (bool, error) {
existingContent, err := repoContent.GetContent()
if err != nil {
return false, err
}
if existingContent == string(content) {
s.logger.Info(
"skip update: no change to cask content",
"cask", chk.Cask, "file", filename,
)
return false, nil
}
sha := repoContent.GetSHA()
commitMsg := fmt.Sprintf(
"feat(cask): update %s to version %s",
chk.Cask, chk.Version.Latest,
)
edits := myers.ComputeEdits(
span.URIFromPath(filename), existingContent, string(content),
)
diff := fmt.Sprint(gotextdiff.ToUnified(
filename, filename, existingContent, edits,
))
s.logger.Info(
"updating cask",
"cask", chk.Cask, "version", chk.Version.Latest, "file", filename,
"diff", diff,
)
s.logger.Debug(
"cask content",
"file", filename, "content", string(content),
)
contResp, _, err := s.gh.Repositories.CreateFile(
ctx, repo.Owner(), repo.Name(), filename,
&github.RepositoryContentFileOptions{
Message: &commitMsg,
Content: content,
SHA: &sha,
},
)
if err != nil {
return false, err
}
s.logger.Info(
"new commit created",
"commit", contResp.GetSHA(), "message", contResp.GetMessage(),
"url", contResp.Commit.GetHTMLURL(),
)
return true, nil
}
func (s *Updater) renderCask(
ctx context.Context,
chk *LiveCheck,
) ([]byte, error) {
releaseName, err := release.VersionToName(chk.Version.Latest)
if err != nil {
return nil, err
}
s.logger.Info("fetching release details",
"release", releaseName, "repo", s.BuildsRepo.URL(),
)
release, resp, err := s.gh.Repositories.GetReleaseByTag(
ctx, s.BuildsRepo.Owner(), s.BuildsRepo.Name(), releaseName,
)
if err != nil {
return nil, err
}
if release == nil || resp.StatusCode == http.StatusNotFound {
return nil, fmt.Errorf("%w: %s", ErrReleaseNotFound, releaseName)
}
info := &ReleaseInfo{
Name: release.GetName(),
Version: chk.Version.Latest,
Assets: map[string]*ReleaseAsset{},
}
s.logger.Info("processing release assets")
for _, asset := range release.Assets {
filename := asset.GetName()
s.logger.Debug("processing asset", "filename", filename)
if strings.HasSuffix(filename, ".sha256") {
filename = strings.TrimSuffix(filename, ".sha256")
}
if _, ok := info.Assets[filename]; !ok {
info.Assets[filename] = &ReleaseAsset{
Filename: filename,
}
}
if strings.HasSuffix(asset.GetName(), ".sha256") {
s.logger.Debug("downloading *.sha256 asset to extract SHA256 value")
r, err2 := s.downloadAssetContent(ctx, asset)
if err2 != nil {
return nil, err2
}
defer r.Close()
content := make([]byte, 64)
n, err2 := io.ReadAtLeast(r, content, 64)
if err2 != nil {
return nil, err2
}
if n < 64 {
return nil, fmt.Errorf(
"%w: %s", ErrFailedSHA256Parse, asset.GetName(),
)
}
sha := string(content)[0:64]
if sha == "" {
return nil, fmt.Errorf(
"%w: %s", ErrFailedSHA256Parse, asset.GetName(),
)
}
info.Assets[filename].SHA256 = sha
} else {
info.Assets[filename].DownloadURL = asset.GetBrowserDownloadURL()
}
}
templateFile := filepath.Join(s.TemplatesDir, chk.Cask+".rb.tpl")
tplContent, err := os.ReadFile(templateFile)
if err != nil {
return nil, err
}
tpl, err := template.New(chk.Cask).Parse(string(tplContent))
if err != nil {
return nil, err
}
var buf bytes.Buffer
err = tpl.Execute(&buf, info)
if err != nil {
return nil, err
}
return buf.Bytes(), nil
}
func (s *Updater) downloadAssetContent(
ctx context.Context,
asset *github.ReleaseAsset,
) (io.ReadCloser, error) {
httpClient := &http.Client{Timeout: 60 * time.Second}
r, downloadURL, err := s.gh.Repositories.DownloadReleaseAsset(
ctx, s.BuildsRepo.Owner(), s.BuildsRepo.Name(),
asset.GetID(), httpClient,
)
if err != nil {
return nil, err
}
if r == nil && downloadURL != "" {
req, err := http.NewRequestWithContext(ctx, "GET", downloadURL, nil)
if err != nil {
return nil, err
}
//nolint:bodyclose
resp, err := httpClient.Do(req)
if err != nil {
return nil, err
}
r = resp.Body
}
if r == nil {
return nil, fmt.Errorf(
"%s: %s", ErrFailedSHA256Download, asset.GetName(),
)
}
return r, nil
}

158
pkg/cli/cask.go Normal file
View File

@@ -0,0 +1,158 @@
package cli
import (
"encoding/json"
"errors"
"os"
"github.com/jimeh/build-emacs-for-macos/pkg/cask"
"github.com/jimeh/build-emacs-for-macos/pkg/repository"
cli2 "github.com/urfave/cli/v2"
)
type caskOptions struct {
BuildsRepo *repository.Repository
GithubToken string
}
func caskCmd() *cli2.Command {
tokenDefaultText := ""
if len(os.Getenv("GITHUB_TOKEN")) > 0 {
tokenDefaultText = "***"
}
return &cli2.Command{
Name: "cask",
Usage: "manage Homebrew Casks",
Flags: []cli2.Flag{
&cli2.StringFlag{
Name: "builds-repository",
Aliases: []string{"builds-repo", "b"},
Usage: "owner/name of GitHub repo for containing builds",
EnvVars: []string{"EMACS_BUILDS_REPOSITORY"},
Value: "jimeh/emacs-builds",
},
&cli2.StringFlag{
Name: "github-token",
Usage: "GitHub API Token",
EnvVars: []string{"GITHUB_TOKEN"},
DefaultText: tokenDefaultText,
Required: true,
},
},
Subcommands: []*cli2.Command{
caskUpdateCmd(),
},
}
}
func caskActionWrapper(
f func(*cli2.Context, *Options, *caskOptions) error,
) func(*cli2.Context) error {
return actionWrapper(func(c *cli2.Context, opts *Options) error {
rOpts := &caskOptions{
GithubToken: c.String("github-token"),
}
if r := c.String("builds-repository"); r != "" {
var err error
rOpts.BuildsRepo, err = repository.NewGitHub(r)
if err != nil {
return err
}
}
return f(c, opts, rOpts)
})
}
func caskUpdateCmd() *cli2.Command {
return &cli2.Command{
Name: "update",
Usage: "update casks based on brew livecheck result in JSON format",
ArgsUsage: "<livecheck.json>",
Flags: []cli2.Flag{
&cli2.StringFlag{
Name: "ref",
Usage: "git ref to create/update casks on top of in the " +
"tap repository",
EnvVars: []string{"GITHUB_REF"},
},
&cli2.StringFlag{
Name: "output",
Aliases: []string{"o"},
Usage: "directory to write cask files to",
},
&cli2.StringFlag{
Name: "tap-repository",
Aliases: []string{"tap"},
Usage: "owner/name of GitHub repo for Homebrew Tap to " +
"commit changes to if --output is not set",
EnvVars: []string{"GITHUB_REPOSITORY"},
},
&cli2.StringFlag{
Name: "templates-dir",
Aliases: []string{"t"},
Usage: "path to directory of cask templates",
EnvVars: []string{"CASK_TEMPLATE_DIR"},
Required: true,
},
&cli2.BoolFlag{
Name: "force",
Aliases: []string{"f"},
Usage: "force update file even if livecheck has it marked " +
"as not outdated (does not force update if cask " +
"content is unchanged)",
Value: false,
},
},
Action: caskActionWrapper(caskUpdateAction),
}
}
func caskUpdateAction(
c *cli2.Context,
opts *Options,
cOpts *caskOptions,
) error {
updateOpts := &cask.UpdateOptions{
BuildsRepo: cOpts.BuildsRepo,
GithubToken: cOpts.GithubToken,
Ref: c.String("ref"),
OutputDir: c.String("output"),
Force: c.Bool("force"),
TemplatesDir: c.String("templates-dir"),
}
if r := c.String("tap-repository"); r != "" {
var err error
updateOpts.TapRepo, err = repository.NewGitHub(r)
if err != nil {
return err
}
}
arg := c.Args().First()
if arg == "" {
return errors.New("no livecheck argument given")
}
if arg == "-" {
err := json.NewDecoder(c.App.Reader).Decode(&updateOpts.LiveChecks)
if err != nil {
return err
}
} else {
f, err := os.Open(arg)
if err != nil {
return err
}
err = json.NewDecoder(f).Decode(&updateOpts.LiveChecks)
if err != nil {
return err
}
}
return cask.Update(c.Context, updateOpts)
}

95
pkg/cli/cli.go Normal file
View File

@@ -0,0 +1,95 @@
package cli
import (
"fmt"
"strings"
cli2 "github.com/urfave/cli/v2"
)
type CLI struct {
App *cli2.App
Version string
Commit string
Date string
}
func New(version, commit, date string) *CLI {
if version == "" {
version = "0.0.0-dev"
}
c := &CLI{
Version: version,
Commit: commit,
Date: date,
App: &cli2.App{
Name: "emacs-builder",
Usage: "Tool to build emacs",
Version: version,
EnableBashCompletion: true,
Flags: []cli2.Flag{
&cli2.StringFlag{
Name: "log-level",
Usage: "set log level",
Aliases: []string{"l"},
Value: "info",
},
&cli2.BoolFlag{
Name: "quiet",
Usage: "silence noisy output",
Aliases: []string{"q"},
Value: false,
},
cli2.VersionFlag,
},
Commands: []*cli2.Command{
planCmd(),
signCmd(),
notarizeCmd(),
packageCmd(),
releaseCmd(),
caskCmd(),
{
Name: "version",
Usage: "print the version",
Aliases: []string{"v"},
Action: func(c *cli2.Context) error {
cli2.VersionPrinter(c)
return nil
},
},
},
},
}
cli2.VersionPrinter = c.VersionPrinter
return c
}
func (s *CLI) VersionPrinter(c *cli2.Context) {
version := c.App.Version
if version == "" {
version = "0.0.0-dev"
}
extra := []string{}
if len(s.Commit) >= 7 {
extra = append(extra, s.Commit[0:7])
}
if s.Date != "" {
extra = append(extra, s.Date)
}
var extraOut string
if len(extra) > 0 {
extraOut += " (" + strings.Join(extra, ", ") + ")"
}
fmt.Printf("%s%s\n", version, extraOut)
}
func (s *CLI) Run(args []string) error {
return s.App.Run(args)
}

79
pkg/cli/notarize.go Normal file
View File

@@ -0,0 +1,79 @@
package cli
import (
"path/filepath"
"github.com/jimeh/build-emacs-for-macos/pkg/notarize"
"github.com/jimeh/build-emacs-for-macos/pkg/plan"
cli2 "github.com/urfave/cli/v2"
)
func notarizeCmd() *cli2.Command {
return &cli2.Command{
Name: "notarize",
Usage: "notarize and staple a dmg, zip, or pkg",
ArgsUsage: "<file>",
Flags: []cli2.Flag{
&cli2.StringFlag{
Name: "bundle-id",
Usage: "bundle identifier",
Value: "org.gnu.Emacs",
},
&cli2.StringFlag{
Name: "ac-username",
Usage: "Apple Connect username",
EnvVars: []string{"AC_USERNAME"},
},
&cli2.StringFlag{
Name: "ac-password",
Usage: "Apple Connect password",
Value: "@env:AC_PASSWORD",
},
&cli2.StringFlag{
Name: "ac-provider",
Usage: "Apple Connect provider",
EnvVars: []string{"AC_PROVIDER"},
},
&cli2.BoolFlag{
Name: "staple",
Usage: "staple file after notarization",
Value: true,
},
&cli2.StringFlag{
Name: "plan",
Usage: "path to build plan YAML file produced by " +
"emacs-builder plan",
Aliases: []string{"p"},
EnvVars: []string{"EMACS_BUILDER_PLAN"},
TakesFile: true,
},
},
Action: actionWrapper(notarizeAction),
}
}
func notarizeAction(c *cli2.Context, opts *Options) error {
options := &notarize.Options{
File: c.Args().Get(0),
BundleID: c.String("bundle-id"),
Username: c.String("ac-username"),
Password: c.String("ac-password"),
Provider: c.String("ac-provider"),
Staple: c.Bool("staple"),
}
if f := c.String("plan"); f != "" {
p, err := plan.Load(f)
if err != nil {
return err
}
if p.Output != nil {
options.File = filepath.Join(
p.Output.Directory, p.Output.DiskImage,
)
}
}
return notarize.Notarize(c.Context, options)
}

49
pkg/cli/options.go Normal file
View File

@@ -0,0 +1,49 @@
package cli
import (
"fmt"
"os"
"sync"
"time"
"github.com/hashicorp/go-hclog"
cli2 "github.com/urfave/cli/v2"
)
type Options struct {
quiet bool
}
func actionWrapper(
f func(*cli2.Context, *Options) error,
) func(*cli2.Context) error {
return func(c *cli2.Context) error {
opts := &Options{
quiet: c.Bool("quiet"),
}
levelStr := c.String("log-level")
level := hclog.LevelFromString(levelStr)
if level == hclog.NoLevel {
return fmt.Errorf("invalid log level \"%s\"", levelStr)
}
// Prevent things from logging if they weren't explicitly given a
// logger.
hclog.SetDefault(hclog.NewNullLogger())
// Create custom logger.
logr := hclog.New(&hclog.LoggerOptions{
Level: level,
Output: os.Stderr,
Mutex: &sync.Mutex{},
TimeFormat: time.RFC3339,
Color: hclog.ColorOff,
})
ctx := hclog.WithContext(c.Context, logr)
c.Context = ctx
return f(c, opts)
}
}

230
pkg/cli/package.go Normal file
View File

@@ -0,0 +1,230 @@
package cli
import (
"crypto/sha256"
"fmt"
"io"
"os"
"path/filepath"
"github.com/hashicorp/go-hclog"
"github.com/jimeh/build-emacs-for-macos/pkg/dmg"
"github.com/jimeh/build-emacs-for-macos/pkg/notarize"
"github.com/jimeh/build-emacs-for-macos/pkg/plan"
"github.com/jimeh/build-emacs-for-macos/pkg/sign"
cli2 "github.com/urfave/cli/v2"
)
func packageCmd() *cli2.Command {
return &cli2.Command{
Name: "package",
Usage: "package a build directory containing Emacs.app into a dmg",
ArgsUsage: "<source-dir>",
Flags: []cli2.Flag{
&cli2.StringFlag{
Name: "volume-name",
Usage: "set volume name, defaults to basename of source dir",
Aliases: []string{"n"},
},
&cli2.BoolFlag{
Name: "sign",
Usage: "sign Emacs.app before packaging, notarize and staple " +
"dmg after packaging",
},
&cli2.StringFlag{
Name: "output",
Usage: "specify output dmg file name, if not specified the " +
"output filename is based on source directory",
Aliases: []string{"o"},
},
&cli2.BoolFlag{
Name: "sha256",
Usage: "create .sha256 checksum file for output dmg",
Aliases: []string{"s"},
Value: true,
},
&cli2.BoolFlag{
Name: "remove-source-dir",
Usage: "remove source directory after successfully " +
"creating dmg",
Aliases: []string{"rm"},
Value: false,
},
&cli2.BoolFlag{
Name: "verbose",
Usage: "verbose output",
Aliases: []string{"v"},
Value: false,
},
&cli2.StringFlag{
Name: "dmgbuild",
Usage: "specify custom path to dmgbuild executable",
},
&cli2.StringFlag{
Name: "sign-identity",
Usage: "(with --sign) signing identity passed to codesign",
EnvVars: []string{"AC_SIGN_IDENTITY"},
},
&cli2.StringFlag{
Name: "bundle-id",
Usage: "(with --sign) bundle identifier",
Value: "org.gnu.Emacs",
},
&cli2.StringFlag{
Name: "ac-username",
Usage: "(with --sign) Apple Connect username",
EnvVars: []string{"AC_USERNAME"},
},
&cli2.StringFlag{
Name: "ac-password",
Usage: "(with --sign) Apple Connect password",
Value: "@env:AC_PASSWORD",
},
&cli2.StringFlag{
Name: "ac-provider",
Usage: "(with --sign) Apple Connect provider",
EnvVars: []string{"AC_PROVIDER"},
},
&cli2.BoolFlag{
Name: "staple",
Usage: "(with --sign) stable after notarization",
Value: true,
},
&cli2.StringFlag{
Name: "plan",
Usage: "path to build plan YAML file produced by " +
"emacs-builder plan",
Aliases: []string{"p"},
EnvVars: []string{"EMACS_BUILDER_PLAN"},
TakesFile: true,
},
},
Action: actionWrapper(packageAction),
}
}
//nolint:funlen
func packageAction(c *cli2.Context, opts *Options) error {
logger := hclog.FromContext(c.Context).Named("package")
sourceDir := c.Args().Get(0)
doSign := c.Bool("sign")
var p *plan.Plan
var err error
if f := c.String("plan"); f != "" {
p, err = plan.Load(f)
if err != nil {
return err
}
}
if doSign {
app := filepath.Join(sourceDir, "Emacs.app")
signOpts := &sign.Options{
Identity: c.String("sign-identity"),
Options: []string{"runtime"},
Deep: true,
Timestamp: true,
Force: true,
Verbose: c.Bool("verbose"),
}
if p != nil {
if p.Output != nil && p.Build != nil {
app = filepath.Join(
p.Output.Directory, p.Build.Name, "Emacs.app",
)
}
}
if !opts.quiet {
signOpts.Output = os.Stdout
}
err = sign.Emacs(c.Context, app, signOpts)
if err != nil {
return err
}
}
dmgOpts := &dmg.Options{
DMGBuild: c.String("dmgbuild"),
SourceDir: sourceDir,
VolumeName: c.String("volume-name"),
OutputFile: c.String("output"),
RemoveSourceDir: c.Bool("remove-source-dir"),
Verbose: c.Bool("verbose"),
}
if p != nil && p.Output != nil && p.Build != nil {
dmgOpts.SourceDir = filepath.Join(
p.Output.Directory, p.Build.Name,
)
dmgOpts.VolumeName = p.Build.Name
dmgOpts.OutputFile = filepath.Join(
p.Output.Directory, p.Output.DiskImage,
)
}
if !opts.quiet {
dmgOpts.Output = os.Stdout
}
outputDMG, err := dmg.Create(c.Context, dmgOpts)
if err != nil {
return err
}
if doSign {
notarizeOpts := &notarize.Options{
File: outputDMG,
BundleID: c.String("bundle-id"),
Username: c.String("ac-username"),
Password: c.String("ac-password"),
Provider: c.String("ac-provider"),
Staple: c.Bool("staple"),
}
err = notarize.Notarize(c.Context, notarizeOpts)
if err != nil {
return err
}
}
if c.Bool("sha256") {
sumFile := outputDMG + ".sha256"
logger.Info("generating SHA256 checksum", "file", outputDMG)
sum, err := fileSHA256(outputDMG)
if err != nil {
return err
}
logger.Info("checksum", "sha256", sum, "file", outputDMG)
content := fmt.Sprintf("%s %s", sum, filepath.Base(outputDMG))
err = os.WriteFile(sumFile, []byte(content), 0o644) //nolint:gosec
if err != nil {
return err
}
logger.Info("wrote checksum", "file", sumFile)
}
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
}

128
pkg/cli/plan.go Normal file
View File

@@ -0,0 +1,128 @@
package cli
import (
"os"
"path/filepath"
"github.com/hashicorp/go-hclog"
"github.com/jimeh/build-emacs-for-macos/pkg/plan"
cli2 "github.com/urfave/cli/v2"
)
func planCmd() *cli2.Command {
wd, err := os.Getwd()
if err != nil {
wd = ""
}
tokenDefaultText := ""
if len(os.Getenv("GITHUB_TOKEN")) > 0 {
tokenDefaultText = "***"
}
return &cli2.Command{
Name: "plan",
Usage: "plan a Emacs.app bundle with codeplan",
ArgsUsage: "<branch/tag>",
Flags: []cli2.Flag{
&cli2.StringFlag{
Name: "emacs-repo",
Usage: "GitHub repository to get Emacs commit info and " +
"tarball from",
Aliases: []string{"e"},
EnvVars: []string{"EMACS_REPO"},
Value: "emacs-mirror/emacs",
},
&cli2.StringFlag{
Name: "sha",
Usage: "override commit SHA of specified git branch/tag",
},
&cli2.StringFlag{
Name: "output",
Usage: "output filename to write plan to instead of printing " +
"to STDOUT",
Aliases: []string{"o"},
},
&cli2.StringFlag{
Name: "output-dir",
Usage: "output directory where build result is stored",
Value: filepath.Join(wd, "builds"),
},
&cli2.StringFlag{
Name: "test-build",
Usage: "plan a test build with given name, which is " +
"published to a draft or pre-release " +
"\"test-builds\" release",
},
&cli2.StringFlag{
Name: "test-release-type",
Value: "prerelease",
Usage: "type of release when doing a test-build " +
"(prerelease or draft)",
},
&cli2.StringFlag{
Name: "github-token",
Usage: "GitHub API Token",
EnvVars: []string{"GITHUB_TOKEN"},
DefaultText: tokenDefaultText,
},
},
Action: actionWrapper(planAction),
}
}
func planAction(c *cli2.Context, opts *Options) error {
logger := hclog.FromContext(c.Context).Named("plan")
ref := c.Args().Get(0)
if ref == "" {
ref = "master"
}
planOpts := &plan.Options{
EmacsRepo: c.String("emacs-repo"),
Ref: ref,
SHAOverride: c.String("sha"),
OutputDir: c.String("output-dir"),
TestBuild: c.String("test-build"),
TestBuildType: plan.Prerelease,
GithubToken: c.String("github-token"),
}
if c.String("test-release-type") == "draft" {
planOpts.TestBuildType = plan.Draft
}
if !opts.quiet {
planOpts.Output = os.Stdout
}
p, err := plan.Create(c.Context, planOpts)
if err != nil {
return err
}
planYAML, err := p.YAML()
if err != nil {
return err
}
var out *os.File
out = os.Stdout
if f := c.String("output"); f != "" {
logger.Info("writing plan", "file", f)
logger.Debug("content", "yaml", planYAML)
out, err = os.Create(f)
if err != nil {
return err
}
defer out.Close()
}
_, err = out.WriteString(planYAML)
if err != nil {
return err
}
return nil
}

263
pkg/cli/release.go Normal file
View File

@@ -0,0 +1,263 @@
package cli
import (
"errors"
"fmt"
"os"
"path/filepath"
"github.com/jimeh/build-emacs-for-macos/pkg/plan"
"github.com/jimeh/build-emacs-for-macos/pkg/release"
"github.com/jimeh/build-emacs-for-macos/pkg/repository"
cli2 "github.com/urfave/cli/v2"
)
type releaseOptions struct {
Plan *plan.Plan
Repository *repository.Repository
Name string
GithubToken string
}
func releaseCmd() *cli2.Command {
tokenDefaultText := ""
if len(os.Getenv("GITHUB_TOKEN")) > 0 {
tokenDefaultText = "***"
}
return &cli2.Command{
Name: "release",
Usage: "manage GitHub releases",
Flags: []cli2.Flag{
&cli2.StringFlag{
Name: "plan",
Usage: "path to build plan YAML file produced by " +
"emacs-builder plan",
Aliases: []string{"p"},
EnvVars: []string{"EMACS_BUILDER_PLAN"},
TakesFile: true,
},
&cli2.StringFlag{
Name: "repository",
Aliases: []string{"repo", "r"},
Usage: "owner/name of GitHub repo to check for release, " +
"ignored if a plan is provided",
EnvVars: []string{"GITHUB_REPOSITORY"},
Value: "jimeh/emacs-builds",
},
&cli2.StringFlag{
Name: "name",
Aliases: []string{"n"},
Usage: "name of release to operate on, ignored if plan " +
"is provided",
},
&cli2.StringFlag{
Name: "github-token",
Usage: "GitHub API Token",
EnvVars: []string{"GITHUB_TOKEN"},
DefaultText: tokenDefaultText,
},
},
Subcommands: []*cli2.Command{
releaseCheckCmd(),
releasePublishCmd(),
releaseBulkCmd(),
},
}
}
func releaseActionWrapper(
f func(*cli2.Context, *Options, *releaseOptions) error,
) func(*cli2.Context) error {
return actionWrapper(func(c *cli2.Context, opts *Options) error {
rOpts := &releaseOptions{
Name: c.String("name"),
GithubToken: c.String("github-token"),
}
if r := c.String("repository"); r != "" {
var err error
rOpts.Repository, err = repository.NewGitHub(r)
if err != nil {
return err
}
}
if f := c.String("plan"); f != "" {
p, err := plan.Load(f)
if err != nil {
return err
}
rOpts.Plan = p
}
return f(c, opts, rOpts)
})
}
func releaseCheckCmd() *cli2.Command {
return &cli2.Command{
Name: "check",
Usage: "check if a GitHub release exists and has specified " +
"asset files",
ArgsUsage: "[<asset-file> ...]",
Action: releaseActionWrapper(releaseCheckAction),
}
}
func releaseCheckAction(
c *cli2.Context,
opts *Options,
rOpts *releaseOptions,
) error {
rlsOpts := &release.CheckOptions{
Repository: rOpts.Repository,
ReleaseName: rOpts.Name,
AssetFiles: c.Args().Slice(),
GithubToken: rOpts.GithubToken,
}
if rOpts.Plan != nil && rOpts.Plan.Release != nil {
rlsOpts.ReleaseName = rOpts.Plan.Release.Name
}
if rOpts.Plan != nil && rOpts.Plan.Output != nil {
rlsOpts.AssetFiles = []string{rOpts.Plan.Output.DiskImage}
}
return release.Check(c.Context, rlsOpts)
}
func releasePublishCmd() *cli2.Command {
return &cli2.Command{
Name: "publish",
Usage: "publish a GitHub release with specified asset " +
"files",
ArgsUsage: "[<asset-file> ...]",
Flags: []cli2.Flag{
&cli2.StringFlag{
Name: "sha",
Aliases: []string{"s"},
Usage: "git SHA to create release on",
EnvVars: []string{"GITHUB_SHA"},
},
&cli2.StringFlag{
Name: "type",
Aliases: []string{"t"},
Usage: "release type, must be normal, prerelease, or draft",
Value: "normal",
},
&cli2.StringFlag{
Name: "title",
Usage: "release title, will use release name if not " +
"specified",
Value: "",
},
},
Action: releaseActionWrapper(releasePublishAction),
}
}
func releasePublishAction(
c *cli2.Context,
opts *Options,
rOpts *releaseOptions,
) error {
rlsOpts := &release.PublishOptions{
Repository: rOpts.Repository,
CommitRef: c.String("release-sha"),
ReleaseName: rOpts.Name,
ReleaseTitle: c.String("title"),
AssetFiles: c.Args().Slice(),
GithubToken: rOpts.GithubToken,
}
rlsType := c.String("type")
switch rlsType {
case "draft":
rlsOpts.ReleaseType = release.Draft
case "prerelease":
rlsOpts.ReleaseType = release.Prerelease
case "normal":
rlsOpts.ReleaseType = release.Normal
default:
return fmt.Errorf("invalid --type \"%s\"", rlsType)
}
if rOpts.Plan != nil {
if rOpts.Plan.Release != nil {
rlsOpts.ReleaseName = rOpts.Plan.Release.Name
rlsOpts.ReleaseTitle = rOpts.Plan.Release.Title
if rOpts.Plan.Release.Draft {
rlsOpts.ReleaseType = release.Draft
} else if rOpts.Plan.Release.Prerelease {
rlsOpts.ReleaseType = release.Prerelease
}
}
if rOpts.Plan.Output != nil {
rlsOpts.AssetFiles = []string{
filepath.Join(
rOpts.Plan.Output.Directory,
rOpts.Plan.Output.DiskImage,
),
}
}
}
return release.Publish(c.Context, rlsOpts)
}
func releaseBulkCmd() *cli2.Command {
return &cli2.Command{
Name: "bulk",
Usage: "bulk modify GitHub releases",
ArgsUsage: "",
Flags: []cli2.Flag{
&cli2.StringFlag{
Name: "name",
Usage: "regexp pattern matching release names to modify",
},
&cli2.StringFlag{
Name: "prerelease",
Usage: "change prerelease flag, must be \"true\" or " +
"\"false\", otherwise prerelease value is not changed",
},
&cli2.BoolFlag{
Name: "dry-run",
Usage: "do not perform any changes",
},
},
Action: releaseActionWrapper(releaseBulkAction),
}
}
func releaseBulkAction(
c *cli2.Context,
opts *Options,
rOpts *releaseOptions,
) error {
bulkOpts := &release.BulkOptions{
Repository: rOpts.Repository,
NamePattern: c.String("name"),
DryRun: c.Bool("dry-run"),
GithubToken: rOpts.GithubToken,
}
switch c.String("prerelease") {
case "true":
v := true
bulkOpts.Prerelease = &v
case "false":
v := false
bulkOpts.Prerelease = &v
case "":
default:
return errors.New(
"--prerelease by me \"true\" or \"false\" when specified",
)
}
return release.Bulk(c.Context, bulkOpts)
}

114
pkg/cli/sign.go Normal file
View File

@@ -0,0 +1,114 @@
package cli
import (
"os"
"path/filepath"
"github.com/jimeh/build-emacs-for-macos/pkg/plan"
"github.com/jimeh/build-emacs-for-macos/pkg/sign"
cli2 "github.com/urfave/cli/v2"
)
func signCmd() *cli2.Command {
return &cli2.Command{
Name: "sign",
Usage: "sign a Emacs.app bundle with codesign",
ArgsUsage: "<emacs-app>",
Flags: []cli2.Flag{
&cli2.StringFlag{
Name: "sign",
Aliases: []string{"s"},
Usage: "signing identity passed to codesign",
EnvVars: []string{"AC_SIGN_IDENTITY"},
Required: true,
},
&cli2.StringSliceFlag{
Name: "entitlements",
Aliases: []string{"e"},
Usage: "comma-separated list of entitlements to enable",
Value: cli2.NewStringSlice(sign.DefaultEmacsEntitlements...),
},
&cli2.BoolFlag{
Name: "deep",
Aliases: []string{"d"},
Usage: "pass --deep to codesign",
Value: true,
},
&cli2.BoolFlag{
Name: "timestamp",
Aliases: []string{"t"},
Usage: "pass --timestamp to codesign",
Value: true,
},
&cli2.BoolFlag{
Name: "force",
Aliases: []string{"f"},
Usage: "pass --force to codesign",
Value: true,
},
&cli2.BoolFlag{
Name: "verbose",
Aliases: []string{"v"},
Usage: "pass --verbose to codesign",
Value: false,
},
&cli2.StringSliceFlag{
Name: "options",
Aliases: []string{"o"},
Usage: "options passed to codesign",
Value: cli2.NewStringSlice("runtime"),
},
&cli2.StringFlag{
Name: "codesign",
Usage: "specify custom path to codesign executable",
},
&cli2.StringFlag{
Name: "plan",
Usage: "path to build plan YAML file produced by " +
"emacs-builder plan",
Aliases: []string{"p"},
EnvVars: []string{"EMACS_BUILDER_PLAN"},
TakesFile: true,
},
},
Action: actionWrapper(signAction),
}
}
func signAction(c *cli2.Context, opts *Options) error {
signOpts := &sign.Options{
Identity: c.String("sign"),
Options: c.StringSlice("options"),
Deep: c.Bool("deep"),
Timestamp: c.Bool("timestamp"),
Force: c.Bool("force"),
Verbose: c.Bool("verbose"),
CodeSignCmd: c.String("codesign"),
}
if v := c.StringSlice("entitlements"); len(v) > 0 {
e := sign.Entitlements(v)
signOpts.Entitlements = &e
}
if !opts.quiet {
signOpts.Output = os.Stdout
}
app := c.Args().Get(0)
if f := c.String("plan"); f != "" {
p, err := plan.Load(f)
if err != nil {
return err
}
if p.Output != nil && p.Build != nil {
app = filepath.Join(
p.Output.Directory, p.Build.Name, "Emacs.app",
)
}
}
return sign.Emacs(c.Context, app, signOpts)
}

42
pkg/commit/commit.go Normal file
View File

@@ -0,0 +1,42 @@
package commit
import (
"fmt"
"time"
"github.com/google/go-github/v35/github"
)
type Commit struct {
SHA string `yaml:"sha"`
Date *time.Time `yaml:"date"`
Author string `yaml:"author"`
Committer string `yaml:"committer"`
Message string `yaml:"message"`
}
func New(rc *github.RepositoryCommit) *Commit {
return &Commit{
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")
}

45
pkg/dmg/assets/assets.go Normal file
View File

@@ -0,0 +1,45 @@
package assets
import (
_ "embed"
"os"
)
//go:generate tiffutil -cathidpicheck bg.png bg@2x.png -out bg.tif
// Background is a raw byte slice of bytes of bg.tiff
//go:embed bg.tif
var Background []byte
// BackgroundTempFile writes Background to a temporary file on disk, returning
// the resulting file path. The returned filepath should be deleted with
// os.Remove() when no longer needed.
func BackgroundTempFile() (string, error) {
return tempFile("*-emacs-bg.tif", Background)
}
// Icon is a raw byte slice of bytes of vol.icns
//go:embed vol.icns
var Icon []byte
// IconTempFile writes Icon to a temporary file on disk, returning the resulting
// file path. The returned filepath should be deleted with os.Remove() when no
// longer needed.
func IconTempFile() (string, error) {
return tempFile("*-emacs-vol.icns", Icon)
}
func tempFile(pattern string, content []byte) (string, error) {
f, err := os.CreateTemp("", pattern)
if err != nil {
return "", err
}
defer f.Close()
_, err = f.Write(content)
if err != nil {
return "", err
}
return f.Name(), nil
}

BIN
pkg/dmg/assets/bg.afdesign Normal file

Binary file not shown.

BIN
pkg/dmg/assets/bg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

BIN
pkg/dmg/assets/bg.tif Normal file

Binary file not shown.

BIN
pkg/dmg/assets/bg@2x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

BIN
pkg/dmg/assets/vol.icns Normal file

Binary file not shown.

138
pkg/dmg/dmg.go Normal file
View File

@@ -0,0 +1,138 @@
package dmg
import (
"context"
"io"
"os"
"path/filepath"
"github.com/hashicorp/go-hclog"
"github.com/jimeh/build-emacs-for-macos/pkg/dmg/assets"
"github.com/jimeh/build-emacs-for-macos/pkg/dmgbuild"
)
type Options struct {
DMGBuild string
SourceDir string
VolumeName string
OutputFile string
RemoveSourceDir bool
Verbose bool
Output io.Writer
}
//nolint:funlen
// Create will create a *.dmg disk image as specified by the given Options.
func Create(ctx context.Context, opts *Options) (string, error) {
logger := hclog.FromContext(ctx).Named("package")
sourceDir, err := filepath.Abs(opts.SourceDir)
if err != nil {
return "", err
}
appBundle := filepath.Join(sourceDir, "Emacs.app")
_, err = os.Stat(appBundle)
if err != nil {
return "", err
}
volIcon, err := assets.IconTempFile()
if err != nil {
return "", err
}
defer os.Remove(volIcon)
bgImg, err := assets.BackgroundTempFile()
if err != nil {
return "", err
}
defer os.Remove(bgImg)
volName := opts.VolumeName
if volName == "" {
volName = filepath.Base(sourceDir)
}
outputDMG := opts.OutputFile
if outputDMG == "" {
outputDMG = sourceDir + ".dmg"
}
settings := &dmgbuild.Settings{
Logger: logger,
Filename: outputDMG,
VolumeName: volName,
Icon: volIcon,
Format: dmgbuild.UDZOFormat,
CompressionLevel: 9,
Files: []*dmgbuild.File{
{
Path: appBundle,
PosX: 170,
PosY: 200,
},
},
Symlinks: []*dmgbuild.Symlink{
{
Name: "Applications",
Target: "/Applications",
PosX: 510,
PosY: 200,
},
},
Window: dmgbuild.Window{
Background: bgImg,
PoxX: 200,
PosY: 200,
Width: 680,
Height: 446,
DefaultView: dmgbuild.Icon,
},
IconView: dmgbuild.IconView{
IconSize: 160,
TextSize: 16,
},
}
copyingFile := filepath.Join(sourceDir, "COPYING")
fi, err := os.Stat(copyingFile)
if err != nil && !os.IsNotExist(err) {
return "", err
} else if err == nil && fi.Mode().IsRegular() {
settings.Files = append(settings.Files, &dmgbuild.File{
Path: copyingFile,
PosX: 340,
PosY: 506,
})
}
if opts.Output != nil {
settings.Stdout = opts.Output
settings.Stderr = opts.Output
}
logger.Info("creating dmg", "file", filepath.Base(outputDMG))
err = dmgbuild.Build(ctx, settings)
if err != nil {
return "", err
}
if opts.RemoveSourceDir {
dir, err := filepath.Abs(opts.SourceDir)
if err != nil {
return "", err
}
logger.Info("removing", "source-dir", dir)
err = os.RemoveAll(dir)
if err != nil {
return "", err
}
}
return outputDMG, nil
}

84
pkg/dmgbuild/dmgbuild.go Normal file
View File

@@ -0,0 +1,84 @@
package dmgbuild
import (
"context"
"fmt"
"os"
"os/exec"
"strings"
"github.com/hashicorp/go-hclog"
)
func Build(ctx context.Context, settings *Settings) error {
if settings == nil {
return fmt.Errorf("no settings provided")
}
logger := hclog.NewNullLogger()
if settings.Logger != nil {
logger = settings.Logger
}
if !strings.HasSuffix(logger.Name(), "dmgbuild") {
logger = logger.Named("dmgbuild")
}
_, err := os.Stat(settings.Filename)
if !os.IsNotExist(err) {
return fmt.Errorf("output dmg exists: %s", settings.Filename)
}
baseCmd := settings.Command
if baseCmd == "" {
path, err2 := exec.LookPath("dmgbuild")
if err2 != nil {
return err2
}
baseCmd = path
}
file, err := settings.TempFile()
if err != nil {
return err
}
defer os.Remove(file)
args := []string{"-s", file, settings.VolumeName, settings.Filename}
if logger.IsDebug() {
content, err2 := os.ReadFile(file)
if err2 != nil {
return err2
}
logger.Debug("using settings", file, string(content))
logger.Debug("executing", "command", baseCmd, "args", args)
}
cmd := exec.CommandContext(ctx, baseCmd, args...)
if settings.Stdout != nil {
cmd.Stdout = settings.Stdout
}
if settings.Stderr != nil {
cmd.Stderr = settings.Stderr
}
err = cmd.Run()
if err != nil {
return err
}
f, err := os.Stat(settings.Filename)
if err != nil {
if os.IsNotExist(err) {
return fmt.Errorf("output DMG file is missing")
}
return err
}
if !f.Mode().IsRegular() {
return fmt.Errorf("output DMG file is not a file")
}
return nil
}

85
pkg/dmgbuild/icon_view.go Normal file
View File

@@ -0,0 +1,85 @@
package dmgbuild
import "fmt"
type arrageOrder string
//nolint:golint
var (
NameOrder arrageOrder = "name"
DateModifiedOrder arrageOrder = "date-modified"
DateCreatedOrder arrageOrder = "date-created"
DateAddedOrder arrageOrder = "date-added"
DateLastOpenedOrder arrageOrder = "date-last-opened"
SizeOrder arrageOrder = "size"
KindOrder arrageOrder = "kind"
LabelOrder arrageOrder = "label"
)
type labelPosition string
//nolint:golint
var (
LabelBottom labelPosition = "bottom"
LabelRight labelPosition = "right"
)
type IconView struct {
ArrangeBy arrageOrder
GridOffsetX int
GridOffsetY int
GridSpacing float32
ScrollPosX float32
ScrollPosY float32
LabelPosition labelPosition
IconSize float32
TextSize float32
}
func NewIconView() IconView {
return IconView{
GridOffsetX: 0,
GridOffsetY: 0,
GridSpacing: 100,
ScrollPosX: 0.0,
ScrollPosY: 0.0,
LabelPosition: LabelBottom,
IconSize: 128,
TextSize: 16,
}
}
func (s *IconView) Render() []string {
r := []string{}
if s.ArrangeBy != "" {
r = append(r, "arrange_by = "+pyStr(string(s.ArrangeBy))+"\n")
}
if s.GridOffsetX > 0 || s.GridOffsetY > 0 {
r = append(r, fmt.Sprintf(
"grid_offset = (%d, %d)\n",
s.GridOffsetX, s.GridOffsetY,
))
}
if s.GridSpacing > 0 {
r = append(r, fmt.Sprintf("grid_spacing = %.2f\n", s.GridSpacing))
}
if s.ScrollPosX > 0 || s.ScrollPosY > 0 {
r = append(r, fmt.Sprintf(
"scroll_position = (%.2f, %.2f)\n",
s.ScrollPosX, s.ScrollPosY,
))
}
if s.LabelPosition != "" {
r = append(r, "label_position = "+pyStr(string(s.LabelPosition))+"\n")
}
if s.IconSize > 0 {
r = append(r, fmt.Sprintf("icon_size = %.2f\n", s.IconSize))
}
if s.TextSize > 0 {
r = append(r, fmt.Sprintf("text_size = %.2f\n", s.TextSize))
}
return r
}

183
pkg/dmgbuild/license.go Normal file
View File

@@ -0,0 +1,183 @@
package dmgbuild
import (
"fmt"
"sort"
"strings"
)
type locale string
//nolint:golint
var (
LocaleAfZA locale = "af_ZA"
LocaleAr locale = "ar"
LocaleBeBY locale = "be_BY"
LocaleBgBG locale = "bg_BG"
LocaleBn locale = "bn"
LocaleBo locale = "bo"
LocaleBr locale = "br"
LocaleCaES locale = "ca_ES"
LocaleCsCZ locale = "cs_CZ"
LocaleCy locale = "cy"
LocaleDaDK locale = "da_DK"
LocaleDeAT locale = "de_AT"
LocaleDeCH locale = "de_CH"
LocaleDeDE locale = "de_DE"
LocaleDzBT locale = "dz_BT"
LocaleElCY locale = "el_CY"
LocaleElGR locale = "el_GR"
LocaleEnAU locale = "en_AU"
LocaleEnCA locale = "en_CA"
LocaleEnGB locale = "en_GB"
LocaleEnIE locale = "en_IE"
LocaleEnSG locale = "en_SG"
LocaleEnUS locale = "en_US"
LocaleEo locale = "eo"
LocaleEs419 locale = "es_419"
LocaleEsES locale = "es_ES"
LocaleEtEE locale = "et_EE"
LocaleFaIR locale = "fa_IR"
LocaleFiFI locale = "fi_FI"
LocaleFoFO locale = "fo_FO"
LocaleFr001 locale = "fr_001"
LocaleFrBE locale = "fr_BE"
LocaleFrCA locale = "fr_CA"
LocaleFrCH locale = "fr_CH"
LocaleFrFR locale = "fr_FR"
LocaleGaLatgIE locale = "ga-Latg_IE"
LocaleGaIE locale = "ga_IE"
LocaleGd locale = "gd"
LocaleGrc locale = "grc"
LocaleGuIN locale = "gu_IN"
LocaleGv locale = "gv"
LocaleHeIL locale = "he_IL"
LocaleHiIN locale = "hi_IN"
LocaleHrHR locale = "hr_HR"
LocaleHuHU locale = "hu_HU"
LocaleHyAM locale = "hy_AM"
LocaleIsIS locale = "is_IS"
LocaleItCH locale = "it_CH"
LocaleItIT locale = "it_IT"
LocaleIuCA locale = "iu_CA"
LocaleJaJP locale = "ja_JP"
LocaleKaGE locale = "ka_GE"
LocaleKl locale = "kl"
LocaleKoKR locale = "ko_KR"
LocaleLtLT locale = "lt_LT"
LocaleLvLV locale = "lv_LV"
LocaleMkMK locale = "mk_MK"
LocaleMrIN locale = "mr_IN"
LocaleMtMT locale = "mt_MT"
LocaleNbNO locale = "nb_NO"
LocaleNeNP locale = "ne_NP"
LocaleNlBE locale = "nl_BE"
LocaleNlNL locale = "nl_NL"
LocaleNnNO locale = "nn_NO"
LocalePa locale = "pa"
LocalePlPL locale = "pl_PL"
LocalePtBR locale = "pt_BR"
LocalePtPT locale = "pt_PT"
LocaleRoRO locale = "ro_RO"
LocaleRuRU locale = "ru_RU"
LocaleSe locale = "se"
LocaleSkSK locale = "sk_SK"
LocaleSlSI locale = "sl_SI"
LocaleSrRS locale = "sr_RS"
LocaleSvSE locale = "sv_SE"
LocaleThTH locale = "th_TH"
LocaleToTO locale = "to_TO"
LocaleTrTR locale = "tr_TR"
LocaleUkUA locale = "uk_UA"
LocaleUrIN locale = "ur_IN"
LocaleUrPK locale = "ur_PK"
LocaleUzUZ locale = "uz_UZ"
LocaleViVN locale = "vi_VN"
LocaleZhCN locale = "zh_CN"
LocaleZhTW locale = "zh_TW"
)
type Buttons struct {
LanguageName string
Agree string
Disagree string
Print string
Save string
Message string
}
type License struct {
DefaultLanguage locale
Licenses map[locale]string
Buttons map[locale]Buttons
}
func NewLicense() License {
return License{}
}
func (s *License) Render() []string {
var l []string
if s.DefaultLanguage != "" {
l = append(l,
"\"default-language\": "+pyStr(string(s.DefaultLanguage)),
)
}
if len(s.Licenses) > 0 {
var items []string
for k, v := range s.Licenses {
items = append(items, fmt.Sprintf(
"%s: %s", pyStr(string(k)), pyMStr(v),
))
}
sort.SliceStable(items, func(i, j int) bool {
return items[i] < items[j]
})
l = append(l,
"\"licenses\": {\n "+
strings.Join(items, ",\n ")+
"\n }",
)
}
if len(s.Buttons) > 0 {
var items []string
for k, v := range s.Buttons {
items = append(items, fmt.Sprintf(
"%s: (\n"+
" %s,\n"+
" %s,\n"+
" %s,\n"+
" %s,\n"+
" %s,\n"+
" %s\n"+
" )",
pyStr(string(k)),
pyStr(v.LanguageName),
pyStr(v.Agree),
pyStr(v.Disagree),
pyStr(v.Print),
pyStr(v.Save),
pyStr(v.Message),
))
}
sort.SliceStable(items, func(i, j int) bool {
return items[i] < items[j]
})
l = append(l,
"\"buttons\": {\n "+
strings.Join(items, ",\n ")+
"\n }",
)
}
if len(l) == 0 {
return []string{}
}
return []string{
"license = {\n " + strings.Join(l, ",\n ") + "\n}\n",
}
}

154
pkg/dmgbuild/list_view.go Normal file
View File

@@ -0,0 +1,154 @@
package dmgbuild
import (
"fmt"
"sort"
"strings"
)
type listColumn string
//nolint:golint
var (
NameColumn listColumn = "name"
DateModifiedColumn listColumn = "date-modified"
DateCreatedColumn listColumn = "date-created"
DateAddedColumn listColumn = "date-added"
DateLastOpenedColumn listColumn = "date-last-opened"
SizeColumn listColumn = "size"
KindColumn listColumn = "kind"
LabelColumn listColumn = "label"
VersionColumn listColumn = "version"
CommentsColumn listColumn = "comments"
)
type direction string
//nolint:golint
var (
Ascending direction = "ascending"
Descending direction = "descending"
)
type ListView struct {
SortBy listColumn
ScrollPosX int
ScrollPosY int
IconSize float32
TextSize float32
UseRelativeDates bool
CalculateAllSizes bool
Columns []listColumn
ColumnWidths map[listColumn]int
ColumnSortDirections map[listColumn]direction
}
func NewListView() ListView {
return ListView{
SortBy: NameColumn,
IconSize: 16,
TextSize: 12,
UseRelativeDates: true,
Columns: []listColumn{
NameColumn,
DateModifiedColumn,
SizeColumn,
KindColumn,
DateAddedColumn,
},
ColumnWidths: map[listColumn]int{
(NameColumn): 300,
(DateModifiedColumn): 181,
(DateCreatedColumn): 181,
(DateAddedColumn): 181,
(DateLastOpenedColumn): 181,
(SizeColumn): 97,
(KindColumn): 115,
(LabelColumn): 100,
(VersionColumn): 75,
(CommentsColumn): 300,
},
ColumnSortDirections: map[listColumn]direction{
(NameColumn): Ascending,
(DateModifiedColumn): Descending,
(DateCreatedColumn): Descending,
(DateAddedColumn): Descending,
(DateLastOpenedColumn): Descending,
(SizeColumn): Descending,
(KindColumn): Ascending,
(LabelColumn): Ascending,
(VersionColumn): Ascending,
(CommentsColumn): Ascending,
},
}
}
func (s *ListView) Render() []string {
r := []string{}
if s.SortBy != "" {
r = append(r, "list_sort_by = "+pyStr(string(s.SortBy))+"\n")
}
if s.ScrollPosX > 0 || s.ScrollPosY > 0 {
r = append(r, fmt.Sprintf(
"list_scroll_position = (%d, %d)\n",
s.ScrollPosX, s.ScrollPosY,
))
}
if s.IconSize > 0 {
r = append(r, fmt.Sprintf("list_icon_size = %.2f\n", s.IconSize))
}
if s.TextSize > 0 {
r = append(r, fmt.Sprintf("list_text_size = %.2f\n", s.TextSize))
}
r = append(r, "list_use_relative_dates = "+pyBool(s.UseRelativeDates)+"\n")
r = append(
r, "list_calculate_all_sizes = "+pyBool(s.CalculateAllSizes)+"\n",
)
if len(s.Columns) > 0 {
var cols []string
for _, col := range s.Columns {
cols = append(cols, pyStr(string(col)))
}
r = append(r,
"list_columns = [\n "+strings.Join(cols, ",\n ")+"\n]\n",
)
}
if len(s.ColumnWidths) > 0 {
var cols []string
for col, w := range s.ColumnWidths {
cols = append(cols, fmt.Sprintf(
"%s: %d", pyStr(string(col)), w,
))
}
sort.SliceStable(cols, func(i, j int) bool {
return cols[i] < cols[j]
})
r = append(r,
"list_column_widths = {\n "+
strings.Join(cols, ",\n ")+
"\n}\n",
)
}
if len(s.ColumnSortDirections) > 0 {
var cols []string
for col, direction := range s.ColumnSortDirections {
cols = append(cols, fmt.Sprintf(
"%s: %s", pyStr(string(col)), pyStr(string(direction)),
))
}
sort.SliceStable(cols, func(i, j int) bool {
return cols[i] < cols[j]
})
r = append(r,
"list_column_sort_directions = {\n "+
strings.Join(cols, ",\n ")+
"\n}\n",
)
}
return r
}

256
pkg/dmgbuild/settings.go Normal file
View File

@@ -0,0 +1,256 @@
package dmgbuild
import (
"fmt"
"io"
"os"
"path/filepath"
"strings"
"github.com/hashicorp/go-hclog"
)
type format string
//nolint:golint
var (
UDROFormat format = "UDRO" // Read-only
UDCOFormat format = "UDCO" // Compressed (ADC)
UDZOFormat format = "UDZO" // Compressed (gzip)
UDBZFormat format = "UDBZ" // Compressed (bzip2)
UFBIFormat format = "UFBI" // Entire device
IPODFormat format = "IPOD" // iPod image
UDxxFormat format = "UDxx" // UDIF stub
UDSBFormat format = "UDSB" // Sparse bundle
UDSPFormat format = "UDSP" // Sparse
UDRWFormat format = "UDRW" // Read/write
UDTOFormat format = "UDTO" // DVD/CD master
DC42Format format = "DC42" // Disk Copy 4.2
RdWrFormat format = "RdWr" // NDIF read/write
RdxxFormat format = "Rdxx" // NDIF read-only
ROCoFormat format = "ROCo" // NDIF Compressed
RkenFormat format = "Rken" // NDIF Compressed (KenCode)
)
type File struct {
Path string
PosX int
PosY int
Hidden bool
HideExtension bool
}
type Symlink struct {
Name string
Target string
PosX int
PosY int
Hidden bool
HideExtension bool
}
type Settings struct {
// Command can be set to a custom dmgbuild executable path. If not set,
// the first "dmgbuild" executable within PATH will be used.
Command string
// Stdout will be set as STDOUT target for dmgbuild execution if not nil.
Stdout io.Writer
// Stderr will be set as STDERR target for dmgbuild execution if not nil.
Stderr io.Writer
// Logger allows logging details of dmbuild process.
Logger hclog.Logger
// dmgbuild settings
Filename string
VolumeName string
Format format
Size string
CompressionLevel int
Files []*File
Symlinks []*Symlink
Icon string
BadgeIcon string
Window Window
IconView IconView
ListView ListView
License License
}
func NewSettings() *Settings {
return &Settings{
Format: UDZOFormat,
CompressionLevel: 9,
Window: NewWindow(),
IconView: NewIconView(),
ListView: NewListView(),
License: NewLicense(),
}
}
//nolint:funlen,gocyclo
// Render returns a string slice where each string is a separate settings
// statement.
func (s *Settings) Render() ([]string, error) {
r := []string{
"# -*- coding: utf-8 -*-\n",
"from __future__ import unicode_literals\n",
}
if s.Filename != "" {
r = append(r, "filename = "+pyStr(s.Filename)+"\n")
}
if s.VolumeName != "" {
r = append(r, "volume_name = "+pyStr(s.VolumeName)+"\n")
}
if s.Format != "" {
r = append(r, "format = "+pyStr(string(s.Format))+"\n")
}
if s.CompressionLevel != 0 {
r = append(r, fmt.Sprintf(
"compression_level = %d\n", s.CompressionLevel,
))
}
if s.Size != "" {
r = append(r, "size = "+pyStr(s.Size)+"\n")
}
var files []string
var symlinks []string
var hide []string
var hideExt []string
var iconLoc []string
if len(s.Files) > 0 {
for _, f := range s.Files {
files = append(files, pyStr(f.Path))
name := filepath.Base(f.Path)
if f.PosX > 0 || f.PosY > 0 {
iconLoc = append(iconLoc,
fmt.Sprintf("%s: (%d, %d)", pyStr(name), f.PosX, f.PosY),
)
}
if f.Hidden {
hide = append(hide, pyStr(filepath.Base(f.Path)))
}
if f.HideExtension {
hideExt = append(hideExt, pyStr(filepath.Base(f.Path)))
}
}
}
if len(s.Symlinks) > 0 {
for _, l := range s.Symlinks {
symlinks = append(symlinks, pyStr(l.Name)+": "+pyStr(l.Target))
if l.PosX > 0 || l.PosY > 0 {
iconLoc = append(iconLoc,
fmt.Sprintf("%s: (%d, %d)", pyStr(l.Name), l.PosX, l.PosY),
)
}
if l.Hidden {
hide = append(hide, pyStr(l.Name))
}
if l.HideExtension {
hideExt = append(hideExt, pyStr(l.Name))
}
}
}
if len(files) > 0 {
r = append(r,
"files = [\n "+strings.Join(files, ",\n ")+"\n]\n",
)
}
if len(symlinks) > 0 {
r = append(r,
"symlinks = {\n "+strings.Join(symlinks, ",\n ")+"\n}\n",
)
}
if len(hide) > 0 {
r = append(r,
"hide = [\n "+strings.Join(hide, ",\n ")+"\n]\n",
)
}
if len(hideExt) > 0 {
r = append(r,
"hide_extensions = [\n "+strings.Join(hideExt, ",\n ")+
"\n]\n",
)
}
if len(iconLoc) > 0 {
r = append(r,
"icon_locations = {\n "+strings.Join(iconLoc, ",\n ")+"\n}\n",
)
}
if s.Icon != "" {
r = append(r, "icon = "+pyStr(s.Icon)+"\n")
}
if s.BadgeIcon != "" {
r = append(r, "badge_icon = "+pyStr(s.BadgeIcon)+"\n")
}
r = append(r, s.Window.Render()...)
r = append(r, s.IconView.Render()...)
r = append(r, s.ListView.Render()...)
r = append(r, s.License.Render()...)
return r, nil
}
func (s *Settings) Write(w io.Writer) error {
out, err := s.Render()
if err != nil {
return err
}
for _, o := range out {
_, err := w.Write([]byte(o))
if err != nil {
return err
}
}
return nil
}
func (s *Settings) TempFile() (string, error) {
f, err := os.CreateTemp("", "*.dmgbuild.settings.py")
if err != nil {
return "", err
}
defer f.Close()
err = s.Write(f)
if err != nil {
return "", err
}
return f.Name(), nil
}
func pyStr(s string) string {
s = strings.ReplaceAll(s, `\`, `\\`)
s = strings.ReplaceAll(s, `"`, `\"`)
s = strings.ReplaceAll(s, "\r", `\r`)
s = strings.ReplaceAll(s, "\n", `\n`)
return `"` + s + `"`
}
func pyMStr(s string) string {
s = strings.ReplaceAll(s, `\`, `\\`)
s = strings.ReplaceAll(s, `"`, `\"`)
return `"""` + s + `"""`
}
func pyBool(v bool) string {
if v {
return "True"
}
return "False"
}

View File

@@ -0,0 +1,444 @@
package dmgbuild
import (
"bytes"
"strings"
"testing"
"github.com/jimeh/undent"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestSettings_Write(t *testing.T) {
test := []struct {
name string
entitlements *Settings
want string
}{
{
name: "empty",
entitlements: &Settings{},
want: undent.String(`
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
show_status_bar = False
show_tab_view = False
show_toolbar = False
show_pathbar = False
show_sidebar = False
show_icon_preview = False
show_item_info = False
include_icon_view_settings = False
include_list_view_settings = False
list_use_relative_dates = False
list_calculate_all_sizes = False`,
),
},
{
name: "full",
entitlements: &Settings{
Filename: "/builds/Emacs.2021-05-25.f4dc646.master.dmg",
VolumeName: "Emacs.2021-05-25.f4dc646.master",
Format: UDBZFormat,
CompressionLevel: 8,
Size: "100m",
Files: []*File{
{
Path: "/builds/Emacs.app",
PosX: 200,
PosY: 200,
},
{
Path: "/builds/README.rtf",
PosX: 200,
PosY: 300,
HideExtension: true,
},
{
Path: "/builds/hide-me.png",
Hidden: true,
},
},
Symlinks: []*Symlink{
{
Name: "Applications",
Target: "/Applications",
PosX: 400,
PosY: 400,
},
{
Name: "QuickLook",
Target: "/Library/QuickLook",
PosX: 500,
PosY: 400,
Hidden: true,
},
{
Name: "System",
Target: "/System",
HideExtension: true,
},
},
Icon: "/opt/misc/assets/volIcon.icns",
BadgeIcon: "/builds/Emacs.app/Contents/Resources/Icon.icns",
Window: Window{
PoxX: 200,
PosY: 250,
Width: 680,
Height: 446,
Background: "/opt/misc/assets/bg.tif",
ShowStatusBar: true,
ShowTabView: true,
ShowToolbar: true,
ShowPathbar: true,
ShowSidebar: true,
SidebarWidth: 165,
DefaultView: list,
ShowIconPreview: true,
ShowItemInfo: true,
IncludeIconViewSettings: true,
IncludeListViewSettings: true,
},
IconView: IconView{
ArrangeBy: NameOrder,
GridOffsetX: 42,
GridOffsetY: 43,
GridSpacing: 44.5,
ScrollPosX: 4.5,
ScrollPosY: 5.5,
LabelPosition: LabelBottom,
IconSize: 160,
TextSize: 15,
},
ListView: ListView{
SortBy: NameColumn,
ScrollPosX: 7,
ScrollPosY: 8,
IconSize: 16,
TextSize: 12,
UseRelativeDates: true,
CalculateAllSizes: true,
Columns: []listColumn{
NameColumn,
DateModifiedColumn,
DateCreatedColumn,
DateAddedColumn,
DateLastOpenedColumn,
SizeColumn,
KindColumn,
LabelColumn,
VersionColumn,
CommentsColumn,
},
ColumnWidths: map[listColumn]int{
(NameColumn): 300,
(DateModifiedColumn): 181,
(DateCreatedColumn): 181,
(DateAddedColumn): 181,
(DateLastOpenedColumn): 181,
(SizeColumn): 97,
(KindColumn): 115,
(LabelColumn): 100,
(VersionColumn): 75,
(CommentsColumn): 300,
},
ColumnSortDirections: map[listColumn]direction{
(NameColumn): Ascending,
(DateModifiedColumn): Descending,
(DateCreatedColumn): Descending,
(DateAddedColumn): Descending,
(DateLastOpenedColumn): Descending,
(SizeColumn): Descending,
(KindColumn): Ascending,
(LabelColumn): Ascending,
(VersionColumn): Ascending,
(CommentsColumn): Ascending,
},
},
License: License{
DefaultLanguage: LocaleEnUS,
Licenses: map[locale]string{
//nolint:lll
(LocaleEnGB): undent.String(`
{\rtf1\ansi\ansicpg1252\cocoartf1504\cocoasubrtf820
{\fonttbl\f0\fnil\fcharset0 Helvetica-Bold;\f1\fnil\fcharset0 Helvetica;}
{\colortbl;\red255\green255\blue255;\red0\green0\blue0;}
{\*\expandedcolortbl;;\cssrgb\c0\c0\c0;}
\paperw11905\paperh16837\margl1133\margr1133\margb1133\margt1133
\deftab720
\pard\pardeftab720\sa160\partightenfactor0
\f0\b\fs60 \cf2 \expnd0\expndtw0\kerning0
\up0 \nosupersub \ulnone \outl0\strokewidth0 \strokec2 Test License\
\pard\pardeftab720\sa160\partightenfactor0
\fs36 \cf2 \strokec2 What is this?\
\pard\pardeftab720\sa160\partightenfactor0
\f1\b0\fs22 \cf2 \strokec2 This is the English license. It says what you are allowed to do with this software.\
\
}`,
),
//nolint:lll
(LocaleSe): undent.String(`
{\rtf1\ansi\ansicpg1252\cocoartf1504\cocoasubrtf820
{\fonttbl\f0\fnil\fcharset0 Helvetica-Bold;\f1\fnil\fcharset0 Helvetica;}
{\colortbl;\red255\green255\blue255;\red0\green0\blue0;}
{\*\expandedcolortbl;;\cssrgb\c0\c0\c0;}
\paperw11905\paperh16837\margl1133\margr1133\margb1133\margt1133
\deftab720
\pard\pardeftab720\sa160\partightenfactor0
\f0\b\fs60 \cf2 \expnd0\expndtw0\kerning0
\up0 \nosupersub \ulnone \outl0\strokewidth0 \strokec2 Test License\
\pard\pardeftab720\sa160\partightenfactor0
\fs36 \cf2 \strokec2 What is this?\
\pard\pardeftab720\sa160\partightenfactor0
\f1\b0\fs22 \cf2 \strokec2 Detta är den engelska licensen. Det står vad du får göra med den här programvaran.\
\
}`,
),
},
Buttons: map[locale]Buttons{
(LocaleEnGB): {
LanguageName: "English",
Agree: "Agree",
Disagree: "Disagree",
Print: "Print",
Save: "Save",
Message: "If you agree with the terms of this " +
"license, press \"Agree\" to install the " +
"software. If you do not agree, press " +
"\"Disagree\".",
},
(LocaleSe): {
LanguageName: "Svenska",
Agree: "Godkänn",
Disagree: "Håller inte med",
Print: "Skriv ut",
Save: "Spara",
Message: "Om du godkänner villkoren i denna " +
"licens, tryck på \"Godkänn\" för att " +
"installera programvaran. Om du inte håller " +
"med, tryck på \"Håller inte med\".",
},
},
},
},
//nolint:lll
want: undent.String(`
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
filename = "/builds/Emacs.2021-05-25.f4dc646.master.dmg"
volume_name = "Emacs.2021-05-25.f4dc646.master"
format = "UDBZ"
compression_level = 8
size = "100m"
files = [
"/builds/Emacs.app",
"/builds/README.rtf",
"/builds/hide-me.png"
]
symlinks = {
"Applications": "/Applications",
"QuickLook": "/Library/QuickLook",
"System": "/System"
}
hide = [
"hide-me.png",
"QuickLook"
]
hide_extensions = [
"README.rtf",
"System"
]
icon_locations = {
"Emacs.app": (200, 200),
"README.rtf": (200, 300),
"Applications": (400, 400),
"QuickLook": (500, 400)
}
icon = "/opt/misc/assets/volIcon.icns"
badge_icon = "/builds/Emacs.app/Contents/Resources/Icon.icns"
background = "/opt/misc/assets/bg.tif"
show_status_bar = True
show_tab_view = True
show_toolbar = True
show_pathbar = True
show_sidebar = True
sidebar_width = 165
default_view = "list-view"
window_rect = ((200, 250), (680, 446))
show_icon_preview = True
show_item_info = True
include_icon_view_settings = True
include_list_view_settings = True
arrange_by = "name"
grid_offset = (42, 43)
grid_spacing = 44.50
scroll_position = (4.50, 5.50)
label_position = "bottom"
icon_size = 160.00
text_size = 15.00
list_sort_by = "name"
list_scroll_position = (7, 8)
list_icon_size = 16.00
list_text_size = 12.00
list_use_relative_dates = True
list_calculate_all_sizes = True
list_columns = [
"name",
"date-modified",
"date-created",
"date-added",
"date-last-opened",
"size",
"kind",
"label",
"version",
"comments"
]
list_column_widths = {
"comments": 300,
"date-added": 181,
"date-created": 181,
"date-last-opened": 181,
"date-modified": 181,
"kind": 115,
"label": 100,
"name": 300,
"size": 97,
"version": 75
}
list_column_sort_directions = {
"comments": "ascending",
"date-added": "descending",
"date-created": "descending",
"date-last-opened": "descending",
"date-modified": "descending",
"kind": "ascending",
"label": "ascending",
"name": "ascending",
"size": "descending",
"version": "ascending"
}
license = {
"default-language": "en_US",
"licenses": {
"en_GB": """{\\rtf1\\ansi\\ansicpg1252\\cocoartf1504\\cocoasubrtf820
{\\fonttbl\\f0\\fnil\\fcharset0 Helvetica-Bold;\\f1\\fnil\\fcharset0 Helvetica;}
{\\colortbl;\\red255\\green255\\blue255;\\red0\\green0\\blue0;}
{\\*\\expandedcolortbl;;\\cssrgb\\c0\\c0\\c0;}
\\paperw11905\\paperh16837\\margl1133\\margr1133\\margb1133\\margt1133
\\deftab720
\\pard\\pardeftab720\\sa160\\partightenfactor0
\\f0\\b\\fs60 \\cf2 \\expnd0\\expndtw0\\kerning0
\\up0 \\nosupersub \\ulnone \\outl0\\strokewidth0 \\strokec2 Test License\\
\\pard\\pardeftab720\\sa160\\partightenfactor0
\\fs36 \\cf2 \\strokec2 What is this?\\
\\pard\\pardeftab720\\sa160\\partightenfactor0
\\f1\\b0\\fs22 \\cf2 \\strokec2 This is the English license. It says what you are allowed to do with this software.\\
\\
}""",
"se": """{\\rtf1\\ansi\\ansicpg1252\\cocoartf1504\\cocoasubrtf820
{\\fonttbl\\f0\\fnil\\fcharset0 Helvetica-Bold;\\f1\\fnil\\fcharset0 Helvetica;}
{\\colortbl;\\red255\\green255\\blue255;\\red0\\green0\\blue0;}
{\\*\\expandedcolortbl;;\\cssrgb\\c0\\c0\\c0;}
\\paperw11905\\paperh16837\\margl1133\\margr1133\\margb1133\\margt1133
\\deftab720
\\pard\\pardeftab720\\sa160\\partightenfactor0
\\f0\\b\\fs60 \\cf2 \\expnd0\\expndtw0\\kerning0
\\up0 \\nosupersub \\ulnone \\outl0\\strokewidth0 \\strokec2 Test License\\
\\pard\\pardeftab720\\sa160\\partightenfactor0
\\fs36 \\cf2 \\strokec2 What is this?\\
\\pard\\pardeftab720\\sa160\\partightenfactor0
\\f1\\b0\\fs22 \\cf2 \\strokec2 Detta är den engelska licensen. Det står vad du får göra med den här programvaran.\\
\\
}"""
},
"buttons": {
"en_GB": (
"English",
"Agree",
"Disagree",
"Print",
"Save",
"If you agree with the terms of this license, press \"Agree\" to install the software. If you do not agree, press \"Disagree\"."
),
"se": (
"Svenska",
"Godkänn",
"Håller inte med",
"Skriv ut",
"Spara",
"Om du godkänner villkoren i denna licens, tryck på \"Godkänn\" för att installera programvaran. Om du inte håller med, tryck på \"Håller inte med\"."
)
}
}`,
),
},
}
for _, tt := range test {
t.Run(tt.name, func(t *testing.T) {
var buf bytes.Buffer
err := tt.entitlements.Write(&buf)
require.NoError(t, err)
assert.Equal(t, tt.want, strings.TrimSpace(buf.String()))
})
}
}
func Test_pyStr(t *testing.T) {
tests := []struct {
name string
s string
want string
}{
{
name: "empty",
s: "",
want: `""`,
},
{
name: "regular string",
s: "foo-bar nope :)",
want: `"foo-bar nope :)"`,
},
{
name: "with single quotes",
s: "john's lost 'flip-flop'",
want: `"john's lost 'flip-flop'"`,
},
{
name: "with double quotes",
s: `john has lost a "flip-flop"`,
want: `"john has lost a \"flip-flop\""`,
},
{
name: "with backslashes",
s: `C:\path\to\file.txt`,
want: `"C:\\path\\to\\file.txt"`,
},
{
name: "with line-feed",
s: "hello\nworld",
want: `"hello\nworld"`,
},
{
name: "with carriage return",
s: "hello\rworld",
want: `"hello\rworld"`,
},
{
name: "with backslashes, single and double quotes",
s: `john's "lost" C:\path\to\file.txt`,
want: `"john's \"lost\" C:\\path\\to\\file.txt"`,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := pyStr(tt.s)
assert.Equal(t, tt.want, got)
})
}
}

83
pkg/dmgbuild/window.go Normal file
View File

@@ -0,0 +1,83 @@
package dmgbuild
import "fmt"
type view string
//nolint:golint
var (
Icon view = "icon-view"
list view = "list-view"
Column view = "column-view"
Coverflow view = "coverflow"
)
type Window struct {
PoxX int
PosY int
Width int
Height int
Background string
ShowStatusBar bool
ShowTabView bool
ShowToolbar bool
ShowPathbar bool
ShowSidebar bool
SidebarWidth int
DefaultView view
ShowIconPreview bool
ShowItemInfo bool
IncludeIconViewSettings bool
IncludeListViewSettings bool
}
func NewWindow() Window {
return Window{
PoxX: 100,
PosY: 150,
Width: 640,
Height: 280,
Background: "builtin-arrow",
DefaultView: Icon,
}
}
func (s *Window) Render() []string {
r := []string{}
if s.Background != "" {
r = append(r, "background = "+pyStr(s.Background)+"\n")
}
r = append(r, "show_status_bar = "+pyBool(s.ShowStatusBar)+"\n")
r = append(r, "show_tab_view = "+pyBool(s.ShowTabView)+"\n")
r = append(r, "show_toolbar = "+pyBool(s.ShowToolbar)+"\n")
r = append(r, "show_pathbar = "+pyBool(s.ShowPathbar)+"\n")
r = append(r, "show_sidebar = "+pyBool(s.ShowSidebar)+"\n")
if s.SidebarWidth > 0 {
r = append(r, fmt.Sprintf(
"sidebar_width = %d\n", s.SidebarWidth,
))
}
if s.DefaultView != "" {
r = append(r, "default_view = "+pyStr(string(s.DefaultView))+"\n")
}
if s.Width > 0 && s.Height > 0 {
r = append(r, fmt.Sprintf(
"window_rect = ((%d, %d), (%d, %d))\n",
s.PoxX, s.PosY, s.Width, s.Height,
))
}
r = append(r, "show_icon_preview = "+pyBool(s.ShowIconPreview)+"\n")
r = append(r, "show_item_info = "+pyBool(s.ShowIconPreview)+"\n")
r = append(
r, "include_icon_view_settings = "+pyBool(s.ShowIconPreview)+"\n",
)
r = append(
r, "include_list_view_settings = "+pyBool(s.ShowIconPreview)+"\n",
)
return r
}

24
pkg/gh/gh.go Normal file
View File

@@ -0,0 +1,24 @@
package gh
import (
"context"
"os"
"github.com/google/go-github/v35/github"
"golang.org/x/oauth2"
)
func New(ctx context.Context, token string) *github.Client {
if token == "" {
token = os.Getenv("GITHUB_TOKEN")
}
if token == "" {
return github.NewClient(nil)
}
ts := oauth2.StaticTokenSource(&oauth2.Token{AccessToken: token})
tc := oauth2.NewClient(ctx, ts)
return github.NewClient(tc)
}

98
pkg/notarize/notarize.go Normal file
View File

@@ -0,0 +1,98 @@
package notarize
import (
"context"
"os/exec"
"path/filepath"
"sync"
"time"
"github.com/hashicorp/go-hclog"
"github.com/mitchellh/gon/notarize"
"github.com/mitchellh/gon/staple"
)
type Options struct {
File string
BundleID string
Username string
Password string
Provider string
Staple bool
}
func Notarize(ctx context.Context, opts *Options) error {
logger := hclog.FromContext(ctx).Named("notarize")
notarizeOpts := &notarize.Options{
File: opts.File,
BundleId: opts.BundleID,
Username: opts.Username,
Password: opts.Password,
Provider: opts.Provider,
BaseCmd: exec.CommandContext(ctx, ""),
Status: &status{
Lock: &sync.Mutex{},
Logger: logger,
},
}
logger.Info("notarizing", "file", filepath.Base(opts.File))
info, err := notarize.Notarize(ctx, notarizeOpts)
if err != nil {
return err
}
logger.Info(
"notarization complete",
"status", info.Status,
"message", info.StatusMessage,
)
if opts.Staple {
logger.Info("stapling", "file", filepath.Base(opts.File))
err := staple.Staple(ctx, &staple.Options{
File: opts.File,
BaseCmd: exec.CommandContext(ctx, ""),
})
if err != nil {
return err
}
}
return nil
}
type status struct {
Lock *sync.Mutex
Logger hclog.Logger
lastStatusTime time.Time
}
func (s *status) Submitting() {
s.Lock.Lock()
defer s.Lock.Unlock()
s.Logger.Info("submitting file for notarization...")
}
func (s *status) Submitted(uuid string) {
s.Lock.Lock()
defer s.Lock.Unlock()
s.Logger.Info("submitted")
s.Logger.Debug("request", "uuid", uuid)
s.Logger.Info("waiting for result from Apple...")
}
func (s *status) Status(info notarize.Info) {
s.Lock.Lock()
defer s.Lock.Unlock()
if time.Now().After(s.lastStatusTime.Add(60 * time.Second)) {
s.lastStatusTime = time.Now()
s.Logger.Info("status update", "status", info.Status)
}
}

40
pkg/osinfo/osinfo.go Normal file
View File

@@ -0,0 +1,40 @@
package osinfo
import (
"os/exec"
"strings"
)
type OSInfo struct {
Name string `yaml:"name"`
Version string `yaml:"version"`
Arch string `yaml:"arch"`
}
func New() (*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) MajorMinor() string {
parts := strings.Split(s.Version, ".")
max := len(parts)
if max > 2 {
max = 2
}
return strings.Join(parts[0:max], ".")
}

141
pkg/plan/create.go Normal file
View File

@@ -0,0 +1,141 @@
package plan
import (
"context"
"fmt"
"io"
"regexp"
"strings"
"github.com/hashicorp/go-hclog"
"github.com/jimeh/build-emacs-for-macos/pkg/commit"
"github.com/jimeh/build-emacs-for-macos/pkg/gh"
"github.com/jimeh/build-emacs-for-macos/pkg/osinfo"
"github.com/jimeh/build-emacs-for-macos/pkg/release"
"github.com/jimeh/build-emacs-for-macos/pkg/repository"
)
var nonAlphaNum = regexp.MustCompile(`[^\w_-]+`)
type TestBuildType string
//nolint:golint
const (
Draft TestBuildType = "draft"
Prerelease TestBuildType = "prerelease"
)
type Options struct {
GithubToken string
EmacsRepo string
Ref string
SHAOverride string
OutputDir string
TestBuild string
TestBuildType TestBuildType
Output io.Writer
}
func Create(ctx context.Context, opts *Options) (*Plan, error) {
logger := hclog.FromContext(ctx).Named("plan")
repo, err := repository.NewGitHub(opts.EmacsRepo)
if err != nil {
return nil, err
}
gh := gh.New(ctx, opts.GithubToken)
lookupRef := opts.Ref
if opts.SHAOverride != "" {
lookupRef = opts.SHAOverride
}
logger.Info("fetching commit info", "ref", lookupRef)
repoCommit, _, err := gh.Repositories.GetCommit(
ctx, repo.Owner(), repo.Name(), lookupRef,
)
if err != nil {
return nil, err
}
commitInfo := commit.New(repoCommit)
osInfo, err := osinfo.New()
if err != nil {
return nil, err
}
version := fmt.Sprintf(
"%s.%s.%s",
commitInfo.DateString(),
commitInfo.ShortSHA(),
sanitizeString(opts.Ref),
)
releaseName := fmt.Sprintf("Emacs.%s", version)
buildName := fmt.Sprintf(
"Emacs.%s.%s.%s",
version,
sanitizeString(osInfo.Name+"-"+osInfo.MajorMinor()),
sanitizeString(osInfo.Arch),
)
diskImage := buildName + ".dmg"
plan := &Plan{
Build: &Build{
Name: buildName,
},
Source: &Source{
Ref: opts.Ref,
Repository: repo,
Commit: commitInfo,
Tarball: &Tarball{
URL: repo.TarballURL(commitInfo.SHA),
},
},
OS: osInfo,
Release: &Release{
Name: releaseName,
Prerelease: true,
},
Output: &Output{
Directory: opts.OutputDir,
DiskImage: diskImage,
},
}
// If given git ref is a stable release tag (emacs-23.2b, emacs-27.2, etc.)
// we modify release properties accordingly.
if v, err := release.GitRefToStableVersion(opts.Ref); err == nil {
plan.Release.Prerelease = false
plan.Release.Name, err = release.VersionToName(v)
if err != nil {
return nil, err
}
}
if opts.TestBuild != "" {
testName := sanitizeString(opts.TestBuild)
plan.Build.Name += ".test." + testName
plan.Release.Title = "Test Builds"
plan.Release.Name = "test-builds"
plan.Release.Prerelease = true
plan.Release.Draft = false
if opts.TestBuildType == Draft {
plan.Release.Prerelease = false
plan.Release.Draft = true
}
index := strings.LastIndex(diskImage, ".")
plan.Output.DiskImage = diskImage[:index] + ".test." +
testName + diskImage[index:]
}
return plan, nil
}
func sanitizeString(s string) string {
return nonAlphaNum.ReplaceAllString(s, "-")
}

82
pkg/plan/plan.go Normal file
View File

@@ -0,0 +1,82 @@
package plan
import (
"bytes"
"io"
"os"
"github.com/jimeh/build-emacs-for-macos/pkg/commit"
"github.com/jimeh/build-emacs-for-macos/pkg/osinfo"
"github.com/jimeh/build-emacs-for-macos/pkg/repository"
"gopkg.in/yaml.v3"
)
type Plan struct {
Build *Build `yaml:"build,omitempty"`
Source *Source `yaml:"source,omitempty"`
OS *osinfo.OSInfo `yaml:"os,omitempty"`
Release *Release `yaml:"release,omitempty"`
Output *Output `yaml:"output,omitempty"`
}
// Load attempts to loads a plan YAML from given filename.
func Load(filename string) (*Plan, error) {
b, err := os.ReadFile(filename)
if err != nil {
return nil, err
}
p := &Plan{}
err = yaml.Unmarshal(b, p)
if err != nil {
return nil, err
}
return p, nil
}
// WriteYAML writes plan in YAML format to given io.Writer.
func (s *Plan) WriteYAML(w io.Writer) error {
enc := yaml.NewEncoder(w)
enc.SetIndent(2)
return enc.Encode(s)
}
// YAML returns plan in YAML format.
func (s *Plan) YAML() (string, error) {
var buf bytes.Buffer
err := s.WriteYAML(&buf)
if err != nil {
return "", err
}
return buf.String(), nil
}
type Build struct {
Name string `yaml:"name,omitempty"`
}
type Source struct {
Ref string `yaml:"ref,omitempty"`
Repository *repository.Repository `yaml:"repository,omitempty"`
Commit *commit.Commit `yaml:"commit,omitempty"`
Tarball *Tarball `yaml:"tarball,omitempty"`
}
type Tarball struct {
URL string `yaml:"url,omitempty"`
}
type Release struct {
Name string `yaml:"name"`
Title string `yaml:"title,omitempty"`
Draft bool `yaml:"draft,omitempty"`
Prerelease bool `yaml:"prerelease,omitempty"`
}
type Output struct {
Directory string `yaml:"directory,omitempty"`
DiskImage string `yaml:"disk_image,omitempty"`
}

84
pkg/release/bulk.go Normal file
View File

@@ -0,0 +1,84 @@
package release
import (
"context"
"regexp"
"github.com/google/go-github/v35/github"
"github.com/hashicorp/go-hclog"
"github.com/jimeh/build-emacs-for-macos/pkg/gh"
"github.com/jimeh/build-emacs-for-macos/pkg/repository"
)
type BulkOptions struct {
Repository *repository.Repository
NamePattern string
Prerelease *bool
DryRun bool
GithubToken string
}
func Bulk(ctx context.Context, opts *BulkOptions) error {
logger := hclog.FromContext(ctx).Named("release")
gh := gh.New(ctx, opts.GithubToken)
nameMatcher, err := regexp.Compile(opts.NamePattern)
if err != nil {
return err
}
nextPage := 1
lastPage := 1
for nextPage <= lastPage {
releases, resp, err := gh.Repositories.ListReleases(
ctx, opts.Repository.Owner(), opts.Repository.Name(),
&github.ListOptions{
Page: nextPage,
PerPage: 100,
},
)
if err != nil {
return err
}
nextPage = resp.NextPage
lastPage = resp.LastPage
for _, r := range releases {
if !nameMatcher.MatchString(r.GetName()) {
continue
}
logger.Info("match found", "release", r.GetName())
var changes []interface{}
if opts.Prerelease != nil && r.GetPrerelease() != *opts.Prerelease {
changes = append(changes, "prerelease", *opts.Prerelease)
r.Prerelease = opts.Prerelease
}
if len(changes) > 0 {
changes = append(
[]interface{}{"release", r.GetName()}, changes...,
)
logger.Info("modifying", changes...)
if !opts.DryRun {
_, _, err = gh.Repositories.EditRelease(
ctx, opts.Repository.Owner(), opts.Repository.Name(),
r.GetID(), r,
)
if err != nil {
return err
}
}
}
}
if nextPage == 0 || lastPage == 0 {
break
}
}
return nil
}

80
pkg/release/check.go Normal file
View File

@@ -0,0 +1,80 @@
package release
import (
"context"
"fmt"
"net/http"
"path/filepath"
"strings"
"github.com/hashicorp/go-hclog"
"github.com/jimeh/build-emacs-for-macos/pkg/gh"
"github.com/jimeh/build-emacs-for-macos/pkg/repository"
)
type CheckOptions struct {
// Repository is the GitHub repository to check.
Repository *repository.Repository
// ReleaseName is the name of the GitHub Release to check.
ReleaseName string
// AssetFiles is a list of files which must all exist in the release for
// the check to pass.
AssetFiles []string
// GitHubToken is the OAuth token used to talk to the GitHub API.
GithubToken string
}
// Check checks if a GitHub repository has a Release by given name, and if the
// release contains assets with given filenames.
func Check(ctx context.Context, opts *CheckOptions) error {
logger := hclog.FromContext(ctx).Named("release")
gh := gh.New(ctx, opts.GithubToken)
repo := opts.Repository
release, resp, err := gh.Repositories.GetReleaseByTag(
ctx, repo.Owner(), repo.Name(), opts.ReleaseName,
)
if err != nil {
if resp.StatusCode == http.StatusNotFound {
return fmt.Errorf("release %s does not exist", opts.ReleaseName)
}
return err
}
logger.Info("release exists", "release", opts.ReleaseName)
missingMap := map[string]bool{}
for _, filename := range opts.AssetFiles {
filename = filepath.Base(filename)
missingMap[filename] = true
for _, a := range release.Assets {
if a.GetName() == filename {
logger.Info("asset exists", "filename", filename)
delete(missingMap, filename)
break
}
}
}
if len(missingMap) == 0 {
return nil
}
var missing []string
for f := range missingMap {
missing = append(missing, f)
}
logger.Error("missing assets", "filenames", missing)
return fmt.Errorf(
"release %s is missing assets:\n- %s",
opts.ReleaseName, strings.Join(missing, "\n-"),
)
}

242
pkg/release/publish.go Normal file
View File

@@ -0,0 +1,242 @@
package release
import (
"context"
"fmt"
"net/http"
"os"
"path/filepath"
"github.com/google/go-github/v35/github"
"github.com/hashicorp/go-hclog"
"github.com/jimeh/build-emacs-for-macos/pkg/gh"
"github.com/jimeh/build-emacs-for-macos/pkg/repository"
)
type releaseType int
// Release type constants
const (
Normal releaseType = iota
Draft
Prerelease
)
type PublishOptions struct {
// Repository is the GitHub repository to publish the release on.
Repository *repository.Repository
// CommitRef is the git commit ref to create the release tag on.
CommitRef string
// ReleaseName is the name of the git tag to create the release on.
ReleaseName string
// ReleaseTitle is the title of the release, if not specified ReleaseName
// will be used.
ReleaseTitle string
// ReleaseType is the type of release to create (normal, prerelease, or
// draft)
ReleaseType releaseType
// AssetFiles is a list of files which must all exist in the release for
// the check to pass.
AssetFiles []string
// GitHubToken is the OAuth token used to talk to the GitHub API.
GithubToken string
}
//nolint:funlen,gocyclo
// Publish creates and publishes a GitHub release.
func Publish(ctx context.Context, opts *PublishOptions) error {
logger := hclog.FromContext(ctx).Named("release")
gh := gh.New(ctx, opts.GithubToken)
files, err := publishFileList(opts.AssetFiles)
if err != nil {
return err
}
tagName := opts.ReleaseName
name := opts.ReleaseTitle
if name == "" {
name = tagName
}
prerelease := opts.ReleaseType == Prerelease
draft := opts.ReleaseType == Draft
logger.Info("checking release", "tag", tagName)
release, resp, err := gh.Repositories.GetReleaseByTag(
ctx, opts.Repository.Owner(), opts.Repository.Name(), tagName,
)
if err != nil {
if resp.StatusCode != http.StatusNotFound {
return err
}
logger.Info("creating release", "tag", tagName, "name", name)
release, _, err = gh.Repositories.CreateRelease(
ctx, opts.Repository.Owner(), opts.Repository.Name(),
&github.RepositoryRelease{
Name: &name,
TagName: &tagName,
TargetCommitish: &opts.CommitRef,
Prerelease: boolPtr(false),
Draft: boolPtr(true),
},
)
if err != nil {
return err
}
}
for _, fileName := range files {
fileIO, err2 := os.Open(fileName)
if err2 != nil {
return err2
}
defer fileIO.Close()
fileInfo, err2 := fileIO.Stat()
if err2 != nil {
return err2
}
fileBaseName := filepath.Base(fileName)
assetExists := false
for _, a := range release.Assets {
if a.GetName() != fileBaseName {
continue
}
if a.GetSize() == int(fileInfo.Size()) {
logger.Info("asset exists with correct size",
"file", fileBaseName,
"local_size", byteCountIEC(fileInfo.Size()),
"remote_size", byteCountIEC(int64(a.GetSize())),
)
assetExists = true
} else {
_, err = gh.Repositories.DeleteReleaseAsset(
ctx, opts.Repository.Owner(), opts.Repository.Name(),
a.GetID(),
)
if err != nil {
return err
}
logger.Info(
"deleted asset with wrong size", "file", fileBaseName,
)
}
}
if !assetExists {
logger.Info("uploading asset",
"file", fileBaseName,
"size", byteCountIEC(fileInfo.Size()),
)
_, _, err2 = gh.Repositories.UploadReleaseAsset(
ctx, opts.Repository.Owner(), opts.Repository.Name(),
release.GetID(),
&github.UploadOptions{Name: fileBaseName},
fileIO,
)
if err2 != nil {
return err2
}
}
}
changed := false
if release.GetName() != name {
release.Name = &name
changed = true
}
if release.GetDraft() != draft {
release.Draft = &draft
changed = true
}
if !draft && release.GetPrerelease() != prerelease {
release.Prerelease = &prerelease
changed = true
}
if changed {
release, _, err = gh.Repositories.EditRelease(
ctx, opts.Repository.Owner(), opts.Repository.Name(),
release.GetID(), release,
)
if err != nil {
return err
}
}
logger.Info("release created", "url", release.GetHTMLURL())
return nil
}
func publishFileList(files []string) ([]string, error) {
var output []string
for _, file := range files {
var err error
file, err = filepath.Abs(file)
if err != nil {
return nil, err
}
stat, err := os.Stat(file)
if err != nil {
return nil, err
}
if !stat.Mode().IsRegular() {
return nil, fmt.Errorf("\"%s\" is not a file", file)
}
output = append(output, file)
sumFile := file + ".sha256"
_, err = os.Stat(sumFile)
fmt.Printf("err: %+v\n", err)
if err != nil {
if os.IsNotExist(err) {
continue
}
return nil, err
}
output = append(output, sumFile)
}
return output, nil
}
func byteCountIEC(b int64) string {
const unit = 1024
if b < unit {
if b == 1 {
return fmt.Sprintf("%d byte", b)
}
return fmt.Sprintf("%d bytes", b)
}
div, exp := int64(unit), 0
for n := b / unit; n >= unit; n /= unit {
div *= unit
exp++
}
return fmt.Sprintf("%.1f %ciB",
float64(b)/float64(div), "KMGTPE"[exp])
}
func boolPtr(v bool) *bool {
return &v
}

42
pkg/release/version.go Normal file
View File

@@ -0,0 +1,42 @@
package release
import (
"errors"
"fmt"
"regexp"
)
// Errors
var (
Err = errors.New("release")
ErrInvalidName = fmt.Errorf("%w: invalid name", Err)
ErrEmptyVersion = fmt.Errorf("%w: empty version", Err)
ErrNotStableRef = fmt.Errorf(
"%w: git ref is not stable tagged release", Err,
)
)
var (
stableVersion = regexp.MustCompile(`^\d+\.\d+(?:[a-z]+)?$`)
stableGetRef = regexp.MustCompile(`^emacs-(\d+\.\d+(?:[a-z]+)?)$`)
)
func VersionToName(version string) (string, error) {
if version == "" {
return "", ErrEmptyVersion
}
if stableVersion.MatchString(version) {
return "Emacs-" + version, nil
}
return "Emacs." + version, nil
}
func GitRefToStableVersion(ref string) (string, error) {
if m := stableGetRef.FindStringSubmatch(ref); len(m) > 1 {
return m[1], nil
}
return "", fmt.Errorf("%w: \"%s\"", ErrNotStableRef, ref)
}

138
pkg/release/version_test.go Normal file
View File

@@ -0,0 +1,138 @@
package release
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestVersionToName(t *testing.T) {
type args struct {
version string
}
tests := []struct {
name string
args args
want string
wantErr string
}{
{
name: "empty",
args: args{
version: "",
},
wantErr: "release: empty version",
},
{
name: "nightly",
args: args{
version: "2021-07-01.1b88404.master",
},
want: "Emacs.2021-07-01.1b88404.master",
},
{
name: "stable",
args: args{
version: "27.2",
},
want: "Emacs-27.2",
},
{
name: "stable with letter",
args: args{
version: "23.3b",
},
want: "Emacs-23.3b",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := VersionToName(tt.args.version)
assert.Equal(t, tt.want, got)
if tt.wantErr != "" {
assert.EqualError(t, err, tt.wantErr)
} else {
assert.NoError(t, err)
}
})
}
}
func TestGitRefToStableVersion(t *testing.T) {
type args struct {
version string
}
tests := []struct {
name string
args args
want string
wantErr string
}{
{
name: "empty",
args: args{
version: "",
},
wantErr: "release: git ref is not stable tagged release: \"\"",
},
{
name: "master",
args: args{
version: "master",
},
wantErr: "release: git ref is not stable tagged release: " +
"\"master\"",
},
{
name: "feature",
args: args{
version: "feature/native-comp",
},
wantErr: "release: git ref is not stable tagged release: " +
"\"feature/native-comp\"",
},
{
name: "stable",
args: args{
version: "emacs-27.2",
},
want: "27.2",
},
{
name: "stable with letter",
args: args{
version: "emacs-23.3b",
},
want: "23.3b",
},
{
name: "future stable",
args: args{
version: "emacs-239.33",
},
want: "239.33",
},
{
name: "future stable with letter",
args: args{
version: "emacs-239.33c",
},
want: "239.33c",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := GitRefToStableVersion(tt.args.version)
assert.Equal(t, tt.want, got)
if tt.wantErr != "" {
assert.EqualError(t, err, tt.wantErr)
} else {
assert.NoError(t, err)
}
})
}
}

View File

@@ -0,0 +1,91 @@
package repository
import (
"errors"
"fmt"
"strings"
)
//nolint:golint
var (
Err = errors.New("repository")
ErrGitHub = fmt.Errorf("%w: github", Err)
)
const GitHubBaseURL = "https://github.com/"
// Type is a repository type
type Type string
const GitHub Type = "github"
// Repository represents basic information about a repository with helper
// methods to get various pieces of information from it.
type Repository struct {
Type Type `yaml:"type,omitempty"`
Source string `yaml:"source,omitempty"`
}
func NewGitHub(ownerAndName string) (*Repository, error) {
parts := strings.Split(ownerAndName, "/")
if len(parts) != 2 || parts[0] == "" || parts[1] == "" {
return nil, fmt.Errorf(
"%w: repository must be give in \"owner/name\" format",
ErrGitHub,
)
}
return &Repository{
Type: GitHub,
Source: ownerAndName,
}, nil
}
func (s *Repository) Owner() string {
switch s.Type {
case GitHub:
return strings.SplitN(s.Source, "/", 2)[0]
default:
return ""
}
}
func (s *Repository) Name() string {
switch s.Type {
case GitHub:
return strings.SplitN(s.Source, "/", 2)[1]
default:
return ""
}
}
func (s *Repository) URL() string {
switch s.Type {
case GitHub:
return GitHubBaseURL + s.Source
default:
return ""
}
}
func (s *Repository) CloneURL() string {
switch s.Type {
case GitHub:
return GitHubBaseURL + s.Source + ".git"
default:
return ""
}
}
func (s *Repository) TarballURL(ref string) string {
if ref == "" {
return ""
}
switch s.Type {
case GitHub:
return GitHubBaseURL + s.Source + "/tarball/" + ref
default:
return ""
}
}

141
pkg/sign/emacs.go Normal file
View File

@@ -0,0 +1,141 @@
package sign
import (
"context"
"fmt"
"io/fs"
"os"
"path/filepath"
"strings"
"github.com/hashicorp/go-hclog"
)
// Emacs signs a Emacs.app application bundle with Apple's codesign utility,
// using correct default entitlements, and also pre-signing any *.eln files
// which are in the bundle, as codesign will not detect them as requiring
// signing even with the --deep flag.
func Emacs(ctx context.Context, appBundle string, opts *Options) error {
if !strings.HasSuffix(appBundle, ".app") {
return fmt.Errorf("%s is not a .app application bundle", appBundle)
}
appBundle, err := filepath.Abs(appBundle)
if err != nil {
return err
}
_, err = os.Stat(appBundle)
if err != nil {
return err
}
logger := hclog.FromContext(ctx).Named("sign")
logger.Info("preparing to sign Emacs.app", "app", appBundle)
newOpts := *opts
if newOpts.EntitlementsFile == "" {
if newOpts.Entitlements == nil {
e := Entitlements(DefaultEmacsEntitlements)
newOpts.Entitlements = &e
}
f, err2 := newOpts.Entitlements.TempFile()
if err2 != nil {
return err2
}
defer os.Remove(f)
newOpts.EntitlementsFile = f
newOpts.Entitlements = nil
}
err = signElnFiles(ctx, appBundle, &newOpts)
if err != nil {
return err
}
err = signCLIHelper(ctx, appBundle, &newOpts)
if err != nil {
return err
}
// Ensure app bundle is signed last, as modifications to the bundle after
// signing will invalidate the signature. Hence anything within it that
// needs to be separately signed, has to happen before signing the whole
// application bundle.
return Files(ctx, []string{appBundle}, &newOpts)
}
func signElnFiles(ctx context.Context, appBundle string, opts *Options) error {
logger := hclog.FromContext(ctx).Named("sign")
elnFiles, err := elnFiles(appBundle)
if err != nil {
return err
}
if len(elnFiles) == 0 {
return nil
}
logger.Info(fmt.Sprintf(
"found %d native-lisp *.eln files in %s to sign",
len(elnFiles), filepath.Base(appBundle),
))
for _, file := range elnFiles {
err := Files(ctx, []string{file}, opts)
if err != nil {
return err
}
}
return nil
}
func signCLIHelper(ctx context.Context, appBundle string, opts *Options) error {
logger := hclog.FromContext(ctx).Named("sign")
cliHelper := filepath.Join(appBundle, "Contents", "MacOS", "bin", "emacs")
fi, err := os.Stat(cliHelper)
if err != nil && !os.IsNotExist(err) {
return err
} else if err == nil && fi.Mode().IsRegular() {
logger.Info(fmt.Sprintf(
"found Contents/MacOS/bin/emacs CLI helper script in %s to sign",
filepath.Base(appBundle),
))
err = Files(ctx, []string{cliHelper}, opts)
if err != nil {
return err
}
}
return nil
}
// elnFiles finds all native-compilation *.eln files within a Emacs.app bundle,
// excluding any *.eln which should be automatically located by codesign when
// signing the Emacs.app bundle itself with the --deep flag. Essentially this
// only returns *.eln files which must be individually signed before signing the
// app bundle itself.
func elnFiles(emacsApp string) ([]string, error) {
var files []string
walkDirFunc := func(path string, d fs.DirEntry, _err error) error {
if d.Type().IsRegular() && strings.HasSuffix(path, ".eln") &&
!strings.Contains(path, ".app/Contents/Frameworks/") {
files = append(files, path)
}
return nil
}
err := filepath.WalkDir(filepath.Join(emacsApp, "Contents"), walkDirFunc)
if err != nil {
return nil, err
}
return files, nil
}

54
pkg/sign/entitlements.go Normal file
View File

@@ -0,0 +1,54 @@
package sign
import (
"bytes"
_ "embed"
"io"
"os"
"text/template"
)
// DefaultEmacsEntitlements is the default set of entitlements application
// bundles are signed with if no entitlements are provided.
var DefaultEmacsEntitlements = []string{
"com.apple.security.cs.allow-jit",
"com.apple.security.network.client",
"com.apple.security.cs.disable-library-validation",
"com.apple.security.automation.apple-events",
}
//go:embed entitlements.tpl
var entitlementsTemplate string
type Entitlements []string
func (e Entitlements) XML() ([]byte, error) {
var buf bytes.Buffer
err := e.Write(&buf)
return buf.Bytes(), err
}
func (e Entitlements) Write(w io.Writer) error {
tpl, err := template.New("entitlements.plist").Parse(entitlementsTemplate)
if err != nil {
return err
}
return tpl.Execute(w, e)
}
func (e Entitlements) TempFile() (string, error) {
f, err := os.CreateTemp("", "*.entitlements.plist")
if err != nil {
return "", err
}
defer f.Close()
err = e.Write(f)
if err != nil {
return "", err
}
return f.Name(), nil
}

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
{{- range . }}
<key>{{ . }}</key>
<true/>{{ end }}
</dict>
</plist>

View File

@@ -0,0 +1,117 @@
package sign
import (
"bytes"
"os"
"strings"
"testing"
"github.com/jimeh/undent"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
var entitlementsTestCases = []struct {
name string
entitlements Entitlements
want string
}{
{
name: "none",
entitlements: Entitlements{},
//nolint:lll
want: undent.String(`
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
</dict>
</plist>`,
),
},
{
name: "one",
entitlements: Entitlements{"com.apple.security.cs.allow-jit"},
//nolint:lll
want: undent.String(`
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.cs.allow-jit</key>
<true/>
</dict>
</plist>`,
),
},
{
name: "many",
entitlements: Entitlements{
"com.apple.security.cs.allow-jit",
"com.apple.security.network.client",
"com.apple.security.cs.disable-library-validation",
"com.apple.security.automation.apple-events",
},
//nolint:lll
want: undent.String(`
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.cs.allow-jit</key>
<true/>
<key>com.apple.security.network.client</key>
<true/>
<key>com.apple.security.cs.disable-library-validation</key>
<true/>
<key>com.apple.security.automation.apple-events</key>
<true/>
</dict>
</plist>`,
),
},
}
func TestDefaultEmacsEntitlements(t *testing.T) {
assert.Equal(t,
[]string{
"com.apple.security.cs.allow-jit",
"com.apple.security.network.client",
"com.apple.security.cs.disable-library-validation",
"com.apple.security.automation.apple-events",
},
DefaultEmacsEntitlements,
)
}
func TestEntitlements_Write(t *testing.T) {
for _, tt := range entitlementsTestCases {
t.Run(tt.name, func(t *testing.T) {
var buf bytes.Buffer
err := tt.entitlements.Write(&buf)
require.NoError(t, err)
assert.Equal(t, tt.want, strings.TrimSpace(buf.String()))
})
}
}
func TestEntitlements_TempFile(t *testing.T) {
for _, tt := range entitlementsTestCases {
t.Run(tt.name, func(t *testing.T) {
tmpFile, err := tt.entitlements.TempFile()
require.NoError(t, err)
defer os.Remove(tmpFile)
content, err := os.ReadFile(tmpFile)
require.NoError(t, err)
assert.Equal(t, tt.want, strings.TrimSpace(string(content)))
assert.True(t,
strings.HasSuffix(tmpFile, ".entitlements.plist"),
"temp file name does not match \"*.entitlements.plist\"",
)
})
}
}

67
pkg/sign/files.go Normal file
View File

@@ -0,0 +1,67 @@
package sign
import (
"context"
"os"
"os/exec"
"strings"
"github.com/hashicorp/go-hclog"
)
func Files(ctx context.Context, files []string, opts *Options) error {
logger := hclog.FromContext(ctx).Named("sign")
args := []string{}
if opts.Identity != "" {
args = append(args, "--sign", opts.Identity)
}
if opts.Deep {
args = append(args, "--deep")
}
if opts.Timestamp {
args = append(args, "--timestamp")
}
if opts.Force {
args = append(args, "--force")
}
if opts.Verbose {
args = append(args, "--verbose")
}
if len(opts.Options) > 0 {
args = append(args, "--options", strings.Join(opts.Options, ","))
}
if opts.EntitlementsFile != "" {
args = append(args, "--entitlements", opts.EntitlementsFile)
} else if opts.Entitlements != nil {
entitlementsFile, err := opts.Entitlements.TempFile()
if err != nil {
return err
}
defer os.Remove(entitlementsFile)
logger.Debug("wrote entitlements", "file", entitlementsFile)
args = append(args, "--entitlements", entitlementsFile)
}
baseCmd := opts.CodeSignCmd
if baseCmd == "" {
path, err := exec.LookPath("codesign")
if err != nil {
return err
}
baseCmd = path
}
args = append(args, files...)
logger.Debug("executing", "command", baseCmd, "args", args)
cmd := exec.CommandContext(ctx, baseCmd, args...)
if opts.Output != nil {
cmd.Stdout = opts.Output
cmd.Stderr = opts.Output
}
return cmd.Run()
}

16
pkg/sign/options.go Normal file
View File

@@ -0,0 +1,16 @@
package sign
import "io"
type Options struct {
Identity string
Entitlements *Entitlements
EntitlementsFile string
Options []string
Deep bool
Timestamp bool
Force bool
Verbose bool
Output io.Writer
CodeSignCmd string
}

1
requirements-ci.txt Normal file
View File

@@ -0,0 +1 @@
dmgbuild