Compare commits

...

190 Commits

Author SHA1 Message Date
8c84ef128f chore(release): 0.6.39 2022-12-04 20:33:32 +00:00
8b447b6237 feat(version): correctly handle Emacs 30.x builds 2022-12-04 20:32:55 +00:00
c3d19694e7 feat(deprecate): posix-spawn patch is no longer supported
Nor is it needed, as it was only an issue in master for a short period
of time.
2022-12-04 20:30:20 +00:00
cff89684c6 chore(release): 0.6.38 2022-12-01 22:35:08 +00:00
48a512fbce feat(patch): add round-undecorated-frame from emacs-plus for 29.x 2022-12-01 22:30:48 +00:00
a3530c02e8 feat(patch): add support for experimental poll patch from emacs-plus for 29.x 2022-12-01 22:30:42 +00:00
138ac74ba5 chore(release): 0.6.37 2022-12-01 22:09:38 +00:00
0ba971ef61 feat(tree-sitter): support new --with-tree-sitter configure flag 2022-12-01 22:08:29 +00:00
1ae8771b2c chore(release): 0.6.36 2022-10-08 14:41:30 +01:00
1f2868d4b3 fix(native-comp): support new configure flag format
Commit e245c4f226979ccb717cccc8f82b2b0a0f96bdac on Emacs' master branch
added support for optional flags to the --with-native-compilation
configure flag.

This change to output of ./configure --help broke the native comp
detection present in our build script.

This change adds support for the new configure flag format, while also
using it to specify AOT when enabled.

It is still backwards compatible with the configure script in older
commits.

Fixes #76
2022-10-08 14:38:37 +01:00
bfa5bcf79b chore(release): 0.6.35 2022-08-10 16:48:24 +01:00
02d85f899f Merge pull request #75 from jimeh/fix-libgccjit12-compatibility
fix(native-comp): compatibility with libgccjit 12 homebrew formula
2022-08-10 16:46:45 +01:00
aeb3a75e5c chore(patch): minor simplification to libgccjit patches
By matching more targeted sets of strings, it should hopefully make
these patches more future-proof.
2022-08-10 16:31:51 +01:00
Alice Rum
e0fd2b16eb fix(native-comp): compatibility with libgccjit 12 homebrew formula
Since the latest update of `libgccjit` in brew to version 12, emacs does
not build again. Problem is that now dylib exists in multiple locations,
and `MAC_CFLAGS` environment variable is not filled correctly in
`configure.ac`. This commit fixes the issue.
2022-08-10 16:30:10 +01:00
d24ac084b7 chore(release): 0.6.34 2022-07-27 23:25:28 +01:00
159a7333de Merge pull request #73 from jimeh/fix-emacs-28
fix(emacs-28): patch configure.ac to support latest libgccjit
2022-07-27 23:23:39 +01:00
b582523642 fix(emacs-28): patch configure.ac to support latest libgccjit
When building Emacs 28.x the build script will attempt to patch
configure.ac if needed to support the latest version of libgccjit which
renamed libgccjit.so to libgccjit.dylib.

Fixes #72
2022-07-27 23:18:58 +01:00
4f1e748df2 chore(release): 0.6.33 2022-04-30 19:37:01 +01:00
d984633991 chore(deps): update gon package to 0.2.5 2022-04-30 19:35:21 +01:00
616f74d624 chore(release): 0.6.32 2022-04-30 17:46:51 +01:00
d7963b7664 chore(helper): minor tweak to emacs helper launch script 2022-04-30 17:46:06 +01:00
a20a8456ab feat(deps): add sqlite brew dependency for Emacs 29.x 2022-04-30 17:45:21 +01:00
8ad3ff4f53 feat(dbus): add flag to explicitly disable dbus support
Fixes #69
2022-04-30 17:27:41 +01:00
e31f5aaf93 fix(native-comp): support libgccjit 11.3.0
The libgccjit shared library file was renamed from libgccjit.so to
libgccjit.0.dylib in 11.3.0.

Fixes #71
2022-04-30 17:13:50 +01:00
c2fb07fdb8 chore(release): 0.6.31 2022-02-25 10:33:12 +00:00
07e0e3dacd fix: set source-directory correctly
Fixes #68
2022-02-25 10:32:03 +00:00
efddb9ef92 chore(release): 0.6.30 2022-02-08 03:07:18 +00:00
f7f4c0433a Merge pull request #66 from jimeh/add-homebrew-site-lisp-path 2022-02-08 03:05:31 +00:00
cbd8cb27b6 fix(site-lisp): add Homebrew's site-lisp directory to locallisppath
This should allow mu4e to be loaded from the mu homebrew package, among
other homebrew packages that provides emacs site-lisp files.

Ref: https://github.com/jimeh/emacs-builds/issues/19
2022-02-08 00:44:09 +00:00
656b96510a chore(release): 0.6.29 2022-02-07 21:07:10 +00:00
084776db6b feat(cask): add support for pretest builds 2022-02-07 21:04:59 +00:00
1e6d6cc6cf chore(release): 0.6.28 2022-01-15 23:19:07 +00:00
68ef4c066c feat(build): add dbus dependency to enable support in Emacs builds
Dbus support is automatically enabled if available on the system.
2022-01-15 23:17:25 +00:00
d476fd33ec chore(release): 0.6.27 2021-12-05 12:57:03 +00:00
baa5930467 Merge pull request #63 from jimeh/add-pretest-build-support
feat(plan): add support for pretest and release candidate builds
2021-12-05 12:54:33 +00:00
743b10c751 feat(plan): add support for pretest and release candidate builds
Add support for naming release and builds accordingly when given a git
ref for a pretest (90 or above patch number) or release
candidate ("-rcX" at the end of the tag).
2021-12-05 12:52:08 +00:00
59f1bcd3e8 chore(release): 0.6.26 2021-11-27 05:01:57 +00:00
e767e284b7 Merge pull request #62 from jimeh/improve-shared-library-embedding 2021-11-27 05:01:16 +00:00
8129a2e93b docs(readme): update Status section, and minor tweaks 2021-11-27 04:58:54 +00:00
4ae288cae3 feat(build): re-link eln files by default again
This reverts commit d338c136db.

Thanks to using relinking with @rpath, paths are shorting leaving enough
room in all *.eln files to be signed.
2021-11-27 04:25:03 +00:00
3bd78d130a feat(native-comp): no longer require gcc homebrew formula
This finally makes Emacs.app with native-comp fully self-contained, no
longer requiring the GCC Homebrew formula to be installed when
loading *.eln files that link against
/usr/local/lib/gcc/11/libgcc_s.1.dylib.

By adding the signing entitlement
com.apple.security.cs.allow-dyld-environment-variables, which allows
dynamic library loading to be controlled via DYLD_* environment
variables. It seems the lack of this was preventing Emacs from loading
the bundled libgcc_s.1.dylib file from Contents/Frameworks.

Fixes #53
2021-11-27 04:01:23 +00:00
fb5362ce18 fix(embed): relink shared libraries with @rpath instead of @executable_path
This allows much shorter shared library link paths within *.eln files
compared to when using @executable_path, which leaves enough space in
them to sign all *.eln files, while directly having them linked against
the embedded copy of GCC shared libraries.
2021-11-27 03:47:43 +00:00
1a34a9504a chore(release): 0.6.25 2021-11-25 23:38:52 +00:00
8513521d29 Merge pull request #61 from jimeh/refactor-file-operations 2021-11-25 23:36:55 +00:00
88bbefadc6 refactor(embed): use cp, mv, and chmod shell commands instead of FileUtils
It seems like Ruby's FileUtils.cp_r method especially was messing with
some file attributes, causing Emacs to print details about loading
various elisp files on startup. While when Emacs.app is copied into the
output build directory using "cp -a" instead of FileUtils.cp_r it does
not exhibit this behavior.
2021-11-25 20:14:16 +00:00
f1bf1d93b8 chore(release): 0.6.24 2021-11-24 21:42:52 +00:00
8a467b0d43 fix(embedding): embedding GCC fails when paths do not require sanitizing
It seems homebrew bottle of GCC for macOS Monterey (12.0) have changed their
directory structure slightly, which caused an issue were we were trying
to rename a specific directory to the same name it already had.

