Compare commits

...

43 Commits

Author SHA1 Message Date
8d165b627b wip: add commit-info command 2021-04-26 00:23:14 +01:00
9c977a75b5 wip: extract tarball downloading into separate command 2021-04-26 00:12:07 +01:00
2054c8c0aa chore(release): 0.4.10 2021-04-26 00:07:30 +01:00
ca09d1a95f docs(readme): update readme about native-comp being merged to master 2021-04-26 00:07:07 +01:00
f1e60e31d9 chore(makefile): add next-version target to preview new-version 2021-04-25 23:09:18 +01:00
8d197aea73 chore: ensure file mode is restored in case of error 2021-04-25 23:07:54 +01:00
844df73c8f fix(cli): correctly default to master branch if no git ref is given
Previously it would build master, but not include "master" in the
final output archive's file name.
2021-04-25 23:04:03 +01:00
f1fc68c8f5 chore(release): 0.4.9 2021-04-08 12:03:41 +01:00
e19a6a7bc2 fix(cli): default to "master" if no git ref is given
Fixes #35
2021-04-08 12:02:45 +01:00
1000999eb2 fix(native_comp): skip symlink creation for recent builds which do not need symlinks
Recent builds places the native-lisp cache folder within
Contents/Resources on macOS, and correctly deals with finding them. This
means the Contents/lisp and Contents/native-lisp symlinks are no longer
needed.

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

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

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

It did require a little bit of cleanup and better organization of the
GCC/libgccjit releated code in the build script, creating a new GccInfo
class which is the central place for determining various paths and
information about GCC and libgccjit which the build will be using.
2021-02-15 09:49:47 +00:00
e56c26d06f docs(readme): update status section 2021-01-15 02:01:13 +00:00
b2860f22c3 chore(deps): add rubocop-daemon and solargraph to Gemfile 2021-01-15 02:00:46 +00:00
d1c5e7afb1 chore(release): 0.4.5 2021-01-06 20:32:30 +00:00
ab55f5421c fix(cli): remove defunct --[no-]native-comp-macos-fixes option
The underlying patching code was removed in v0.4.1 (commit
70bf6b05d5), as it was no longer needed,
but the related CLI flag and README info was mistakenly left in place.
2021-01-06 20:31:31 +00:00
eb09d5fa49 chore(release): 0.4.4 2021-01-02 14:01:37 +00:00
a47d3e0c6a fix(deps): add autoconf to Brewfile
On a fresh install of Big Sur with only the Xcode CLI tools installed,
autoconf is not available. Hence it needs to be installed from homebrew
instead.
2021-01-02 14:00:49 +00:00
5a61a72a73 chore(release): 0.4.3 2020-12-28 11:47:11 +00:00
9cdf67e71b Merge pull request #26 from jimeh/fix-big-sur
fix(big-sur): resolve issues with building and native-comp on Big Sur
2020-12-28 11:42:45 +00:00
946856e9c2 fix(big-sur): add Xcode CLI tools lib directory to runtime LIBRARY_PATH 2020-12-25 15:59:53 +00:00
claford-v-lawrence
2247158051 fix(big-sur): added support for building on Big Sur
Curtsy of https://apple.stackexchange.com/questions/408999/gfortran-compiler-error-on-mac-os-big-sur
2020-12-25 15:59:47 +00:00
4a7c507858 chore(release): 0.4.2 2020-12-09 23:58:00 +00:00
884f1607f6 fix(cli): avoid error if --git-sha is used without a branch/tag/sha argument
Fixes #21
2020-12-09 23:57:51 +00:00
c7daa1350b fix(native_comp): update env setup patch for recent changes to comp.el 2020-12-09 23:35:23 +00:00
9223ff8e28 chore(release): 0.4.1 2020-10-29 10:38:07 +00:00
70bf6b05d5 feat(native_comp): remove patch based on feature/native-comp-macos-fixes branch
Based on comparing two Emacs.app builds, one with and one without the
patch, it seems to have no effect at all on the contents of Emacs.app.
2020-10-29 10:35:18 +00:00
8936f4762a docs(readme): add CLI usage instructions
Resolves #16
2020-10-04 16:33:25 +01:00
be8e04e7a5 chore(release): 0.4.0 2020-10-04 16:15:11 +01:00
aadf32cbbe Merge pull request #17 from jimeh/support-libgccjit-homebrew-formula
feat(native_comp)!: use new libgccjit Homebrew formula
2020-10-04 16:14:38 +01:00
d8bbcb72b3 feat(native_comp)!: use new libgccjit Homebrew formula
The new libgccjit Homebrew formula negates the need to install a custom
patched gcc formula from source to get libgccjit.

As it's a separate formula, the file structure is a bit different
though, requiring some changes to the script. This means it is no longer
compatible libgccjit from the custom gcc formula. If you already have
the custom patched gcc formula installed, you can replace it with the
standard gcc formula by running:

    brew reinstall gcc

In theory though, it should work even with the patched gcc formula, as
long as libgccjit is installed too. But it will probably produce a
Emacs.app that's around 35MB larger than it needs to, thanks to
duplicating the libgccjit.so.0.0.1 file within the final application.

BREAKING CHANGE: Standard Homewbrew `gcc` and `libgccjit` formula are now required for native-comp, instead of the custom patched gcc formula.
2020-10-04 15:12:41 +01:00
d6f11b5459 docs(readme): update usage section 2020-09-23 00:49:14 +01:00
09e67381cb chore(release): 0.3.0 2020-09-22 20:29:06 +01:00
d21ccad3e4 docs(readme): update custom eln cache location instructions
Recent builds seem to pre-allocate empty `*.eln.tmp` files rather than
empty `*.eln` files. So the issue of empty `*.eln` files preventing
Emacs from starting should no longer be an issue.

I'm leaving #3 open for now, in case anyone is using older git SHAs from
the list of known good commits in #6.
2020-09-22 20:28:49 +01:00
036d2a82d9 Merge pull request #15 from jimeh/setup-library-path-with-elisp
feat(native_comp)!: use elisp patch instead of launcher script to set LIBRARY_PATH
2020-09-22 20:06:23 +01:00
111cb64993 feat(native_comp)!: use elisp patch instead of launcher script to set LIBRARY_PATH
Replace the launcher script with a emacs-lisp patch to `comp.el` which
sets the `LIBRARY_PATH` environment variable to point at the embedded
GCC/libgccjit.

This fixes issues related to the launcher script on macOS 10.15 and
later.

Fixes #14
BREAKING CHANGE: `--[no-]launcher` option is deprecated, as launcher script is no longer used.
2020-09-22 20:03:17 +01:00
21 changed files with 806 additions and 378 deletions

View File

@@ -1,13 +1,16 @@
# frozen_string_literal: true # frozen_string_literal: true
brew 'autoconf'
brew 'coreutils' brew 'coreutils'
brew 'curl' brew 'curl'
brew 'expat' brew 'expat'
brew 'gcc'
brew 'gmp' brew 'gmp'
brew 'gnu-sed' brew 'gnu-sed'
brew 'gnutls' brew 'gnutls'
brew 'jansson' brew 'jansson'
brew 'libffi' brew 'libffi'
brew 'libgccjit'
brew 'libiconv' brew 'libiconv'
brew 'librsvg' brew 'librsvg'
brew 'libtasn1' brew 'libtasn1'

View File