This behavior is still required as it looks like GCC on macOS
Catalina (10.15), the GCC homebrew bottle still uses the old directory
structure.
2021-11-24 21:39:03 +00:00
9c29f721b4 chore(release): 0.6.23 2021-11-10 22:19:28 +00:00
4ac71ddf39 docs(readme): update usage section with latest help text 2021-11-10 22:19:06 +00:00
d338c136db fix(build): do not re-link eln files by default
Relinking is currently failing for some of the *.eln files. See #60 for
details of the error.

Long-term plan is to investigate native-comp's compilation options to
hopefully resolve this issue.
2021-11-10 22:11:13 +00:00
d054a17fc7 chore(release): 0.6.22 2021-11-10 01:56:19 +00:00
4030ceb9ca feat(patch): add support for posix-spawn patch from emacs-plus 2021-11-10 01:55:38 +00:00
ad1a7dd62c chore(release): 0.6.21 2021-10-27 21:45:43 +01:00
665e6addec Merge pull request #59 from jimeh/improve-native-comp-patch 2021-10-27 21:42:05 +01:00
7f2aba0e4e chore(builder): add hidden sign-files command for testing purposes
Useful when needing to sign singular files with same ease of signing a
Emacs.app bundle.
2021-10-27 21:38:29 +01:00
be326b22aa refactor(native-comp): improve native-comp env setup patch
Instead of patching emacs sources before building Emacs, we can use
site-start.el instead for a much cleaner way of setting LIBRARY_PATH
before the user config is loaded.
2021-10-27 21:38:29 +01:00
ef4f2ad930 refactor: use site-start.el instead of abusing subdirs.el
Set source-directory in site-start.el instead of subdirs.el which we
were basically abusing to execute Elisp before the user config is
loaded.
2021-10-27 21:38:28 +01:00
2293c87fc9 refactor(build): turn add_cli_helper function into a embedder class 2021-10-27 03:12:30 +01:00
97178bf77a chore(release): 0.6.20 2021-10-23 20:35:09 +01:00
591c39e629 fix(notarization): explicitly only copy *.c and *.h C source files
Previously we just copies the whole "src" directory from the workdir, it
turns out it has some binary files after a successful build, which
caused Apple's notarization process to fail.

As we actually only care about the *.c and *.h files from the "src"
directory, let's explicitly only copy those files.
2021-10-23 20:32:13 +01:00
d6c99f8c60 chore(release): 0.6.19 2021-10-23 17:26:50 +01:00
bcbd01778d fix(patch): resolve emacs-29 symlink patches to their real URL
Annoyingly, when downloading raw files from GitHub, symlinks do not
return the content of the symlink target, but instead it simply returns
the symlink target path (../emacs-28/fix-window-role.patch).

Hence we have to check the patch file content, and if it contains only a
single line of text, we assume it's a symlink, and resolve it relative
to the original download URL.
2021-10-23 17:24:45 +01:00
5602475542 chore(release): 0.6.18 2021-10-23 16:20:44 +01:00
67b8c5f397 feat(docs): embed C source files and set source-directory accordingly
This enables the original C source to be linked to when describing
entities which are written in C.
2021-10-23 16:18:05 +01:00
4dad5812fa fix(patches): correctly use emacs 28.x and 29.x patches
The master branch is now Emacs 29.x, and Emacs 28.x lives on the
"emacs-28" release branch.
2021-10-23 16:15:39 +01:00
65bdff0b6d chore(release): 0.6.17 2021-10-10 22:42:01 +01:00
a956dc1b42 feat(release): tweak GitHub release description 2021-10-10 22:41:26 +01:00
af23b63518 chore(release): 0.6.16 2021-10-10 18:44:41 +01:00
9c20f77fe3 Merge pull request #58 from jimeh/release-improvements 2021-10-10 18:42:37 +01:00
2e2f9bc98a feat(build): handle macOS Big Sur and later version number 2021-10-10 18:25:38 +01:00
d63cd545aa feat(cask): make cask template helpers more flexible
Helpers available in cask templates can now accept one or more strings
as input, and first asset filename that matches all given strings is
returned.
2021-10-10 18:25:38 +01:00
1bbfe5d3ea feat(plan): allow build plan to be output as YAML or JSON 2021-10-10 18:25:37 +01:00
b4c5184cef fix(release): publish arguments are not handled as asset files to upload 2021-10-10 18:25:37 +01:00
e7a991ef92 feat(release): force-replace existing asset files by default
The file-size check is obviously not a very reliable way to determine if
the local and remote files are different. Hence we now default to always
uploading all given asset files, replacing any existing ones with the
same file name.

The size check logic is still available via the --asset-size-check flag.
2021-10-10 18:25:37 +01:00
7118ed8560 feat(release): add description to GitHub Releases
The description includes links to the Emacs source repo used, the git
ref, commit, tarball download URL, and build log (GitHub Actions Run).
2021-10-10 18:25:36 +01:00
a71cbda511 chore(release): remove default release repository value
This should be explicitly provided via environment variable of command
line flag.
2021-10-10 18:25:36 +01:00
4c0eb37353 chore(debug): remove left-over debug print statement 2021-10-10 18:25:36 +01:00
b81e101ca7 style(lint): Renamed unused function arguments to _ 2021-10-10 18:25:35 +01:00
c760ffa25e Merge pull request #55 from puzza007/patch-1
Fix typo in log message
2021-08-30 10:11:58 +01:00
Paul Oliver
931e6ddf9e Fix typo in log message 2021-08-26 16:00:12 +12:00
f17f485b9f chore(release): 0.6.15 2021-08-05 02:38:15 +01:00
3622df550c fix(build): another --relink-eln-files flag fix
I probably should not be doing these kind of changes after midnight
as I get too lazy to test it as it takes like 20 minutes to do a build
locally :P
2021-08-05 02:37:01 +01:00
2a9707ec89 chore(release): 0.6.14 2021-08-05 01:20:13 +01:00
1fc7faac1f fix(build): silly typo 2021-08-05 01:19:39 +01:00
786d253df6 chore(plan): do not modify archive behavior based on plan disk image
This allows archive creation (or not) to still be controlled via the
--[no-]archive flag.
2021-08-05 00:21:17 +01:00
228ae0939c chore(release): 0.6.13 2021-08-04 23:59:58 +01:00
ac943c430c fix(native_comp): add option to enable/disable relinking *.eln files
Relinking the /usr/local/lib/gcc/10/libgcc_s.1.dylib shared library
within bundled *.eln files is still causing issues with code signing, so
I'm adding an option to toggle *.eln file re-linking on/off, with it on
by default.
2021-08-04 23:58:27 +01:00
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
66 changed files with 7748 additions and 383 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

89
.golangci.yml Normal file
View File

@@ -0,0 +1,89 @@
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
- source: "`yaml:"
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

@@ -3,6 +3,7 @@
brew 'autoconf'
brew 'coreutils'
brew 'curl'
brew 'dbus'
brew 'expat'
brew 'gcc'
brew 'gmp'
@@ -22,5 +23,7 @@ brew 'make'
brew 'ncurses'
brew 'nettle'
brew 'pkg-config'
brew 'sqlite'
brew 'texinfo'
brew 'tree-sitter'
brew 'zlib'

3
Brewfile.ci Normal file
View File

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

View File