@@ -2,6 +2,101 @@
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. 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.4.10](https://github.com/jimeh/build-emacs-for-macos/compare/0.4.9...0.4.10) (2021-04-25)
### Bug Fixes
* **cli:** correctly default to master branch if no git ref is given ([844df73](https://github.com/jimeh/build-emacs-for-macos/commit/844df73c8fa8440e657f7900ec89cdedb7c4c312))
### [0.4.9](https://github.com/jimeh/build-emacs-for-macos/compare/0.4.8...0.4.9) (2021-04-08)
### Bug Fixes
* **cli:** default to "master" if no git ref is given ([e19a6a7](https://github.com/jimeh/build-emacs-for-macos/commit/e19a6a7bc24379292ee06ae4c805b8c5365f2d97)), closes [#35](https://github.com/jimeh/build-emacs-for-macos/issues/35)
* **native_comp:** skip symlink creation for recent builds which do not need symlinks ([1000999](https://github.com/jimeh/build-emacs-for-macos/commit/1000999eb2673dc207a390ff3f902b9987b99173))
### [0.4.8](https://github.com/jimeh/build-emacs-for-macos/compare/0.4.7...0.4.8) (2021-02-27)
### Bug Fixes
* **native_comp:** add support for new --with-native-compilation flag ([581594d](https://github.com/jimeh/build-emacs-for-macos/commit/581594da3cfbf1dd2fa28e91710b767e21ff75d2))
### [0.4.7](https://github.com/jimeh/build-emacs-for-macos/compare/0.4.6...0.4.7) (2021-02-21)
### Bug Fixes
* **native_comp:** add libgccjit include dir during build stage ([e25ceaa](https://github.com/jimeh/build-emacs-for-macos/commit/e25ceaa7e25b0e1b9947401597845b5ba43e6cd1)), closes [#20](https://github.com/jimeh/build-emacs-for-macos/issues/20)
### [0.4.6](https://github.com/jimeh/build-emacs-for-macos/compare/0.4.5...0.4.6) (2021-02-15)
### Bug Fixes
* **native_comp:** improve env setup patch fixing potential issues ([dca023d](https://github.com/jimeh/build-emacs-for-macos/commit/dca023daecd8704f197cbc391380aa194bc47d62))
### [0.4.5](https://github.com/jimeh/build-emacs-for-macos/compare/0.4.4...0.4.5) (2021-01-06)
### Bug Fixes
* **cli:** remove defunct --[no-]native-comp-macos-fixes option ([ab55f54](https://github.com/jimeh/build-emacs-for-macos/commit/ab55f5421c81dc629e487bf4b8bb402657cb1bc4))
### [0.4.4](https://github.com/jimeh/build-emacs-for-macos/compare/0.4.3...0.4.4) (2021-01-02)
### Bug Fixes
* **deps:** add autoconf to Brewfile ([a47d3e0](https://github.com/jimeh/build-emacs-for-macos/commit/a47d3e0c6a8ea8161a3bad0eafdac2401cf53129))
### [0.4.3](https://github.com/jimeh/build-emacs-for-macos/compare/0.4.2...0.4.3) (2020-12-28)
### Bug Fixes
* **big-sur:** add Xcode CLI tools lib directory to runtime LIBRARY_PATH ([946856e](https://github.com/jimeh/build-emacs-for-macos/commit/946856e9c242d4a6fb5f839d8cae0acfafecdfc6))
* **big-sur:** added support for building on Big Sur ([2247158](https://github.com/jimeh/build-emacs-for-macos/commit/2247158051d0f59933569b6974b2b5269f13c79e))
### [0.4.2](https://github.com/jimeh/build-emacs-for-macos/compare/0.4.1...0.4.2) (2020-12-09)
### Bug Fixes
* **cli:** avoid error if --git-sha is used without a branch/tag/sha argument ([884f160](https://github.com/jimeh/build-emacs-for-macos/commit/884f1607f6707ca187b1abfb0ce562757d872230)), closes [#21](https://github.com/jimeh/build-emacs-for-macos/issues/21)
* **native_comp:** update env setup patch for recent changes to comp.el ([c7daa13](https://github.com/jimeh/build-emacs-for-macos/commit/c7daa1350bd69df172ce6484c54189d2cee8d97e))
### [0.4.1](https://github.com/jimeh/build-emacs-for-macos/compare/0.4.0...0.4.1) (2020-10-29)
### Features
* **native_comp:** remove patch based on feature/native-comp-macos-fixes branch ([70bf6b0](https://github.com/jimeh/build-emacs-for-macos/commit/70bf6b05d584976632b2fd2947c0bf692f5b6421))
## [0.4.0](https://github.com/jimeh/build-emacs-for-macos/compare/0.3.0...0.4.0) (2020-10-04)
### ⚠ BREAKING CHANGES
* **native_comp:** Standard Homewbrew `gcc` and `libgccjit` formula are now required for native-comp, instead of the custom patched gcc formula.
### Features
* **native_comp:** use new libgccjit Homebrew formula ([d8bbcb7](https://github.com/jimeh/build-emacs-for-macos/commit/d8bbcb72b33f6bde8678c9d37548217ffdf3d641))
## [0.3.0](https://github.com/jimeh/build-emacs-for-macos/compare/0.2.0...0.3.0) (2020-09-22)
### ⚠ BREAKING CHANGES
* **native_comp:** `--[no-]launcher` option is deprecated, as launcher script is no longer used.
### Features
* **native_comp:** use elisp patch instead of launcher script to set LIBRARY_PATH ([111cb64](https://github.com/jimeh/build-emacs-for-macos/commit/111cb6499368d14853a5927d38a43fc5e2f759f4)), closes [#14](https://github.com/jimeh/build-emacs-for-macos/issues/14)
## [0.2.0](https://github.com/jimeh/build-emacs-for-macos/compare/0.1.1...0.2.0) (2020-09-20) ## [0.2.0](https://github.com/jimeh/build-emacs-for-macos/compare/0.1.1...0.2.0) (2020-09-20)

View File

@@ -1,21 +0,0 @@
diff --git a/Formula/gcc.rb b/Formula/gcc.rb
index 1bd636d496..03ad124218 100644
--- a/Formula/gcc.rb
+++ b/Formula/gcc.rb
@@ -53,7 +53,7 @@ class Gcc < Formula
# - Ada, which requires a pre-existing GCC Ada compiler to bootstrap
# - Go, currently not supported on macOS
# - BRIG
- languages = %w[c c++ objc obj-c++ fortran]
+ languages = %w[c c++ objc obj-c++ fortran jit]
osmajor = `uname -r`.split(".").first
pkgversion = "Homebrew GCC #{pkg_version} #{build.used_options*" "}".strip
@@ -73,6 +73,7 @@ class Gcc < Formula
--with-system-zlib
--with-pkgversion=#{pkgversion}
--with-bugurl=https://github.com/Homebrew/homebrew-core/issues
+ --enable-host-shared
]
# Xcode 10 dropped 32-bit support

View File

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

View File

@@ -1,9 +1,12 @@
.PHONY: new-version .PHONY: new-version
new-version: new-version: check-npx
$(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))
npx standard-version 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))

108
README.md
View File

@@ -1,7 +1,8 @@
# build-emacs-for-macos # build-emacs-for-macos
My personal hacked together script for building a completely self-contained 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. Use this script at your own risk.
@@ -18,7 +19,7 @@ Use this script at your own risk.
## Status ## Status
As of writing (2020-08-19) it works for me on my machine. Your luck may vary. As of writing (2021-04-25) it works for me on my machine. Your luck may vary.
I have successfully built: I have successfully built:
@@ -29,8 +30,8 @@ I have successfully built:
For reference, my machine is: For reference, my machine is:
- 13-inch MacBook Pro (2020), 10th-gen 2.3 GHz Quad-Core Intel Core i7 (4c/8t) - 13-inch MacBook Pro (2020), 10th-gen 2.3 GHz Quad-Core Intel Core i7 (4c/8t)
- macOS 10.15.6 (19G2021) - macOS Big Sur 11.2.3 (20D91)
- Xcode 11.7 - Xcode 12.4 (12D4e)
## Limitations ## Limitations
@@ -74,13 +75,11 @@ Options:
--[no-]xwidgets Enable/disable XWidgets (default: enabled if supported) --[no-]xwidgets Enable/disable XWidgets (default: enabled if supported)
--[no-]native-comp Enable/disable native-comp (default: enabled if supported) --[no-]native-comp Enable/disable native-comp (default: enabled if supported)
--[no-]native-full-aot Enable/disable NATIVE_FULL_AOT / Ahead of Time compilation (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)
--[no-]launcher Enable/disable embedded launcher script (default: enabled if native-comp is enabled)
--rsvg Enable SVG image support via librsvg, can yield a unstable build (default: disabled) --rsvg Enable SVG image support via librsvg, can yield a unstable build (default: disabled)
--no-titlebar Apply no-titlebar patch (default: disabled) --no-titlebar Apply no-titlebar patch (default: disabled)
--no-frame-refocus Apply no-frame-refocus 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-]native-fast-boot DEPRECATED: use --[no-]native-full-aot instead
--[no-]launcher DEPRECATED: Launcher script is no longer used.
``` ```
Resulting applications are saved to the `builds` directory in a bzip2 compressed Resulting applications are saved to the `builds` directory in a bzip2 compressed
@@ -89,10 +88,13 @@ tarball.
If you don't want the build process to eat all your CPU cores, pass in a `-j` If you don't want the build process to eat all your CPU cores, pass in a `-j`
value of how many CPU cores you want it to use. value of how many CPU cores you want it to use.
Re-building the same Git SHA again can yield weird results unless you first
trash the corresponding directory from the `sources` directory.
### Examples ### Examples
To download a tarball of the `master` branch (Emacs 28.x as of writing) and To download a tarball of the `master` branch (Emacs 28.x with native-compilation
build Emacs.app from it: as of writing) and build Emacs.app from it:
``` ```
./build-emacs-for-macos ./build-emacs-for-macos
@@ -109,43 +111,50 @@ All sources as downloaded as tarballs from the
to get a list of tags/branches available to install, simply check said to get a list of tags/branches available to install, simply check said
repository. repository.
## Use Self-Contained 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.
Personally I use something similar to this:
```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"
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.
## Native-Comp ## Native-Comp
To build a Emacs.app with native-comp support _Note: On 2021-04-25 the `feature/native-comp` branch was
([gccemacs](https://akrl.sdf.org/gccemacs.html)) from the `feature/native-comp` [merged](http://git.savannah.gnu.org/cgit/emacs.git/commit/?id=289000eee729689b0cf362a21baa40ac7f9506f6)
branch, you will need to install a patched version of Homebrew's `gcc` formula into `master`._
that includes libgccjit.
The patch itself is in `./Formula/gcc.rb.patch`, and comes from The build script will automatically detect if the source tree being built
[this](https://gist.github.com/mikroskeem/0a5c909c1880408adf732ceba6d3f9ab#1-gcc-with-libgccjit-enabled) supports native-compilation, and enable it if available. You can override this
gist. to force it on/off by passing `--native-comp` or `--no-native-comp`
respectfully.
You can install the patched formula by running the helper script:
```
./install-patched-gcc
```
The helper script will copy your local `gcc.rb` Forumla from Homebrew to
`./Formula`, and apply the `./Formula/gcc.rb.patch` to it. After which it then
proceed to install the patched gcc formula which includes libgccjit.
As it requires installing and compiling GCC from source, it can take anywhere
between 30-60 minutes or more depending on your machine.
And finally to build a Emacs.app with native compilation enabled, run:
```
./build-emacs-for-macos feature/native-comp
```
By default `NATIVE_FULL_AOT` is disabled which ensures a fast build by native 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 compiling as few elisp source files as possible to build Emacs itself. Any
lisp files will be dynamically compiled in the background the first time you use remaining elisp files will be dynamically compiled in the background the first
them. time they are used.
On my machine it takes around 10 minutes to build Emacs.app with To enable native full Ahead-of-Time compilation, pass in the `--native-full-aot`
`NATIVE_FULL_AOT` disabled. With it enabled it takes around 20-25 minutes. option, which will native-compile all of Emacs' elisp as 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 ### Configuration
@@ -160,26 +169,13 @@ By default natively compiled `*.eln` files will be cached in
the first element of the `comp-eln-load-path` variable. The path string must end the first element of the `comp-eln-load-path` variable. The path string must end
with a `/`. with a `/`.
Also it seems somewhat common that some `*.eln` files are left behind with a
zero-byte file size if Emacs is quit while async native compilation is in
progress. Such empty files causes errors on startup, and needs to be deleted.
Below is an example which stores all compiled `*.eln` files in `cache/eln-cache` Below is an example which stores all compiled `*.eln` files in `cache/eln-cache`
within your Emacs configuration directory, and also deletes any `*.eln` files in within your Emacs configuration directory:
said directory which have a file size of zero bytes:
```elisp ```elisp
(when (boundp 'comp-eln-load-path) (when (boundp 'comp-eln-load-path)
(let ((eln-cache-dir (expand-file-name "cache/eln-cache/" (setcar comp-eln-load-path
user-emacs-directory)) (expand-file-name "cache/eln-cache/" user-emacs-directory)))
(find-exec (executable-find "find")))
(setcar comp-eln-load-path eln-cache-dir)
;; Quitting emacs while native compilation in progress can leave zero byte
;; sized *.eln files behind. Hence delete such files during startup.
(when find-exec
(call-process find-exec nil nil nil eln-cache-dir
"-name" "*.eln" "-size" "0" "-delete" "-or"
"-name" "*.eln.tmp" "-size" "0" "-delete"))))
``` ```
### Issues ### Issues

52
bin/commit-info Executable file
View File

@@ -0,0 +1,52 @@
#!/usr/bin/env ruby
# frozen_string_literal: true
require 'optparse'
require_relative '../lib/commit_info'
require_relative '../lib/errors'
require_relative '../lib/log'
options = {
repo: 'emacs-mirror/emacs',
output: File.expand_path('../tarballs', __dir__),
log_level: :info
}
OptionParser.new do |opts|
opts.banner = <<~TXT
Usage: ./commit-info [options] <branch/tag/sha>
Fetch commit info of given GitHub repository branch, tag, or SHA.
Options:
TXT
opts.on('-r', '--repo STRING',
"GitHub repository (default: #{options[:repo]})") do |v|
options[:repo] = v
end
opts.on('-o', '--output DIR', 'Directory to save tarball in ' \
"(default: #{options[:output]})") do |v|
options[:output] = v
end
opts.on('-l', '--log-level LEVEL', 'Log level ' \
"(default: #{options[:log_level]})") do |v|
options[:log_level] = v.to_sym
end
end.parse!
begin
logger = Log.new('commit-info', options[:log_level])
commit = CommitInfo.new(
ref: ARGV[0],
repo: options[:repo],
logger: logger
).perform
puts commit.to_yaml
rescue Error => e
handle_error(e)
end

53
bin/download-tarball Executable file
View File

@@ -0,0 +1,53 @@
#!/usr/bin/env ruby
# frozen_string_literal: true
require 'optparse'
require_relative '../lib/download_tarball'
require_relative '../lib/errors'
require_relative '../lib/log'
options = {
repo: 'emacs-mirror/emacs',
output: File.expand_path('../tarballs', __dir__),
log_level: :info
}
OptionParser.new do |opts|
opts.banner = <<~TXT
Usage: ./download-tarball [options] <branch/tag/sha>
Download a tarball of given GitHub repository branch, tag, or SHA.
Options:
TXT
opts.on('-r', '--repo STRING',
"GitHub repository (default: #{options[:repo]})") do |v|
options[:repo] = v
end
opts.on('-o', '--output DIR', 'Directory to save tarball in ' \
"(default: #{options[:output]})") do |v|
options[:output] = v
end
opts.on('-l', '--log-level LEVEL', 'Log level ' \
"(default: #{options[:log_level]})") do |v|
options[:log_level] = v.to_sym
end
end.parse!
begin
logger = Log.new('download-tarball', options[:log_level])
tarball = DownloadTarball.new(
ref: ARGV[0],
repo: options[:repo],
output: options[:output],
logger: logger
).perform
puts tarball.to_yaml
rescue Error => e
handle_error(e)
end

View File

@@ -72,17 +72,18 @@ class Build
DOWNLOAD_URL = 'https://github.com/emacs-mirror/emacs/tarball/%s' DOWNLOAD_URL = 'https://github.com/emacs-mirror/emacs/tarball/%s'
LATEST_URL = 'https://api.github.com/repos/emacs-mirror/emacs/commits/%s' LATEST_URL = 'https://api.github.com/repos/emacs-mirror/emacs/commits/%s'
NATIVE_COMP_REF_REGEXP = %r{^feature/native-comp}.freeze NATIVE_COMP_REF_REGEXP = %r{^feature/native-comp}.freeze
LAUNCHER_TEMPLATE = './launcher.bash.erb'
attr_reader :root_dir attr_reader :root_dir
attr_reader :source_dir attr_reader :source_dir
attr_reader :ref attr_reader :ref
attr_reader :options attr_reader :options
attr_reader :gcc_info
def initialize(root_dir, ref = 'master', options = {}) def initialize(root_dir, ref = nil, options = {})
@root_dir = root_dir @root_dir = root_dir
@ref = ref @ref = ref || 'master'
@options = options @options = options
@gcc_info = GccInfo.new
end end
def build def build
@@ -96,16 +97,11 @@ class Build
autogen autogen
detect_native_comp if options[:native_comp].nil? detect_native_comp if options[:native_comp].nil?
if options[:native_comp] && options[:launcher].nil?
options[:launcher] = true
end
app = compile_source(@source_dir) app = compile_source(@source_dir)
symlink_internals(app) symlink_internals(app)
LibEmbedder.new(app, brew_dir, extra_libs).embed LibEmbedder.new(app, brew_dir, extra_libs).embed
LibGccJitEmbedder.new(app, gcc_dir).embed if options[:native_comp] GccLibEmbedder.new(app, gcc_info).embed if options[:native_comp]
LauncherEmbedder.new(app, LAUNCHER_TEMPLATE).embed if options[:launcher]
archive_app(app) archive_app(app)
end end
@@ -128,15 +124,11 @@ class Build
@brew_dir ||= `brew --prefix`.chomp @brew_dir ||= `brew --prefix`.chomp
end end
def gcc_dir
@gcc_dir ||= `brew --prefix gcc`.chomp
end
def extra_libs def extra_libs
@extra_libs ||= [ @extra_libs ||= [
"#{brew_dir}/opt/expat/lib/libexpat.1.dylib", File.join(brew_dir, 'opt/expat/lib/libexpat.1.dylib'),
"#{brew_dir}/opt/libiconv/lib/libiconv.2.dylib", File.join(brew_dir, 'opt/libiconv/lib/libiconv.2.dylib'),
"#{brew_dir}/opt/zlib/lib/libz.1.dylib" File.join(brew_dir, 'opt/zlib/lib/libz.1.dylib')
] ]
end end
@@ -145,7 +137,7 @@ class Build
url = (DOWNLOAD_URL % sha) url = (DOWNLOAD_URL % sha)
filename = "emacs-mirror-emacs-#{sha[0..6]}.tgz" filename = "emacs-mirror-emacs-#{sha[0..6]}.tgz"
target = File.join(tarballs_dir, filename) target = File.join(tarballs_dir, filename)
if File.exist?(target) if File.exist?(target)
info "#{filename} already exists locally, attempting to use." info "#{filename} already exists locally, attempting to use."
@@ -188,14 +180,20 @@ class Build
@configure_help @configure_help
end end
def supports_native_comp?
@supports_native_comp ||= !!configure_help.match(/\s+--with-nativecomp\s+/)
end
def supports_xwidgets? def supports_xwidgets?
@supports_xwidgets ||= !!configure_help.match(/\s+--with-xwidgets\s+/) @supports_xwidgets ||= !!configure_help.match(/\s+--with-xwidgets\s+/)
end end
def supports_native_comp?
@supports_native_comp ||= !native_comp_configure_flag.nil?
end
def native_comp_configure_flag
@native_comp_configure_flag ||= configure_help.match(
/\s+(--with-native(?:comp|-compilation))\s+/
)&.[](1)
end
def detect_native_comp def detect_native_comp
info 'Detecting native-comp support: ', newline: false info 'Detecting native-comp support: ', newline: false
options[:native_comp] = supports_native_comp? options[:native_comp] = supports_native_comp?
@@ -208,22 +206,6 @@ class Build
err 'This emacs source tree does not support native-comp' err 'This emacs source tree does not support native-comp'
end end
def verify_libgccjit
err 'gcc not installed' unless Dir.exist?(gcc_dir)
return if Dir["#{gcc_dir}/lib/**/libgccjit.so*"].any?
err "Detected GCC (#{gcc_dir}) does not have libgccjit. Ensure patched " \
'gcc brew formula has been installed via ./install-patched-gcc'
end
def gcc_library_paths
@gcc_library_paths ||= Dir[
"#{gcc_dir}/lib/gcc/*",
"#{gcc_dir}/lib/gcc/*/gcc/*apple-darwin*/*"
].sort_by { |p| [p.size, p] }
end
def autogen def autogen
FileUtils.cd(source_dir) do FileUtils.cd(source_dir) do
if File.exist?('autogen/copy_autogen') if File.exist?('autogen/copy_autogen')
@@ -235,10 +217,10 @@ class Build
end end
def compile_source(source) def compile_source(source)
target = "#{source}/nextstep" target = File.join(source, 'nextstep')
emacs_app = "#{target}/Emacs.app" emacs_app = File.join(target, 'Emacs.app')
if File.exist?("#{target}/Emacs.app") if File.exist?(emacs_app)
info 'Emacs.app already exists in ' \ info 'Emacs.app already exists in ' \
"\"#{target.gsub(root_dir + '/', '')}\", attempting to use." "\"#{target.gsub(root_dir + '/', '')}\", attempting to use."
return emacs_app return emacs_app
@@ -250,50 +232,62 @@ class Build
if options[:native_comp] if options[:native_comp]
info 'Compiling with native-comp enabled' info 'Compiling with native-comp enabled'
verify_native_comp verify_native_comp
verify_libgccjit gcc_info.verify_libgccjit
if options[:macos_fixes] && ref != 'feature/native-comp-macos-fixes' apply_native_comp_env_setup_patch(source)
apply_native_comp_macos_fixes
end
ENV['CFLAGS'] = [ ENV['CFLAGS'] = [
"-I#{gcc_dir}/include", "-I#{File.join(gcc_info.root_dir, 'include')}",
"-I#{File.join(gcc_info.libgccjit_root_dir, 'include')}",
'-O2', '-O2',
'-march=native' '-march=native',
ENV['CFLAGS']
].compact.join(' ') ].compact.join(' ')
ENV['LDFLAGS'] = [ ENV['LDFLAGS'] = [
gcc_library_paths.map { |path| "-L#{path}" }, "-L#{gcc_info.lib_dir}",
"-I#{gcc_dir}/include" "-L#{gcc_info.darwin_lib_dir}",
].flatten.compact.join(' ') "-L#{gcc_info.libgccjit_lib_dir}",
"-I#{File.join(gcc_info.root_dir, 'include')}",
"-I#{File.join(gcc_info.libgccjit_root_dir, 'include')}",
ENV['LDFLAGS']
].compact.join(' ')
ENV['LIBRARY_PATH'] = [ ENV['LIBRARY_PATH'] = [
gcc_library_paths, gcc_info.lib_dir,
gcc_info.darwin_lib_dir,
gcc_info.libgccjit_lib_dir,
ENV['LIBRARY_PATH'] ENV['LIBRARY_PATH']
].flatten.compact.join(':') ].compact.join(':')
end end
ENV['CC'] = 'clang' ENV['CC'] = 'clang'
ENV['PKG_CONFIG_PATH'] = [ ENV['PKG_CONFIG_PATH'] = [
"#{brew_dir}/lib/pkgconfig", File.join(brew_dir, 'lib/pkgconfig'),
"#{brew_dir}/share/pkgconfig", File.join(brew_dir, 'share/pkgconfig'),
"#{brew_dir}/opt/expat/lib/pkgconfig", File.join(brew_dir, 'opt/expat/lib/pkgconfig'),
"#{brew_dir}/opt/libxml2/lib/pkgconfig", File.join(brew_dir, 'opt/libxml2/lib/pkgconfig'),
"#{brew_dir}/opt/ncurses/lib/pkgconfig", File.join(brew_dir, 'opt/ncurses/lib/pkgconfig'),
"#{brew_dir}/opt/zlib/lib/pkgconfig", File.join(brew_dir, 'opt/zlib/lib/pkgconfig'),
"#{brew_dir}/Homebrew/Library/Homebrew/os/mac/pkgconfig/#{OS.version}", File.join(brew_dir, 'Homebrew/Library/Homebrew/os/mac/pkgconfig',
OS.version.to_s),
ENV['PKG_CONFIG_PATH'] ENV['PKG_CONFIG_PATH']
].compact.join(':') ].compact.join(':')
ENV['PATH'] = [ ENV['PATH'] = [
"#{brew_dir}/opt/make/libexec/gnubin", File.join(brew_dir, 'opt/make/libexec/gnubin'),
"#{brew_dir}/opt/coreutils/libexec/gnubin", File.join(brew_dir, 'opt/coreutils/libexec/gnubin'),
"#{brew_dir}/opt/gnu-sed/libexec/gnubin", File.join(brew_dir, 'opt/gnu-sed/libexec/gnubin'),
"#{brew_dir}/bin", File.join(brew_dir, 'bin'),
"#{brew_dir}/opt/texinfo/bin", File.join(brew_dir, 'opt/texinfo/bin'),
ENV['PATH'] ENV['PATH']
].compact.join(':') ].compact.join(':')
ENV['LIBRARY_PATH'] = [
ENV['LIBRARY_PATH'],
'/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/lib'
].compact.join(':')
configure_flags = [ configure_flags = [
'--with-ns', '--with-ns',
'--with-modules', '--with-modules',
@@ -304,7 +298,7 @@ class Build
if options[:xwidgets] && supports_xwidgets? if options[:xwidgets] && supports_xwidgets?
configure_flags << '--with-xwidgets' configure_flags << '--with-xwidgets'
end end
configure_flags << '--with-nativecomp' if options[:native_comp] configure_flags << native_comp_configure_flag if options[:native_comp]
configure_flags << '--without-rsvg' unless options[:rsvg] configure_flags << '--without-rsvg' unless options[:rsvg]
run_cmd './configure', *configure_flags run_cmd './configure', *configure_flags
@@ -320,17 +314,17 @@ class Build
make_flags = [] make_flags = []
make_flags += ['-j', options[:parallel].to_s] if options[:parallel] make_flags += ['-j', options[:parallel].to_s] if options[:parallel]
if options[:native_full_aot]
info 'Using NATIVE_FULL_AOT=1'
make_flags << 'NATIVE_FULL_AOT=1'
ENV.delete('NATIVE_FAST_BOOT')
else
ENV.delete('NATIVE_FULL_AOT')
ENV['NATIVE_FAST_BOOT'] = '1'
end
if options[:native_comp] if options[:native_comp]
make_flags << "BYTE_COMPILE_EXTRA_FLAGS=--eval '(setq comp-speed 2)'" make_flags << "BYTE_COMPILE_EXTRA_FLAGS=--eval '(setq comp-speed 2)'"
if options[:native_full_aot]
info 'Using NATIVE_FULL_AOT=1'
make_flags << 'NATIVE_FULL_AOT=1'
ENV.delete('NATIVE_FAST_BOOT')
else
ENV.delete('NATIVE_FULL_AOT')
ENV['NATIVE_FAST_BOOT'] = '1'
end
end end
run_cmd 'make', *make_flags run_cmd 'make', *make_flags
@@ -345,13 +339,20 @@ class Build
def symlink_internals(app) def symlink_internals(app)
return unless options[:native_comp] return unless options[:native_comp]
info 'Creating symlinks within Emacs.app needed for native-comp'
FileUtils.cd(File.join(app, 'Contents')) do FileUtils.cd(File.join(app, 'Contents')) do
# Skip creation of symlinks if *.eln files are located under
# Resources/native-lisp. Emacs is capable of finding lisp sources and
# *.eln cache files without symlinks.
return if Dir['Resources/native-lisp/**/*.eln'].any?
info 'Creating symlinks within Emacs.app needed for native-comp'
FileUtils.ln_s('Resources/lisp', 'lisp') unless File.exist?('lisp') FileUtils.ln_s('Resources/lisp', 'lisp') unless File.exist?('lisp')
source = Dir['MacOS/libexec/emacs/**/eln-cache', source = Dir['MacOS/libexec/emacs/**/eln-cache',
'MacOS/lib/emacs/**/native-lisp'].first 'MacOS/lib/emacs/**/native-lisp'].first
err 'Failed to find native-lisp cache directory for symlink creation.'
target = File.basename(source) target = File.basename(source)
FileUtils.ln_s(source, target) unless File.exist?(target) FileUtils.ln_s(source, target) unless File.exist?(target)
end end
@@ -361,18 +362,18 @@ class Build
FileUtils.mkdir_p(builds_dir) FileUtils.mkdir_p(builds_dir)
metadata = [ metadata = [
ref.gsub(/\W/, '-'), meta[:ref]&.gsub(/\W/, '-'),
meta[:date], meta[:date],
meta[:sha][0..6], meta[:sha][0..6],
"macOS-#{OS.version}", "macOS-#{OS.version}",
OS.arch OS.arch
] ].compact
filename = "Emacs.app-[#{metadata.join('][')}].tbz" filename = "Emacs.app-[#{metadata.join('][')}].tbz"
target = "#{builds_dir}/#{filename}" target = "#{builds_dir}/#{filename}"
app_base = File.basename(app) app_base = File.basename(app)
app_dir = File.dirname(app) app_dir = File.dirname(app)
if !File.exist?(target) if !File.exist?(target)
info "Creating #{filename} archive in \"#{builds_dir}\"..." info "Creating #{filename} archive in \"#{builds_dir}\"..."
@@ -402,16 +403,19 @@ class Build
return @meta if @meta return @meta if @meta
ref_sha = options[:git_sha] || ref ref_sha = options[:git_sha] || ref
info "Fetching info for git ref: #{ref_sha}"
url = format(LATEST_URL, ref_sha) url = format(LATEST_URL, ref_sha)
commit_json = http_get(url) commit_json = http_get(url)
err "Failed to get commit info about: #{ref_sha}" if commit_json.nil? err "Failed to get commit info about: #{ref_sha}" if commit_json.nil?
commit = JSON.parse(commit_json) commit = JSON.parse(commit_json)
meta = {
@meta = {
sha: commit['sha'], sha: commit['sha'],
date: Date.parse(commit['commit']['committer']['date']) date: Date.parse(commit['commit']['committer']['date'])
} }
meta[:ref] = ref if ref && ref[0..6] != meta[:sha][0..6]
@meta = meta
end end
def http_get(url) def http_get(url)
@@ -426,35 +430,19 @@ class Build
system(*args) || err("Exit code: #{$CHILD_STATUS.exitstatus}") system(*args) || err("Exit code: #{$CHILD_STATUS.exitstatus}")
end end
def apply_native_comp_macos_fixes def apply_native_comp_env_setup_patch(source)
filename = 'Makefile.in' term = 'native-compile-setup-environment-variables'
pattern = /^src: Makefile\n(.*BIN_DESTDIR.*)\nblessmail: Makefile src\n/m file = 'lisp/emacs-lisp/comp.el'
content = File.read(filename).gsub(pattern) do return if `grep '#{term}' '#{file}'`.strip.size.positive?
old_src_body = Regexp.last_match(1).strip
# check if already patched template = File.read(
if old_src_body.include?('BIN_DESTDIR=\'${ns_appbindir}/\'') File.join(__dir__, 'patches/native-comp-env-setup.diff.erb')
return old_src_body )
end patch = ERB.new(template).result(gcc_info.get_binding)
patch_file = File.join(source, 'macos_patches/native-comp-env-setup.diff')
self_contained = old_src_body.gsub( File.write(patch_file, patch)
'BIN_DESTDIR=\'$(DESTDIR)${bindir}/\'', apply_patch({ file: patch_file }, source)
'BIN_DESTDIR=\'${ns_appbindir}/\''
)
<<~REPLACEMENT
src: Makefile
ifeq (${ns_self_contained},no)
\t#{old_src_body}
else
\t#{self_contained}
endif
blessmail: Makefile src
REPLACEMENT
end
File.open(filename, 'w') { |f| f.write(content) }
end end
def effective_version def effective_version
@@ -521,11 +509,14 @@ class Build
def apply_patch(patch, target) def apply_patch(patch, target)
err "\"#{target}\" does not exist." unless File.exist?(target) err "\"#{target}\" does not exist." unless File.exist?(target)
if patch[:url] if patch[:file]
info 'Applying patch...'
FileUtils.cd(target) { run_cmd('patch', '-f', '-p1', '-i', patch[:file]) }
elsif patch[:url]
patch_dir = "#{target}/macos_patches" patch_dir = "#{target}/macos_patches"
run_cmd('mkdir', '-p', patch_dir) run_cmd('mkdir', '-p', patch_dir)
patch_file = "#{patch_dir}/patch-{num}.diff" patch_file = File.join(patch_dir, 'patch-{num}.diff')
num = 1 num = 1
while File.exist?(patch_file.gsub('{num}', num.to_s.rjust(3, '0'))) while File.exist?(patch_file.gsub('{num}', num.to_s.rjust(3, '0')))
num += 1 num += 1
@@ -535,8 +526,7 @@ class Build
info "Downloading patch: #{patch[:url]}" info "Downloading patch: #{patch[:url]}"
run_cmd('curl', '-L#', patch[:url], '-o', patch_file) run_cmd('curl', '-L#', patch[:url], '-o', patch_file)
info 'Applying patch...' apply_patch({ file: patch_file }, target)
FileUtils.cd(target) { run_cmd('patch', '-f', '-p1', '-i', patch_file) }
elsif patch[:replace] elsif patch[:replace]
err 'Patch replace input error' unless patch[:replace].size == 3 err 'Patch replace input error' unless patch[:replace].size == 3
@@ -569,16 +559,16 @@ class AbstractEmbedder
private private
def invocation_dir
File.join(app, 'Contents/MacOS')
end
def bin def bin
"#{app}/Contents/MacOS/Emacs" File.join(invocation_dir, 'Emacs')
end end
def lib_dir def lib_dir
"#{app}/Contents/MacOS/#{lib_dir_name}" File.join(invocation_dir, 'lib')
end
def lib_dir_name
"lib-#{OS.arch}-#{OS.version.to_s.tr('.', '_')}"
end end
end end
@@ -621,18 +611,18 @@ class LibEmbedder < AbstractEmbedder
while_writable(exe) do while_writable(exe) do
if match[2] == exe_file if match[2] == exe_file
system('install_name_tool', '-id', system('install_name_tool', '-id',
"@executable_path/#{rel_path}/#{match[2]}", exe) File.join('@executable_path', rel_path, match[2].to_s), exe)
else else
system('install_name_tool', '-change', match[1], system('install_name_tool', '-change', match[1],
"@executable_path/#{rel_path}/#{match[2]}", exe) File.join('@executable_path', rel_path, match[2].to_s), exe)
end end
end end
next if match[2] == exe_file || File.exist?("#{lib_dir}/#{match[2]}") next if match[2] == exe_file || File.exist?(File.join(lib_dir, match[2]))
FileUtils.mkdir_p(lib_dir) FileUtils.mkdir_p(lib_dir)
FileUtils.cp(match[1], lib_dir) FileUtils.cp(match[1], lib_dir)
copy_libs("#{lib_dir}/#{match[2]}", rel_path) copy_libs(File.join(lib_dir, match[2].to_s), rel_path)
end end
end end
@@ -651,7 +641,7 @@ class LibEmbedder < AbstractEmbedder
while_writable(target) do while_writable(target) do
system('install_name_tool', '-id', system('install_name_tool', '-id',
"@executable_path/#{rel_path}/#{lib_file}", target) File.join('@executable_path', rel_path, lib_file), target)
end end
copy_libs(target, rel_path) copy_libs(target, rel_path)
@@ -675,7 +665,7 @@ class LibEmbedder < AbstractEmbedder
while_writable(bin_path) do while_writable(bin_path) do
system( system(
'install_name_tool', '-change', match[1], 'install_name_tool', '-change', match[1],
"@executable_path/#{rel_path}/#{match[2]}", File.join('@executable_path', rel_path, match[2].to_s),
bin_path bin_path
) )
end end
@@ -687,16 +677,17 @@ class LibEmbedder < AbstractEmbedder
mode = File.stat(file).mode mode = File.stat(file).mode
File.chmod(0o775, file) File.chmod(0o775, file)
yield yield
ensure
File.chmod(mode, file) File.chmod(mode, file)
end end
end end
class LibGccJitEmbedder < AbstractEmbedder class GccLibEmbedder < AbstractEmbedder
attr_reader :gcc_dir attr_reader :gcc_info
def initialize(app, gcc_dir) def initialize(app, gcc_info)
super(app) super(app)
@gcc_dir = gcc_dir @gcc_info = gcc_info
end end
def embed def embed
@@ -706,103 +697,121 @@ class LibGccJitEmbedder < AbstractEmbedder
end end
info 'Embedding libgccjit into Emacs.app' info 'Embedding libgccjit into Emacs.app'
if gcc_version.empty?
err "No suitable GCC lib with libgccjit found in #{gcc_dir}" if gcc_info.lib_dir.empty?
err "No suitable GCC lib dir found in #{gcc_info.root_dir}"
end end
FileUtils.mkdir_p(File.dirname(target_dir)) FileUtils.mkdir_p(File.dirname(target_dir))
FileUtils.cp_r(source_dir, target_dir) FileUtils.cp_r(source_dir, target_dir)
FileUtils.rm(Dir[File.join(target_dir, '**/.DS_Store')], force: true)
end end
private private
def embedded? def embedded?
Dir[File.join(target_dir, 'libgccjit.so*')].any? Dir[File.join(target_dir, 'libgcc*')].any?
end end
def target_dir def target_dir
File.join(lib_dir, 'gcc', gcc_version) File.join(invocation_dir, gcc_info.relative_lib_dir)
end
def gcc_version
@gcc_version ||= Dir[File.join(gcc_dir, 'lib', 'gcc', '*', 'libgccjit.so*')]
.map { |path| File.dirname(path) }
.select { |path| path.match(%r{/\d+$}) }
.uniq
.map { |dir| File.basename(dir).to_i }
.max
.to_s
end end
def source_dir def source_dir
@source_dir ||= File.join(gcc_dir, 'lib', 'gcc', gcc_version) gcc_info.lib_dir
end end
end end
class LauncherEmbedder < AbstractEmbedder class GccInfo
attr_reader :template include Output
def initialize(app, template) def root_dir
super(app) @root_dir ||= `brew --prefix gcc`.chomp
@template = template
end end
def embed def major_version
if embedded? @major_version ||= File.basename(lib_dir)
info 'Launcher script already embedded in Emacs.app' end
return
def lib_dir
@lib_dir ||= Dir[File.join(root_dir, 'lib/gcc/*/libgcc*')]
.map { |path| File.dirname(path) }
.select { |path| File.basename(path).match(/^\d+$/) }
.max_by { |path| File.basename(path).to_i }
end
def relative_lib_dir
@relative_lib_dir ||= relative_dir(lib_dir, root_dir)
end
def darwin_lib_dir
@darwin_lib_dir ||= Dir[
File.join(lib_dir, 'gcc/*apple-darwin*/*')
].max_by do |path|
[
File.basename(File.dirname(path)).match(/darwin(\d+)$/)[1].to_i,
File.basename(path).split('.').map(&:to_i)
]
end
end
def relative_darwin_lib_dir
@relative_darwin_lib_dir ||= relative_dir(darwin_lib_dir, root_dir)
end
def libgccjit_root_dir
@libgccjit_root_dir ||= `brew --prefix libgccjit`.chomp
end
def libgccjit_major_version
@libgccjit_major_version ||= File.basename(libgccjit_lib_dir.to_s)
end
def libgccjit_lib_dir
@libgccjit_lib_dir ||= Dir[
File.join(libgccjit_root_dir, 'lib/gcc/*/libgccjit.so*')
]
.map { |path| File.dirname(path) }
.select { |path| File.basename(path).match(/^\d+$/) }
.max_by { |path| File.basename(path).to_i }
end
def verify_libgccjit
err 'gcc not installed' unless Dir.exist?(root_dir)
err 'libgccjit not installed' unless Dir.exist?(libgccjit_root_dir)
if libgccjit_lib_dir&.empty?
err "Detected libgccjit (#{libgccjit_root_dir}) does not have any " \
'libgccjit.so* files. Please try reinstalling libgccjit: ' \
'brew reinstall libgccjit'
end end
info 'Embedding launcher script into Emacs.app' return if major_version == libgccjit_major_version
unless File.exist?("#{bin}#{bin_suffix}") err <<~TEXT
FileUtils.mv(bin, "#{bin}#{bin_suffix}") Detected GCC and libgccjit library paths do not belong to the same major
end version of GCC. Detected paths:
- #{lib_dir}
- #{libgccjit_lib_dir}
TEXT
end
unless File.exist?("#{bin}#{bin_suffix}#{dump_ext}") def get_binding
FileUtils.mv("#{bin}#{dump_ext}", "#{bin}#{bin_suffix}#{dump_ext}") binding
end
unless File.exist?(bin)
File.write(bin, launcher)
File.chmod(0o775, bin)
end
end end
private private
def bin_suffix def relative_dir(path, root)
'-bin' root += '/' unless root[-1] == '/'
end return if path[0..root.size - 1] != root
def dump_ext path[root.size..-1]
'.pdmp'
end
def embedded?
File.exist?(bin) &&
File.exist?("#{bin}#{bin_suffix}") &&
File.exist?("#{bin}#{bin_suffix}#{dump_ext}")
end
def launcher
@launcher ||= ERB.new(File.read(template)).result(binding)
end
def library_paths
@library_paths ||= Dir[
"#{lib_dir}/gcc/*",
"#{lib_dir}/gcc/*/gcc/*apple-darwin*/*"
].map do |p|
p.gsub(/^#{Regexp.escape(lib_dir + '/')}/, '')
end.sort_by { |p| [p.size, p] }
end end
end end
if __FILE__ == $PROGRAM_NAME if __FILE__ == $PROGRAM_NAME
cli_options = { cli_options = {
macos_fixes: true,
native_full_aot: false, native_full_aot: false,
parallel: Etc.nprocessors, parallel: Etc.nprocessors,
rsvg: false, rsvg: false,
@@ -849,18 +858,6 @@ if __FILE__ == $PROGRAM_NAME
cli_options[:native_full_aot] = v cli_options[:native_full_aot] = v
end end
opts.on('--[no-]native-comp-macos-fixes',
'Enable/disable fix based on feature/native-comp-macos-fixes ' \
'branch (default: enabled if native-comp supported)') do |v|
cli_options[:macos_fixes] = v
end
opts.on('--[no-]launcher',
'Enable/disable embedded launcher script ' \
'(default: enabled if native-comp is enabled)') do |v|
cli_options[:launcher] = v
end
opts.on('--rsvg', 'Enable SVG image support via librsvg, ' \ opts.on('--rsvg', 'Enable SVG image support via librsvg, ' \
'can yield a unstable build (default: disabled)') do 'can yield a unstable build (default: disabled)') do
cli_options[:rsvg] = true cli_options[:rsvg] = true
@@ -885,6 +882,12 @@ if __FILE__ == $PROGRAM_NAME
'use --native-full-aot instead' 'use --native-full-aot instead'
end end
end end
opts.on('--[no-]launcher',
'DEPRECATED: Launcher script is no longer used.') do |_|
raise Error, '--[no-]launcher option is deprecated, launcher ' \
'script is no longer used.'
end
end.parse! end.parse!
Build.new(File.expand_path(__dir__), ARGV.shift, cli_options).build Build.new(File.expand_path(__dir__), ARGV.shift, cli_options).build

View File

@@ -1,27 +0,0 @@
#! /usr/bin/env bash
set -e
brewdir="$(brew --prefix)"
formula="${brewdir}/Homebrew/Library/Taps/homebrew/homebrew-core/Formula/gcc.rb"
if [ ! -f "$formula" ]; then
echo "ERROR: ${formula} does not exist." 1>&2
exit 1
fi
gnubins=(
"${brewdir}/opt/coreutils/libexec/gnubin"
"${brewdir}/opt/make/libexec/gnubin"
"${brewdir}/opt/gnu-sed/libexec/gnubin"
)
for gnubin in "${gnubins[@]}"; do
if [ -d "$gnubin" ]; then
export PATH="${gnubin}:$PATH"
fi
done
cp "$formula" ./Formula/
patch -f -p1 -i ./Formula/gcc.rb.patch
brew install ./Formula/gcc.rb --build-from-source --force

View File

@@ -1,65 +0,0 @@
#!/bin/bash
# This launcher script is not part of Emacs proper. It is from the
# build-emacs-for-macos project (https://github.com/jimeh/build-emacs-for-macos)
# and helps facilitate proper startup of Emacs with environment varibales set as
# needed.
#
# Licensed under CC0 1.0 Universal:
# https://creativecommons.org/publicdomain/zero/1.0/
#
set -e
resolve_link() {
local file="$1"
while [ -L "$file" ]; do
file="$(readlink "$file")"
done
echo "$file"
}
realname() {
local path="$1"
local resolved
local cwd
cwd="$(pwd)"
resolved="$(resolve_link "$path")"
cd "$(dirname "$resolved")"
echo "$(pwd)/$(basename "$resolved")"
cd "$cwd"
}
join() {
local IFS="$1"
local parts=()
shift
for arg in "$@"; do
if [ "$arg" != "" ]; then
parts+=("$arg")
fi
done
echo "${parts[*]}"
}
DIR="$(dirname "$(realname "$0")")"
BIN="${DIR}/Emacs<%= bin_suffix %>"
export PATH="${DIR}/bin:${DIR}/libexec:${PATH}"
<% if library_paths.any? %>
LIB_PATHS=(
'<%= library_paths.map { |p| p.gsub('\'', "\"'\"") }.join("'\n '") %>'
)
for lib in "${LIB_PATHS[@]}"; do
if [ -d "${DIR}/<%= lib_dir_name %>/${lib}" ]; then
libs="$(join : "$libs" "${DIR}/<%= lib_dir_name %>/${lib}")"
fi
done
LIBRARY_PATH="$(join : "$libs" "$LIBRARY_PATH")"
export LIBRARY_PATH
<% end %>
exec "$BIN" "$@"

9
lib/base_action.rb Normal file
View File

@@ -0,0 +1,9 @@
# frozen_string_literal: true
require_relative './common'
require_relative './output'
class BaseAction
include Common
include Output
end

46
lib/commit.rb Normal file
View File

@@ -0,0 +1,46 @@
# frozen_string_literal: true
require 'json'
require 'time'
require 'yaml'
require_relative './errors'
require_relative './common'
class Commit
include Common
attr_reader :repo
attr_reader :ref
attr_reader :message
attr_reader :sha
attr_reader :time
def initialize(sha:, time:, repo: nil, ref: nil, message: nil)
@sha = sha
@time = time
@repo = repo
@ref = ref
@message = message
end
def sha_short
sha[0..6]
end
def to_hash
{
'repo' => repo,
'ref' => ref,
'sha' => sha,
'sha_short' => sha_short,
'time' => time.utc,
'timestamp' => time.utc.to_i,
'message' => message
}
end
def to_yaml
to_hash.to_yaml
end
end

43
lib/commit_info.rb Normal file
View File

@@ -0,0 +1,43 @@
# frozen_string_literal: true
require_relative './base_action'
require_relative './commit'
class CommitInfo < BaseAction
COMMIT_URL = 'https://api.github.com/repos/%s/commits/%s'
attr_reader :ref
attr_reader :repo
attr_reader :logger
def initialize(ref:, repo:, logger:)
@ref = ref
@repo = repo
@logger = logger
err 'branch/tag/sha argument cannot be empty' if ref.nil? || ref.empty?
end
def perform
info "Fetching info for git ref: #{ref}"
url = format(COMMIT_URL, repo, ref)
commit_json = http_get(url)
err "Failed to get commit info about: #{ref}" if commit_json.nil?
parsed = JSON.parse(commit_json)
commit = Commit.new(
repo: repo,
ref: ref,
sha: parsed&.dig('sha'),
message: parsed&.dig('commit', 'message'),
time: Time.parse(parsed&.dig('commit', 'committer', 'date')).utc
)
err 'Failed to get commit SHA' if commit.sha.nil? || commit.sha.empty?
err 'Failed to get commit time' if commit.time.nil?
commit
end
end

23
lib/common.rb Normal file
View File

@@ -0,0 +1,23 @@
# frozen_string_literal: true
require 'net/http'
module Common
private
def self.included(base)
base.extend(self)
end
def run_cmd(*args)
info "executing: #{args.join(' ')}"
system(*args) || err("Exit code: #{$CHILD_STATUS.exitstatus}")
end
def http_get(url)
response = Net::HTTP.get_response(URI.parse(url))
return unless response.code == '200'
response.body
end
end

61
lib/download_tarball.rb Normal file
View File

@@ -0,0 +1,61 @@
# frozen_string_literal: true
require 'fileutils'
require 'json'
require 'time'
require_relative './base_action'
require_relative './commit_info'
require_relative './tarball'
class DownloadTarball < BaseAction
TARBALL_URL = 'https://github.com/%s/tarball/%s'
attr_reader :ref
attr_reader :repo
attr_reader :output
attr_reader :logger
def initialize(ref:, repo:, output:, logger:)
@ref = ref
@repo = repo
@output = output
@logger = logger
err 'branch/tag/sha argument cannot be empty' if ref.nil? || ref.empty?
end
def perform
FileUtils.mkdir_p(output)
tarball = Tarball.new(file: target, commit: commit)
if File.exist?(target)
info "#{filename} already exists locally, attempting to use."
return tarball
end
info 'Downloading tarball from GitHub. This could take a while, ' \
'please be patient.'
result = run_cmd('curl', '-L', url, '-o', target)
err 'Download failed.' if !result || !File.exist?(target)
tarball
end
def url
@url ||= format(TARBALL_URL, repo, commit.sha)
end
def filename
@filename ||= "#{repo.gsub(/[^\w]/, '-')}-#{commit.sha_short}.tgz"
end
def target
@target ||= File.join(output, filename)
end
def commit
@commit ||= CommitInfo.new(ref: ref, repo: repo, logger: logger).perform
end
end

12
lib/errors.rb Normal file
View File

@@ -0,0 +1,12 @@
# frozen_string_literal: true
def handle_error(err)
warn "ERROR: #{err.message}"
Process.exit 1
end
class Error < StandardError; end
class CommitNotFound < Error; end
class NoCommitSHA < Error; end
class NoCommitTime < Error; end

31
lib/log.rb Normal file
View File

@@ -0,0 +1,31 @@
# frozen_string_literal: true
class Log
extend Forwardable
attr_reader :name
attr_reader :level
def initialize(name, level = :info)
@name = name
@level = level
end
def_delegators :logger, :debug, :info, :warn, :error, :fatal, :unkonwn
private
def logger
@logger ||= Logger.new($stderr).tap do |l|
l.progname = name
l.level = level
l.formatter = formatter
end
end
def formatter
proc do |severity, _datetime, progname, msg|
"==> [#{progname}] #{severity}: #{msg}\n"
end
end
end

28
lib/output.rb Normal file
View File

@@ -0,0 +1,28 @@
# frozen_string_literal: true
require 'forwardable'
require 'logger'
require_relative './errors'
module Output
extend Forwardable
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
def logger_name(name = nil)
return @logger_name if name.nil?
@logger_name = name
end
end
def_delegators :logger, :debug, :info, :warn, :error, :fatal, :unkonwn
def err(msg = nil)
raise Error, msg
end
end

24
lib/tarball.rb Normal file
View File

@@ -0,0 +1,24 @@
# frozen_string_literal: true
require 'yaml'
class Tarball
attr_reader :file
attr_reader :commit
def initialize(file:, commit:)
@file = file
@commit = commit
end
def to_hash
{
'file' => file,
'commit' => commit.to_hash
}
end
def to_yaml
to_hash.to_yaml
end
end

View File

@@ -0,0 +1,57 @@
diff --git a/lisp/emacs-lisp/comp.el b/lisp/emacs-lisp/comp.el
index 4036080976..2ff8dbd74c 100644
--- a/lisp/emacs-lisp/comp.el
+++ b/lisp/emacs-lisp/comp.el
@@ -4079,6 +4079,52 @@ of (commands) to run simultaneously."
(let ((load (not (not load))))
(native--compile-async paths recursively load selector)))
+;;;###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-dir (expand-file-name
+ "<%= relative_lib_dir %>"
+ invocation-directory))
+ (darwin-dir (expand-file-name
+ "<%= relative_darwin_lib_dir %>"
+ invocation-directory))
+ (lib-paths (list)))
+
+ (if library-path-env
+ (push library-path-env lib-paths))
+ (if (file-directory-p devtools-dir)
+ (push devtools-dir lib-paths))
+ (push darwin-dir lib-paths)
+ (push gcc-dir lib-paths)
+
+ (setenv "LIBRARY_PATH" (mapconcat 'identity lib-paths ":"))))
+
+ ;; Remove advice, as it only needs to run once.
+ (advice-remove 'native-compile
+ '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