@@ -2,6 +2,431 @@
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.39](https://github.com/jimeh/build-emacs-for-macos/compare/v0.6.38...v0.6.39) (2022-12-04)
### Features
* **deprecate:** posix-spawn patch is no longer supported ([c3d1969](https://github.com/jimeh/build-emacs-for-macos/commit/c3d19694e7e4d33d462c9917683c2d63f69002f5))
* **version:** correctly handle Emacs 30.x builds ([8b447b6](https://github.com/jimeh/build-emacs-for-macos/commit/8b447b6237fbbd94c4e72af8ee79969c7cfc9363))
### [0.6.38](https://github.com/jimeh/build-emacs-for-macos/compare/v0.6.37...v0.6.38) (2022-12-01)
### Features
* **patch:** add round-undecorated-frame from emacs-plus for 29.x ([48a512f](https://github.com/jimeh/build-emacs-for-macos/commit/48a512fbce79759caa987e2880585bd0bc937977))
* **patch:** add support for experimental poll patch from emacs-plus for 29.x ([a3530c0](https://github.com/jimeh/build-emacs-for-macos/commit/a3530c02e8260106f87d464e5cb398dcb2819460))
### [0.6.37](https://github.com/jimeh/build-emacs-for-macos/compare/v0.6.36...v0.6.37) (2022-12-01)
### Features
* **tree-sitter:** support new --with-tree-sitter configure flag ([0ba971e](https://github.com/jimeh/build-emacs-for-macos/commit/0ba971ef61a195c91e87aa381d5d3b044461b4f6))
### [0.6.36](https://github.com/jimeh/build-emacs-for-macos/compare/v0.6.35...v0.6.36) (2022-10-08)
### Bug Fixes
* **native-comp:** support new configure flag format ([1f2868d](https://github.com/jimeh/build-emacs-for-macos/commit/1f2868d4b3784e906665e9f3b6b9bba8fd72292f)), closes [#76](https://github.com/jimeh/build-emacs-for-macos/issues/76)
### [0.6.35](https://github.com/jimeh/build-emacs-for-macos/compare/v0.6.34...v0.6.35) (2022-08-10)
### Bug Fixes
* **native-comp:** compatibility with libgccjit 12 homebrew formula ([e0fd2b1](https://github.com/jimeh/build-emacs-for-macos/commit/e0fd2b16eb91ac5a98ed4ec31f4773ab22cbd470))
### [0.6.34](https://github.com/jimeh/build-emacs-for-macos/compare/v0.6.33...v0.6.34) (2022-07-27)
### Bug Fixes
* **emacs-28:** patch configure.ac to support latest libgccjit ([b582523](https://github.com/jimeh/build-emacs-for-macos/commit/b582523642ad4c5298f5a7890edd9b48c0433684)), closes [#72](https://github.com/jimeh/build-emacs-for-macos/issues/72)
### [0.6.33](https://github.com/jimeh/build-emacs-for-macos/compare/v0.6.32...v0.6.33) (2022-04-30)
### [0.6.32](https://github.com/jimeh/build-emacs-for-macos/compare/v0.6.31...v0.6.32) (2022-04-30)
### Features
* **dbus:** add flag to explicitly disable dbus support ([8ad3ff4](https://github.com/jimeh/build-emacs-for-macos/commit/8ad3ff4f53505408aa097527177032a1fd6008e0)), closes [#69](https://github.com/jimeh/build-emacs-for-macos/issues/69)
* **deps:** add sqlite brew dependency for Emacs 29.x ([a20a845](https://github.com/jimeh/build-emacs-for-macos/commit/a20a8456ab1e8de6357d5d121b9565ba65a6dd71))
### Bug Fixes
* **native-comp:** support libgccjit 11.3.0 ([e31f5aa](https://github.com/jimeh/build-emacs-for-macos/commit/e31f5aaf9355b674c2a86b8eda35f6513f344b72)), closes [#71](https://github.com/jimeh/build-emacs-for-macos/issues/71)
### [0.6.31](https://github.com/jimeh/build-emacs-for-macos/compare/v0.6.30...v0.6.31) (2022-02-25)
### Bug Fixes
* set source-directory correctly ([07e0e3d](https://github.com/jimeh/build-emacs-for-macos/commit/07e0e3dacddfbdb7a59aceaa2dc9cdf503ac2bcc)), closes [#68](https://github.com/jimeh/build-emacs-for-macos/issues/68)
### [0.6.30](https://github.com/jimeh/build-emacs-for-macos/compare/v0.6.29...v0.6.30) (2022-02-08)
### Bug Fixes
* **site-lisp:** add Homebrew's site-lisp directory to locallisppath ([cbd8cb2](https://github.com/jimeh/build-emacs-for-macos/commit/cbd8cb27b6ceff2e128c38cd1cc8f8380b9b4bfb))
### [0.6.29](https://github.com/jimeh/build-emacs-for-macos/compare/v0.6.28...v0.6.29) (2022-02-07)
### Features
* **cask:** add support for pretest builds ([084776d](https://github.com/jimeh/build-emacs-for-macos/commit/084776db6b7e61958088d7b2a2588e9889e60c21))
### [0.6.28](https://github.com/jimeh/build-emacs-for-macos/compare/v0.6.27...v0.6.28) (2022-01-15)
### Features
* **build:** add dbus dependency to enable support in Emacs builds ([68ef4c0](https://github.com/jimeh/build-emacs-for-macos/commit/68ef4c066c3fd1a7337198e8f773866088b4f481))
### [0.6.27](https://github.com/jimeh/build-emacs-for-macos/compare/v0.6.26...v0.6.27) (2021-12-05)
### Features
* **plan:** add support for pretest and release candidate builds ([743b10c](https://github.com/jimeh/build-emacs-for-macos/commit/743b10c751e146ec7569f39a475c20a0489955f4))
### [0.6.26](https://github.com/jimeh/build-emacs-for-macos/compare/v0.6.25...v0.6.26) (2021-11-27)
### Features
* **build:** re-link eln files by default again ([4ae288c](https://github.com/jimeh/build-emacs-for-macos/commit/4ae288cae34c5e1d291dad7b6b654fe37c4a221f))
* **native-comp:** no longer require gcc homebrew formula ([3bd78d1](https://github.com/jimeh/build-emacs-for-macos/commit/3bd78d130a5419a6530a7d30e271569e501870fb)), closes [#53](https://github.com/jimeh/build-emacs-for-macos/issues/53)
### Bug Fixes
* **embed:** relink shared libraries with [@rpath](https://github.com/rpath) instead of [@executable](https://github.com/executable)_path ([fb5362c](https://github.com/jimeh/build-emacs-for-macos/commit/fb5362ce183ce43e52afcc0fc721cf2145f9c85b))
### [0.6.25](https://github.com/jimeh/build-emacs-for-macos/compare/v0.6.24...v0.6.25) (2021-11-25)
### [0.6.24](https://github.com/jimeh/build-emacs-for-macos/compare/v0.6.23...v0.6.24) (2021-11-24)
### Bug Fixes
* **embedding:** embedding GCC fails when paths do not require sanitizing ([8a467b0](https://github.com/jimeh/build-emacs-for-macos/commit/8a467b0d43140f6956d53c27e2319ae1b572868c))
### [0.6.23](https://github.com/jimeh/build-emacs-for-macos/compare/v0.6.22...v0.6.23) (2021-11-10)
### Bug Fixes
* **build:** do not re-link eln files by default ([d338c13](https://github.com/jimeh/build-emacs-for-macos/commit/d338c136db8acc4378154cf66ed7db5462787602)), closes [#60](https://github.com/jimeh/build-emacs-for-macos/issues/60)
### [0.6.22](https://github.com/jimeh/build-emacs-for-macos/compare/v0.6.21...v0.6.22) (2021-11-10)
### Features
* **patch:** add support for posix-spawn patch from emacs-plus ([4030ceb](https://github.com/jimeh/build-emacs-for-macos/commit/4030ceb9cab6749af3c28161ac97caec90a8aed0))
### [0.6.21](https://github.com/jimeh/build-emacs-for-macos/compare/v0.6.20...v0.6.21) (2021-10-27)
### [0.6.20](https://github.com/jimeh/build-emacs-for-macos/compare/v0.6.19...v0.6.20) (2021-10-23)
### Bug Fixes
* **notarization:** explicitly only copy *.c and *.h C source files ([591c39e](https://github.com/jimeh/build-emacs-for-macos/commit/591c39e629c9556adcf296cd5c15dd0b17c4d986))
### [0.6.19](https://github.com/jimeh/build-emacs-for-macos/compare/v0.6.18...v0.6.19) (2021-10-23)
### Bug Fixes
* **patch:** resolve emacs-29 symlink patches to their real URL ([bcbd017](https://github.com/jimeh/build-emacs-for-macos/commit/bcbd01778d416b99205c51f348a543489889f66d))
### [0.6.18](https://github.com/jimeh/build-emacs-for-macos/compare/v0.6.17...v0.6.18) (2021-10-23)
### Features
* **docs:** embed C source files and set source-directory accordingly ([67b8c5f](https://github.com/jimeh/build-emacs-for-macos/commit/67b8c5f3974e178e31519846d46af82d9770ad6e))
### Bug Fixes
* **patches:** correctly use emacs 28.x and 29.x patches ([4dad581](https://github.com/jimeh/build-emacs-for-macos/commit/4dad5812fa2b67adc7262a778829013995a904bc))
### [0.6.17](https://github.com/jimeh/build-emacs-for-macos/compare/v0.6.16...v0.6.17) (2021-10-10)
### Features
* **release:** tweak GitHub release description ([a956dc1](https://github.com/jimeh/build-emacs-for-macos/commit/a956dc1b42b2648e2663f5b48c72d7428fe75f19))
### [0.6.16](https://github.com/jimeh/build-emacs-for-macos/compare/v0.6.15...v0.6.16) (2021-10-10)
### Features
* **build:** handle macOS Big Sur and later version number ([2e2f9bc](https://github.com/jimeh/build-emacs-for-macos/commit/2e2f9bc98acdc972a22add3d1015bd80cad20b41))
* **cask:** make cask template helpers more flexible ([d63cd54](https://github.com/jimeh/build-emacs-for-macos/commit/d63cd545aab3a35e0cbbbcabd862525acbc414b8))
* **plan:** allow build plan to be output as YAML or JSON ([1bbfe5d](https://github.com/jimeh/build-emacs-for-macos/commit/1bbfe5d3ea810215b417e5b80d2902f03d68f366))
* **release:** add description to GitHub Releases ([7118ed8](https://github.com/jimeh/build-emacs-for-macos/commit/7118ed856053de06ddcdfba2a2d6fa40f58c17ab))
* **release:** force-replace existing asset files by default ([e7a991e](https://github.com/jimeh/build-emacs-for-macos/commit/e7a991ef92a5c546106a8badaf9c60247a1397b5))
### Bug Fixes
* **release:** publish arguments are not handled as asset files to upload ([b4c5184](https://github.com/jimeh/build-emacs-for-macos/commit/b4c5184cefe43fdc54b1ad5c8a1970f104137644))
### [0.6.15](https://github.com/jimeh/build-emacs-for-macos/compare/v0.6.14...v0.6.15) (2021-08-05)
### Bug Fixes
* **build:** another --relink-eln-files flag fix ([3622df5](https://github.com/jimeh/build-emacs-for-macos/commit/3622df550c72fc9da70235005239b278b5822cf6))
### [0.6.14](https://github.com/jimeh/build-emacs-for-macos/compare/v0.6.13...v0.6.14) (2021-08-05)
### Bug Fixes
* **build:** silly typo ([1fc7faa](https://github.com/jimeh/build-emacs-for-macos/commit/1fc7faac1f040466fa4474a873d2290273780ee2))
### [0.6.13](https://github.com/jimeh/build-emacs-for-macos/compare/v0.6.12...v0.6.13) (2021-08-04)
### Bug Fixes
* **native_comp:** add option to enable/disable relinking *.eln files ([ac943c4](https://github.com/jimeh/build-emacs-for-macos/commit/ac943c430c58e0761ac44e8d25d4d55a461d01a2))
### [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)

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))

160
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.
@@ -16,21 +17,10 @@ Use this script at your own risk.
built from the `master` branch. This script allows you to choose any branch,
tag, or git ref you want.
## Status
## Binary Builds
As of writing (2020-10-04) it works for me on my machine. Your luck may vary.
I have successfully built:
- `emacs-27.1` release git tag
- `master` branch (Emacs 28.x)
- `feature/native-comp` branch (Emacs 28.x)
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
Nightly and stable binary builds produced with this build script are available
from [jimeh/emacs-builds](https://github.com/jimeh/emacs-builds).
## Limitations
@@ -40,9 +30,9 @@ The build produced does have some limitations:
application will be that of the machine it was built on.
- The minimum required macOS version of the built application will be the same
as that of the machine it was built on.
- The application is not signed, so running it on machines other than the one
that built the application will yield warnings. If you want to make a signed
Emacs.app, google is you friend for finding signing instructions.
- The application is not signed automatically, but the CLI tool used to sign the
nightly builds is available. Run `go run ./cmd/emacs-builder package --help`
for details. More detailed instructions will come soon.
## Requirements
@@ -60,6 +50,26 @@ The build produced does have some limitations:
brew install ruby
```
## Status
As of writing (2021-11-27) it works for me on my machine and for the nightly
builds in [jimeh/emacs-builds](https://github.com/jimeh/emacs-builds). Your luck
may vary.
I have successfully built:
- `emacs-28` release branch
- `master` branch (Emacs 29.x)
For reference, my machine is:
- 13-inch MacBook Pro (2020), 10th-gen 2.3 GHz Quad-Core Intel Core i7 (4c/8t)
- macOS Monterey 12.0.1 (21A559)
- Xcode 13.1 (13A1030d)
Nightly builds are built with GitHub Actions on GitHub-hosted runners, using
`macos-10.15`.
## Usage
```
@@ -71,16 +81,24 @@ 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-]relink-eln-files Enable/disable re-linking shared libraries in bundled *.eln files (default: enabled)
--[no-]rsvg Enable/disable SVG image support via librsvg (default: enabled)
--no-titlebar Apply no-titlebar patch (default: disabled)
--posix-spawn Apply posix-spawn 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-build-dir
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 +112,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 +130,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 +213,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.5
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.5 h1:mVWtqTzV03W0avJqmqjk9M0qls3TDUXfc9ETJaPIOWY=
github.com/mitchellh/gon v0.2.5/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() {
"$(command -v greadlink || command -v readlink)" "$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,62 +0,0 @@
diff --git a/lisp/emacs-lisp/comp.el b/lisp/emacs-lisp/comp.el
index 25e2de9..bcedd31 100644
--- a/lisp/emacs-lisp/comp.el
+++ b/lisp/emacs-lisp/comp.el
@@ -2801,6 +2801,57 @@ queued with LOAD %"
(comp-run-async-workers)
(message "Compilation started."))))
+;;;###autoload
+(defun native-compile-setup-environment-variables (&rest _args)
+ "Ensure LIBRARY_PATH is set correctly when libgccjit is bundled."
+ (when (and (eq system-type 'darwin)
+ (string-match-p "\.app\/Contents\/MacOS\/?$"
+ invocation-directory))
+ (let* ((library-path-env (getenv "LIBRARY_PATH"))
+ (devtools-dir
+ "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/lib")
+ (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 (file-directory-p devtools-dir)
+ (list devtools-dir) (list))
+ (if library-path-env (list library-path-env) (list)))))
+
+ (when (and gcc-dir darwin-dir)
+ (setenv "LIBRARY_PATH" (mapconcat 'identity lib-paths ":")))))
+
+ ;; Remove advice, as it only needs to run once.
+ (advice-remove 'native-compile
+ 'native-compile-setup-environment-variables)
+ (advice-remove 'comp--native-compile
+ 'native-compile-setup-environment-variables)
+ (advice-remove 'native-compile-async
+ 'native-compile-setup-environment-variables)
+ (advice-remove 'native--compile-async
+ 'native-compile-setup-environment-variables))
+
+;; Ensure environment setup runs before any native compilation.
+(advice-add 'native-compile :before
+ 'native-compile-setup-environment-variables)
+(advice-add 'comp--native-compile :before
+ 'native-compile-setup-environment-variables)
+(advice-add 'native-compile-async :before
+ 'native-compile-setup-environment-variables)
+(advice-add 'native--compile-async :before
+ 'native-compile-setup-environment-variables)
+
(provide 'comp)
;;; comp.el ends here

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"`
}

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

@@ -0,0 +1,66 @@
package cask
import (
"sort"
"strings"
)
type ReleaseInfo struct {
Name string
Version string
Assets map[string]*ReleaseAsset
}
func (s *ReleaseInfo) Asset(needles ...string) *ReleaseAsset {
if len(needles) == 1 {
if a, ok := s.Assets[needles[0]]; 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 {
for _, needle := range needles {
if !strings.Contains(a.Filename, needle) {
continue
}
}
return a
}
return nil
}
func (s *ReleaseInfo) DownloadURL(needles ...string) string {
a := s.Asset(needles...)
if a == nil {
return ""
}
return a.DownloadURL
}
func (s *ReleaseInfo) SHA256(needles ...string) string {
a := s.Asset(needles...)
if a == nil {
return ""
}
return a.SHA256
}
type ReleaseAsset struct {
Filename string
DownloadURL string
SHA256 string
}

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

@@ -0,0 +1,491 @@
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)
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,
_ *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)
}

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

@@ -0,0 +1,96 @@
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(),
signFilesCmd(),
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, _ *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
}

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

@@ -0,0 +1,146 @@
package cli
import (
"fmt"
"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: "format",
Aliases: []string{"f"},
Usage: "output format of build plan (yaml or json)",
Value: "yaml",
},
&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
}
format := c.String("format")
var plan string
switch format {
case "yaml", "yml":
format = "yaml"
plan, err = p.YAML()
case "json":
format = "json"
plan, err = p.JSON()
default:
err = fmt.Errorf("--format must be yaml or json")
}
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", format, plan)
out, err = os.Create(f)
if err != nil {
return err
}
defer out.Close()
}
_, err = out.WriteString(plan)
if err != nil {
return err
}
return nil
}

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

@@ -0,0 +1,277 @@
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: "",
},
&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,
_ *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: "",
},
&cli2.BoolFlag{
Name: "asset-size-check",
Usage: "Do not replace existing asset files if local and " +
"remote file sizes match.",
Value: false,
},
},
Action: releaseActionWrapper(releasePublishAction),
}
}
func releasePublishAction(
c *cli2.Context,
_ *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(),
AssetSizeCheck: c.Bool("asset-size-check"),
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 c.Args().Len() > 0 {
rlsOpts.AssetFiles = c.Args().Slice()
}
if rOpts.Plan != nil {
rlsOpts.Source = rOpts.Plan.Source
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
}
}
// Set asset files based on plan if no file arguments were given.
if len(rlsOpts.AssetFiles) == 0 && 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,
_ *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)
}

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

@@ -0,0 +1,160 @@
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)
}
func signFilesCmd() *cli2.Command {
signCmd := signCmd()
var flags []cli2.Flag
for _, f := range signCmd.Flags {
n := f.Names()
if len(n) > 0 && n[0] == "plan" {
continue
}
flags = append(flags, f)
}
return &cli2.Command{
Name: "sign-files",
Usage: "sign files with codesign",
ArgsUsage: "<file> [<file>...]",
Hidden: true,
Flags: flags,
Action: actionWrapper(signFilesAction),
}
}
func signFilesAction(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
}
return sign.Files(c.Context, c.Args().Slice(), 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" json:"sha"`
Date *time.Time `yaml:"date" json:"date"`
Author string `yaml:"author" json:"author"`
Committer string `yaml:"committer" json:"committer"`
Message string `yaml:"message" json:"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)
}
}

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

@@ -0,0 +1,50 @@
package osinfo
import (
"os/exec"
"strconv"
"strings"
)
type OSInfo struct {
Name string `yaml:"name" json:"name"`
Version string `yaml:"version" json:"version"`
Arch string `yaml:"arch" json:"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
}
// DistinctVersion returns macOS version down to a distinct "major"
// version. For macOS 10.x, this will include the first two numeric parts of the
// version (10.15), while for 11.x and later, the first numeric part is enough
// (11).
func (s *OSInfo) DistinctVersion() string {
parts := strings.Split(s.Version, ".")
if n, _ := strconv.Atoi(parts[0]); n >= 11 {
return parts[0]
}
max := len(parts)
if max > 2 {
max = 2
}
return strings.Join(parts[0:max], ".")
}

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

@@ -0,0 +1,183 @@
package plan
import (
"context"
"fmt"
"io"
"regexp"
"strconv"
"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"
"github.com/jimeh/build-emacs-for-macos/pkg/sanitize"
"github.com/jimeh/build-emacs-for-macos/pkg/source"
)
var gitTagMatcher = regexp.MustCompile(
`^emacs(-.*)?-((\d+\.\d+)(?:\.(\d+))?(-rc\d+)?(.+)?)$`,
)
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) { //nolint:funlen
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
}
absoluteVersion := fmt.Sprintf(
"%s.%s.%s",
commitInfo.DateString(),
commitInfo.ShortSHA(),
sanitize.String(opts.Ref),
)
version, channel, err := parseGitRef(opts.Ref)
if err != nil {
return nil, err
}
var releaseName string
switch channel {
case release.Stable, release.RC:
releaseName = "Emacs-" + version
case release.Pretest:
version += "-pretest"
absoluteVersion += "-pretest"
releaseName = "Emacs-" + version
default:
version = absoluteVersion
releaseName = "Emacs." + version
}
buildName := fmt.Sprintf(
"Emacs.%s.%s.%s",
absoluteVersion,
sanitize.String(osInfo.Name+"-"+osInfo.DistinctVersion()),
sanitize.String(osInfo.Arch),
)
diskImage := buildName + ".dmg"
plan := &Plan{
Build: &Build{
Name: buildName,
},
Source: &source.Source{
Ref: opts.Ref,
Repository: repo,
Commit: commitInfo,
Tarball: &source.Tarball{
URL: repo.TarballURL(commitInfo.SHA),
},
},
OS: osInfo,
Release: &Release{
Name: releaseName,
Prerelease: channel != release.Stable,
Channel: channel,
},
Output: &Output{
Directory: opts.OutputDir,
DiskImage: diskImage,
},
}
if opts.TestBuild != "" {
testName := sanitize.String(opts.TestBuild)
plan.Build.Name += ".test." + testName
plan.Release.Title = "Test Builds (" + testName + ")"
plan.Release.Name = "test-builds"
plan.Release.Prerelease = false
plan.Release.Draft = true
if opts.TestBuildType == Prerelease {
plan.Release.Prerelease = true
plan.Release.Draft = false
}
index := strings.LastIndex(diskImage, ".")
plan.Output.DiskImage = diskImage[:index] + ".test." +
testName + diskImage[index:]
}
return plan, nil
}
func parseGitRef(ref string) (string, release.Channel, error) {
m := gitTagMatcher.FindStringSubmatch(ref)
if len(m) == 0 {
return "", release.Nightly, nil
}
if strings.Contains(m[1], "pretest") {
return m[2], release.Pretest, nil
}
if m[4] != "" {
n, err := strconv.Atoi(m[4])
if err != nil {
return "", "", err
}
if n >= 90 {
return m[2], release.Pretest, nil
}
}
if strings.HasPrefix(m[5], "-rc") {
return m[2], release.RC, nil
}
if m[2] == m[3] {
return m[2], release.Stable, nil
}
return "", "", nil
}

421
pkg/plan/create_test.go Normal file
View File

@@ -0,0 +1,421 @@
package plan
import (
"testing"
"github.com/jimeh/build-emacs-for-macos/pkg/release"
"github.com/stretchr/testify/assert"
)
func Test_parseGitRef(t *testing.T) {
t.Parallel()
type args struct {
ref string
}
type want struct {
version string
channel release.Channel
err string
}
tests := []struct {
name string
args args
want want
}{
{
name: "master",
args: args{ref: "master"},
want: want{version: "", channel: release.Nightly, err: ""},
},
{
name: "emacs-28",
args: args{ref: "emacs-28"},
want: want{version: "", channel: release.Nightly, err: ""},
},
{
name: "emacs-27",
args: args{ref: "emacs-27"},
want: want{version: "", channel: release.Nightly, err: ""},
},
{
name: "emacs-26",
args: args{ref: "emacs-26"},
want: want{version: "", channel: release.Nightly, err: ""},
},
{
name: "emacs-24",
args: args{ref: "emacs-24"},
want: want{version: "", channel: release.Nightly, err: ""},
},
{
name: "feature/native-comp",
args: args{ref: "feature/native-comp"},
want: want{version: "", channel: release.Nightly, err: ""},
},
{
name: "feature/pgtk",
args: args{ref: "feature/pgtk"},
want: want{version: "", channel: release.Nightly, err: ""},
},
{
name: "emacs-19.34",
args: args{ref: "emacs-19.34"},
want: want{version: "19.34", channel: release.Stable, err: ""},
},
{
name: "emacs-20.4",
args: args{ref: "emacs-20.4"},
want: want{version: "20.4", channel: release.Stable, err: ""},
},
{
name: "emacs-22.3",
args: args{ref: "emacs-22.3"},
want: want{version: "22.3", channel: release.Stable, err: ""},
},
{
name: "emacs-23.4",
args: args{ref: "emacs-23.4"},
want: want{version: "23.4", channel: release.Stable, err: ""},
},
{
name: "emacs-24.0.97",
args: args{ref: "emacs-24.0.97"},
want: want{version: "24.0.97", channel: release.Pretest, err: ""},
},
{
name: "emacs-24.2",
args: args{ref: "emacs-24.2"},
want: want{version: "24.2", channel: release.Stable, err: ""},
},
{
name: "emacs-24.2.90",
args: args{ref: "emacs-24.2.90"},
want: want{version: "24.2.90", channel: release.Pretest, err: ""},
},
{
name: "emacs-24.2.93",
args: args{ref: "emacs-24.2.93"},
want: want{version: "24.2.93", channel: release.Pretest, err: ""},
},
{
name: "emacs-24.3",
args: args{ref: "emacs-24.3"},
want: want{version: "24.3", channel: release.Stable, err: ""},
},
{
name: "emacs-24.3-rc1",
args: args{ref: "emacs-24.3-rc1"},
want: want{version: "24.3-rc1", channel: release.RC, err: ""},
},
{
name: "emacs-24.3.90",
args: args{ref: "emacs-24.3.90"},
want: want{version: "24.3.90", channel: release.Pretest, err: ""},
},
{
name: "emacs-24.3.94",
args: args{ref: "emacs-24.3.94"},
want: want{version: "24.3.94", channel: release.Pretest, err: ""},
},
{
name: "emacs-24.4",
args: args{ref: "emacs-24.4"},
want: want{version: "24.4", channel: release.Stable, err: ""},
},
{
name: "emacs-24.4-rc1",
args: args{ref: "emacs-24.4-rc1"},
want: want{version: "24.4-rc1", channel: release.RC, err: ""},
},
{
name: "emacs-24.4.90",
args: args{ref: "emacs-24.4.90"},
want: want{version: "24.4.90", channel: release.Pretest, err: ""},
},
{
name: "emacs-24.4.91",
args: args{ref: "emacs-24.4.91"},
want: want{version: "24.4.91", channel: release.Pretest, err: ""},
},
{
name: "emacs-24.5",
args: args{ref: "emacs-24.5"},
want: want{version: "24.5", channel: release.Stable, err: ""},
},
{
name: "emacs-24.5-rc1",
args: args{ref: "emacs-24.5-rc1"},
want: want{version: "24.5-rc1", channel: release.RC, err: ""},
},
{
name: "emacs-24.5-rc3",
args: args{ref: "emacs-24.5-rc3"},
want: want{version: "24.5-rc3", channel: release.RC, err: ""},
},
{
name: "emacs-24.5-rc3-fixed",
args: args{ref: "emacs-24.5-rc3-fixed"},
want: want{version: "24.5-rc3-fixed", channel: release.RC, err: ""},
},
{
name: "emacs-25.0.90",
args: args{ref: "emacs-25.0.90"},
want: want{version: "25.0.90", channel: release.Pretest, err: ""},
},
{
name: "emacs-25.0.95",
args: args{ref: "emacs-25.0.95"},
want: want{version: "25.0.95", channel: release.Pretest, err: ""},
},
{
name: "emacs-25.1",
args: args{ref: "emacs-25.1"},
want: want{version: "25.1", channel: release.Stable, err: ""},
},
{
name: "emacs-25.1-rc1",
args: args{ref: "emacs-25.1-rc1"},
want: want{version: "25.1-rc1", channel: release.RC, err: ""},
},
{
name: "emacs-25.1-rc2",
args: args{ref: "emacs-25.1-rc2"},
want: want{version: "25.1-rc2", channel: release.RC, err: ""},
},
{
name: "emacs-25.1.90",
args: args{ref: "emacs-25.1.90"},
want: want{version: "25.1.90", channel: release.Pretest, err: ""},
},
{
name: "emacs-25.1.91",
args: args{ref: "emacs-25.1.91"},
want: want{version: "25.1.91", channel: release.Pretest, err: ""},
},
{
name: "emacs-25.2",
args: args{ref: "emacs-25.2"},
want: want{version: "25.2", channel: release.Stable, err: ""},
},
{
name: "emacs-25.2-rc1",
args: args{ref: "emacs-25.2-rc1"},
want: want{version: "25.2-rc1", channel: release.RC, err: ""},
},
{
name: "emacs-25.2-rc2",
args: args{ref: "emacs-25.2-rc2"},
want: want{version: "25.2-rc2", channel: release.RC, err: ""},
},
{
name: "emacs-26.0.90",
args: args{ref: "emacs-26.0.90"},
want: want{version: "26.0.90", channel: release.Pretest, err: ""},
},
{
name: "emacs-26.0.91",
args: args{ref: "emacs-26.0.91"},
want: want{version: "26.0.91", channel: release.Pretest, err: ""},
},
{
name: "emacs-26.1",
args: args{ref: "emacs-26.1"},
want: want{version: "26.1", channel: release.Stable, err: ""},
},
{
name: "emacs-26.1-rc1",
args: args{ref: "emacs-26.1-rc1"},
want: want{version: "26.1-rc1", channel: release.RC, err: ""},
},
{
name: "emacs-26.1.90",
args: args{ref: "emacs-26.1.90"},
want: want{version: "26.1.90", channel: release.Pretest, err: ""},
},
{
name: "emacs-26.1.92",
args: args{ref: "emacs-26.1.92"},
want: want{version: "26.1.92", channel: release.Pretest, err: ""},
},
{
name: "emacs-26.2",
args: args{ref: "emacs-26.2"},
want: want{version: "26.2", channel: release.Stable, err: ""},
},
{
name: "emacs-26.2.90",
args: args{ref: "emacs-26.2.90"},
want: want{version: "26.2.90", channel: release.Pretest, err: ""},
},
{
name: "emacs-26.3",
args: args{ref: "emacs-26.3"},
want: want{version: "26.3", channel: release.Stable, err: ""},
},
{
name: "emacs-26.3-rc1",
args: args{ref: "emacs-26.3-rc1"},
want: want{version: "26.3-rc1", channel: release.RC, err: ""},
},
{
name: "emacs-27.0.90",
args: args{ref: "emacs-27.0.90"},
want: want{version: "27.0.90", channel: release.Pretest, err: ""},
},
{
name: "emacs-27.0.91",
args: args{ref: "emacs-27.0.91"},
want: want{version: "27.0.91", channel: release.Pretest, err: ""},
},
{
name: "emacs-27.1",
args: args{ref: "emacs-27.1"},
want: want{version: "27.1", channel: release.Stable, err: ""},
},
{
name: "emacs-27.1-rc1",
args: args{ref: "emacs-27.1-rc1"},
want: want{version: "27.1-rc1", channel: release.RC, err: ""},
},
{
name: "emacs-27.1-rc2",
args: args{ref: "emacs-27.1-rc2"},
want: want{version: "27.1-rc2", channel: release.RC, err: ""},
},
{
name: "emacs-27.1.90",
args: args{ref: "emacs-27.1.90"},
want: want{version: "27.1.90", channel: release.Pretest, err: ""},
},
{
name: "emacs-27.1.91",
args: args{ref: "emacs-27.1.91"},
want: want{version: "27.1.91", channel: release.Pretest, err: ""},
},
{
name: "emacs-27.2",
args: args{ref: "emacs-27.2"},
want: want{version: "27.2", channel: release.Stable, err: ""},
},
{
name: "emacs-27.2-rc1",
args: args{ref: "emacs-27.2-rc1"},
want: want{version: "27.2-rc1", channel: release.RC, err: ""},
},
{
name: "emacs-27.2-rc2",
args: args{ref: "emacs-27.2-rc2"},
want: want{version: "27.2-rc2", channel: release.RC, err: ""},
},
{
name: "emacs-28.0.90",
args: args{ref: "emacs-28.0.90"},
want: want{version: "28.0.90", channel: release.Pretest, err: ""},
},
{
name: "emacs-pretest-21.0.100",
args: args{ref: "emacs-pretest-21.0.100"},
want: want{version: "21.0.100", channel: release.Pretest, err: ""},
},
{
name: "emacs-pretest-21.0.106",
args: args{ref: "emacs-pretest-21.0.106"},
want: want{version: "21.0.106", channel: release.Pretest, err: ""},
},
{
name: "emacs-pretest-21.0.90",
args: args{ref: "emacs-pretest-21.0.90"},
want: want{version: "21.0.90", channel: release.Pretest, err: ""},
},
{
name: "emacs-pretest-21.0.99",
args: args{ref: "emacs-pretest-21.0.99"},
want: want{version: "21.0.99", channel: release.Pretest, err: ""},
},
{
name: "emacs-pretest-22.0.90",
args: args{ref: "emacs-pretest-22.0.90"},
want: want{version: "22.0.90", channel: release.Pretest, err: ""},
},
{
name: "emacs-pretest-22.0.99",
args: args{ref: "emacs-pretest-22.0.99"},
want: want{version: "22.0.99", channel: release.Pretest, err: ""},
},
{
name: "emacs-pretest-22.0.990",
args: args{ref: "emacs-pretest-22.0.990"},
want: want{version: "22.0.990", channel: release.Pretest, err: ""},
},
{
name: "emacs-pretest-22.1.90",
args: args{ref: "emacs-pretest-22.1.90"},
want: want{version: "22.1.90", channel: release.Pretest, err: ""},
},
{
name: "emacs-pretest-22.2.90",
args: args{ref: "emacs-pretest-22.2.90"},
want: want{version: "22.2.90", channel: release.Pretest, err: ""},
},
{
name: "emacs-pretest-23.0.90",
args: args{ref: "emacs-pretest-23.0.90"},
want: want{version: "23.0.90", channel: release.Pretest, err: ""},
},
{
name: "emacs-pretest-23.1.90",
args: args{ref: "emacs-pretest-23.1.90"},
want: want{version: "23.1.90", channel: release.Pretest, err: ""},
},
{
name: "emacs-pretest-23.2.90",
args: args{ref: "emacs-pretest-23.2.90"},
want: want{version: "23.2.90", channel: release.Pretest, err: ""},
},
{
name: "emacs-pretest-23.2.91",
args: args{ref: "emacs-pretest-23.2.91"},
want: want{version: "23.2.91", channel: release.Pretest, err: ""},
},
{
name: "emacs-pretest-23.2.93",
args: args{ref: "emacs-pretest-23.2.93"},
want: want{version: "23.2.93", channel: release.Pretest, err: ""},
},
{
name: "emacs-pretest-23.2.93.1",
args: args{ref: "emacs-pretest-23.2.93.1"},
want: want{version: "23.2.93.1", channel: release.Pretest, err: ""},
},
{
name: "emacs-pretest-23.3.90",
args: args{ref: "emacs-pretest-23.3.90"},
want: want{version: "23.3.90", channel: release.Pretest, err: ""},
},
{
name: "emacs-pretest-24.0.05",
args: args{ref: "emacs-pretest-24.0.05"},
want: want{version: "24.0.05", channel: release.Pretest, err: ""},
},
{
name: "emacs-pretest-24.0.90",
args: args{ref: "emacs-pretest-24.0.90"},
want: want{version: "24.0.90", channel: release.Pretest, err: ""},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, gotChannel, err := parseGitRef(tt.args.ref)
assert.Equal(t, tt.want.version, got)
assert.Equal(t, tt.want.channel, gotChannel)
if tt.want.err == "" {
assert.NoError(t, err)
} else {
assert.EqualError(t, err, tt.want.err)
}
})
}
}

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

@@ -0,0 +1,92 @@
package plan
import (
"bytes"
"encoding/json"
"io"
"os"
"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/source"
"gopkg.in/yaml.v3"
)
type Plan struct {
Build *Build `yaml:"build,omitempty" json:"build,omitempty"`
Source *source.Source `yaml:"source,omitempty" json:"source,omitempty"`
OS *osinfo.OSInfo `yaml:"os,omitempty" json:"os,omitempty"`
Release *Release `yaml:"release,omitempty" json:"release,omitempty"`
Output *Output `yaml:"output,omitempty" json:"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
}
// WriteJSON writes plan in JSON format to given io.Writer.
func (s *Plan) WriteJSON(w io.Writer) error {
enc := json.NewEncoder(w)
enc.SetIndent("", " ")
return enc.Encode(s)
}
// JSON returns plan in JSON format.
func (s *Plan) JSON() (string, error) {
var buf bytes.Buffer
err := s.WriteJSON(&buf)
if err != nil {
return "", err
}
return buf.String(), nil
}
type Build struct {
Name string `yaml:"name,omitempty" json:"name,omitempty"`
}
type Release struct {
Name string `yaml:"name" json:"name"`
Title string `yaml:"title,omitempty" json:"title,omitempty"`
Draft bool `yaml:"draft,omitempty" json:"draft,omitempty"`
Prerelease bool `yaml:"prerelease,omitempty" json:"prerelease,omitempty"`
Channel release.Channel `yaml:"channel,omitempty" json:"channel,omitempty"`
}
type Output struct {
Directory string `yaml:"directory,omitempty" json:"directory,omitempty"`
DiskImage string `yaml:"disk_image,omitempty" json:"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
}

11
pkg/release/channel.go Normal file
View File

@@ -0,0 +1,11 @@
package release
type Channel string
// Release channels
const (
Stable Channel = "stable"
RC Channel = "release-candidate"
Pretest Channel = "pretest"
Nightly Channel = "nightly"
)

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-"),
)
}

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

@@ -0,0 +1,299 @@
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"
"github.com/jimeh/build-emacs-for-macos/pkg/source"
)
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
// Source contains the source used to build the asset files. When set a
// release body/description text will be generated based on source commit
// details.
Source *source.Source
// AssetFiles is a list of files which must all exist in the release for
// the check to pass.
AssetFiles []string
// AssetSizeCheck causes a file size check for any existing asset files on a
// release which have the same filename as a asset we want to upload. If the
// size of the local and remote files are the same, the existing asset file
// is left in place. When this is false, given asset files will always be
// uploaded, replacing any asset files with the same filename.
AssetSizeCheck bool
// GitHubToken is the OAuth token used to talk to the GitHub API.
GithubToken string
}
// 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
body := ""
if opts.Source != nil {
body, err = releaseBody(opts)
if err != nil {
return err
}
logger.Debug("rendered release body", "content", body)
}
created := false
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
}
created = true
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),
Body: &body,
},
)
if err != nil {
return err
}
}
err = uploadReleaseAssets(ctx, gh, release, files, opts)
if err != nil {
return err
}
changed := false
if release.GetName() != name {
release.Name = &name
changed = true
}
if body != "" && release.GetBody() != body {
release.Body = &body
changed = true
}
if release.GetDraft() != draft {
release.Draft = &draft
changed = true
}
if !draft && release.GetPrerelease() != prerelease {
release.Prerelease = &prerelease
changed = true
}
if changed {
logger.Info("updating release attributes", "url", release.GetHTMLURL())
release, _, err = gh.Repositories.EditRelease(
ctx, opts.Repository.Owner(), opts.Repository.Name(),
release.GetID(), release,
)
if err != nil {
return err
}
}
if created {
logger.Info("release created", "url", release.GetHTMLURL())
} else {
logger.Info("release updated", "url", release.GetHTMLURL())
}
return nil
}
func uploadReleaseAssets(
ctx context.Context,
gh *github.Client,
release *github.RepositoryRelease,
fileNames []string,
opts *PublishOptions,
) error {
logger := hclog.FromContext(ctx).Named("release")
for _, fileName := range fileNames {
logger.Debug("processing asset", "file", filepath.Base(fileName))
fileIO, err := os.Open(fileName)
if err != nil {
return err
}
defer fileIO.Close()
fileInfo, err := fileIO.Stat()
if err != nil {
return err
}
fileBaseName := filepath.Base(fileName)
assetExists := false
for _, a := range release.Assets {
if a.GetName() != fileBaseName {
continue
}
if opts.AssetSizeCheck && 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 {
logger.Info(
"deleting existing asset", "file", fileBaseName,
)
_, err = gh.Repositories.DeleteReleaseAsset(
ctx, opts.Repository.Owner(), opts.Repository.Name(),
a.GetID(),
)
if err != nil {
return err
}
}
}
if !assetExists {
logger.Info("uploading asset",
"file", fileBaseName,
"size", byteCountIEC(fileInfo.Size()),
)
_, _, err = gh.Repositories.UploadReleaseAsset(
ctx, opts.Repository.Owner(), opts.Repository.Name(),
release.GetID(),
&github.UploadOptions{Name: fileBaseName},
fileIO,
)
if err != nil {
return err
}
}
}
return nil
}
func publishFileList(files []string) ([]string, error) {
results := map[string]struct{}{}
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)
}
results[file] = struct{}{}
sumFile := file + ".sha256"
_, err = os.Stat(sumFile)
if err != nil {
if os.IsNotExist(err) {
continue
}
return nil, err
}
results[sumFile] = struct{}{}
}
var output []string
for f := range results {
output = append(output, f)
}
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
}

View File

@@ -0,0 +1,78 @@
package release
import (
"bytes"
"os"
"strings"
"text/template"
)
var tplFuncs = template.FuncMap{
"indent": func(n int, s string) string {
pad := strings.Repeat(" ", n)
return pad + strings.ReplaceAll(s, "\n", "\n"+pad)
},
}
var bodyTpl = template.Must(template.New("body").Funcs(tplFuncs).Parse(`
{{- $t := "` + "`" + `" -}}
### Build Details
{{ with .SourceURL -}}
- Source: {{ . }}
{{- end }}
{{- if .CommitURL }}
- Commit: {{ .CommitURL }}
{{- if .CommitSHA }} ({{ $t }}{{ .CommitSHA }}{{ $t }}){{ end }}
{{- end }}
{{- with .TarballURL }}
- Tarball: {{ . }}
{{- end }}
{{- with .BuildLogURL }}
- Build Log: {{ . }} (available for 90 days)
{{- end }}`,
))
type bodyData struct {
SourceURL string
CommitSHA string
CommitURL string
BuildLogURL string
TarballURL string
}
func releaseBody(opts *PublishOptions) (string, error) {
src := opts.Source
if src.Repository == nil || src.Commit == nil {
return "", nil
}
data := &bodyData{
SourceURL: src.Repository.TreeURL(src.Ref),
CommitSHA: src.Commit.SHA,
CommitURL: src.Repository.CommitURL(src.Commit.SHA),
TarballURL: src.Repository.TarballURL(src.Commit.SHA),
}
// If available, use the exact value from the build plan.
if src.Tarball != nil {
data.TarballURL = src.Tarball.URL
}
// If running within GitHub Actions, provide link to build log.
if opts.Repository != nil {
if id := os.Getenv("GITHUB_RUN_ID"); id != "" {
data.BuildLogURL = opts.Repository.ActionRunURL(id)
}
}
var buf bytes.Buffer
err := bodyTpl.Execute(&buf, data)
if err != nil {
return "", err
}
return buf.String(), nil
}

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

@@ -0,0 +1,44 @@
package release
import (
"errors"
"fmt"
"regexp"
"strings"
)
// 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]+)?$`)
stableGitRef = regexp.MustCompile(`^emacs-(\d+\.\d+(?:[a-z]+)?)$`)
)
func VersionToName(version string) (string, error) {
if version == "" {
return "", ErrEmptyVersion
}
if stableVersion.MatchString(version) ||
strings.HasSuffix(version, "-pretest") {
return "Emacs-" + version, nil
}
return "Emacs." + version, nil
}
func GitRefToStableVersion(ref string) (string, error) {
if m := stableGitRef.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,142 @@
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" json:"type,omitempty"`
Source string `yaml:"source,omitempty" json:"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 ""
}
}
func (s *Repository) CommitURL(ref string) string {
if ref == "" {
return ""
}
switch s.Type {
case GitHub:
return GitHubBaseURL + s.Source + "/commit/" + ref
default:
return ""
}
}
func (s *Repository) TreeURL(ref string) string {
if ref == "" {
return ""
}
switch s.Type {
case GitHub:
return GitHubBaseURL + s.Source + "/tree/" + ref
default:
return ""
}
}
func (s *Repository) ActionRunURL(runID string) string {
if runID == "" {
return ""
}
switch s.Type {
case GitHub:
return GitHubBaseURL + s.Source + "/actions/runs/" + runID
default:
return ""
}
}
func (s *Repository) ReleaseURL(releaseName string) string {
if releaseName == "" {
return ""
}
switch s.Type {
case GitHub:
return GitHubBaseURL + s.Source + "/releases/tag/" + releaseName
default:
return ""
}
}

View File

@@ -0,0 +1,54 @@
package repository
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestRepository_ReleaseURL(t *testing.T) {
type fields struct {
Type Type
Source string
}
type args struct {
releaseName string
}
tests := []struct {
name string
fields fields
args args
want string
}{
{
name: "empty name",
fields: fields{Type: GitHub, Source: "foo/bar"},
args: args{releaseName: ""},
want: "",
},
{
name: "GitHub, foo/bar, v1.0.0",
fields: fields{Type: GitHub, Source: "foo/bar"},
args: args{releaseName: "v1.0.0"},
want: "https://github.com/foo/bar/releases/tag/v1.0.0",
},
{
name: "Not GitHub, foo/bar, v1.0.0",
fields: fields{Type: Type("oops"), Source: "foo/bar"},
args: args{releaseName: "v1.0.0"},
want: "",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
repo := &Repository{
Type: tt.fields.Type,
Source: tt.fields.Source,
}
got := repo.ReleaseURL(tt.args.releaseName)
assert.Equal(t, tt.want, got)
})
}
}

9
pkg/sanitize/string.go Normal file
View File

@@ -0,0 +1,9 @@
package sanitize
import "regexp"
var nonAlphaNum = regexp.MustCompile(`[^\w_-]+`)
func String(s string) string {
return nonAlphaNum.ReplaceAllString(s, "-")
}

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
}

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

@@ -0,0 +1,55 @@
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.cs.allow-dyld-environment-variables",
"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,121 @@
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.cs.allow-dyld-environment-variables",
"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.cs.allow-dyld-environment-variables</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.cs.allow-dyld-environment-variables",
"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
}

17
pkg/source/source.go Normal file
View File

@@ -0,0 +1,17 @@
package source
import (
"github.com/jimeh/build-emacs-for-macos/pkg/commit"
"github.com/jimeh/build-emacs-for-macos/pkg/repository"
)
type Source struct {
Ref string `yaml:"ref,omitempty" json:"ref,omitempty"`
Repository *repository.Repository `yaml:"repository,omitempty" json:"repository,omitempty"`
Commit *commit.Commit `yaml:"commit,omitempty" json:"commit,omitempty"`
Tarball *Tarball `yaml:"tarball,omitempty" json:"tarball,omitempty"`
}
type Tarball struct {
URL string `yaml:"url,omitempty" json:"url,omitempty"`
}

1
requirements-ci.txt Normal file
View File

@@ -0,0 +1 @@
dmgbuild