Skip to content

Commit 03ed54c

Browse files
authored
feat(startup): replace bundled site-start.el approach with a custom source patch (#124)
Because we bundle libgccjit and gcc libraries, as well as C sources into the Emacs .app bundle itself, some extra setup is required during startup of Emacs to ensure that native compliation works, and C sources are found when needed. Previously this was done by adding a custom site-start.el file to the Emacs.app bundle, which was loaded at startup. This approach had some issues, namely that when launching Emacs with `-Q` or `--no-site-file`, the file was not loaded, preventing native compilation from working. Here we replace the site-start.el approach with a custom patch adding macos-startup.el, which adds a hook to `after-pdump-load-hook`. This ensures that the startup code is always run, and before any user configuration is loaded.
1 parent 9d98b63 commit 03ed54c

File tree

1 file changed

+104
-61
lines changed

1 file changed

+104
-61
lines changed

build-emacs-for-macos

Lines changed: 104 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -408,6 +408,7 @@ class Build
408408
fatal 'Tarball extraction failed.' unless result
409409

410410
patches.each { |patch| apply_patch(patch, target) }
411+
apply_macos_startup_patch(target)
411412

412413
# Keep a copy of src after patches have been applied. This will be used to
413414
# embed C sources into the output Emacs.app bundle.
@@ -900,6 +901,93 @@ class Build
900901
File.write(filename, content)
901902
end
902903

904+
MACOS_STARTUP_EL_CONTENT = <<~ELISP
905+
;;; macos-startup.el --- macOS specific startup actions -*- lexical-binding: t -*-
906+
907+
;; Maintainer: Jim Myhrberg <[email protected]>
908+
;; Keywords: macos, internal
909+
;; Homepage: https://github.com/jimeh/build-emacs-for-macos
910+
911+
;; This file is not part of GNU Emacs.
912+
913+
;;; Commentary:
914+
915+
;; This file contains macOS specific startup actions for self-contained
916+
;; macOS *.app bundles. It enables native-compilation via a bundled
917+
;; libgccjit, and for bundled C-sources to be found for documentation
918+
;; purposes,
919+
920+
;;; Code:
921+
922+
(defun macos-startup--in-app-bundle-p ()
923+
"Check if invoked from a macOS .app bundle."
924+
(and (eq system-type 'darwin)
925+
invocation-directory
926+
(string-match-p ".+\\.app/Contents/MacOS/?$" invocation-directory)))
927+
928+
(defun macos-startup--set-source-directory ()
929+
"Set `source-directory' so that C-sources can be located."
930+
(let* ((src-dir (expand-file-name "../Resources/src" invocation-directory)))
931+
(when (file-directory-p src-dir)
932+
(setq source-directory (file-name-directory src-dir)))))
933+
934+
(defun macos-startup--setup-library-path ()
935+
"Configure LIBRARY_PATH env var for native compilation on macOS.
936+
937+
Ensures LIBRARY_PATH includes paths to the libgccjit and gcc libraries
938+
which are bundled into the .app bundle. This allows native compilation
939+
to work without any external system dependencies aside from Xcode."
940+
(let* ((new-paths
941+
(list (expand-file-name "../Frameworks/gcc/lib" invocation-directory)
942+
(expand-file-name "../Frameworks/gcc/lib/apple-darwin" invocation-directory)
943+
"/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/lib"))
944+
(valid-paths (delq nil (mapcar (lambda (path)
945+
(when (file-directory-p path)
946+
path))
947+
new-paths)))
948+
(existing-paths (split-string (or (getenv "LIBRARY_PATH") "") ":" t))
949+
(unique-paths (delete-dups (append valid-paths existing-paths))))
950+
951+
(when unique-paths
952+
(setenv "LIBRARY_PATH" (mapconcat 'identity unique-paths path-separator)))))
953+
954+
(defun macos-startup--init ()
955+
"Perform macOS specific startup operations."
956+
(when (macos-startup--in-app-bundle-p)
957+
(macos-startup--set-source-directory)
958+
(when (and (fboundp 'native-comp-available-p)
959+
(native-comp-available-p))
960+
(macos-startup--setup-library-path))))
961+
962+
(add-hook 'after-pdump-load-hook #'macos-startup--init)
963+
964+
;;; macos-startup.el ends here
965+
ELISP
966+
967+
def apply_macos_startup_patch(target)
968+
macos_startup_el = File.join(target, 'lisp', 'macos-startup.el')
969+
970+
unless File.exist?(macos_startup_el)
971+
info 'Adding macos-startup.el to lisp sources...'
972+
FileUtils.mkdir_p(File.dirname(macos_startup_el))
973+
File.write(macos_startup_el, MACOS_STARTUP_EL_CONTENT)
974+
end
975+
976+
loadup_el = File.join(target, 'lisp', 'loadup.el')
977+
loadup_content = File.read(loadup_el)
978+
979+
return if loadup_content.include?('(load "macos-startup")')
980+
981+
info 'Patching loadup.el to load macos-startup.el...'
982+
File.write(
983+
loadup_el,
984+
loadup_content.gsub(
985+
'(load "startup")',
986+
"(load \"startup\")\n(load \"macos-startup\")"
987+
)
988+
)
989+
end
990+
903991
def meta
904992
return @meta if @meta
905993

@@ -1068,6 +1156,20 @@ class Build
10681156
else
10691157
apply_patch({ file: patch_file }, target)
10701158
end
1159+
elsif patch[:source]
1160+
patch_dir = "#{target}/macos_patches"
1161+
run_cmd('mkdir', '-p', patch_dir)
1162+
1163+
patch_file = File.join(patch_dir, 'patch-{num}.diff')
1164+
num = 1
1165+
while File.exist?(patch_file.gsub('{num}', num.to_s.rjust(3, '0')))
1166+
num += 1
1167+
end
1168+
patch_file = patch_file.gsub('{num}', num.to_s.rjust(3, '0'))
1169+
1170+
File.write(patch_file, patch[:source])
1171+
1172+
apply_patch({ file: patch_file }, target)
10711173
elsif patch[:replace]
10721174
fatal 'Patch replace input error' unless patch[:replace].size == 3
10731175

@@ -1198,12 +1300,6 @@ class CLIHelperEmbedder < AbstractEmbedder
11981300
end
11991301

12001302
class CSourcesEmbedder < AbstractEmbedder
1201-
PATH_PATCH = <<~ELISP
1202-
;; Allow Emacs to find bundled C sources.
1203-
(setq source-directory
1204-
(expand-file-name ".." (file-name-directory load-file-name)))
1205-
ELISP
1206-
12071303
attr_reader :source_dir
12081304

12091305
def initialize(app, source_dir)
@@ -1228,15 +1324,6 @@ class CSourcesEmbedder < AbstractEmbedder
12281324
src_dir, target_dir, File.join('**', '*.{awk,c,cc,h,in,m,mk}')
12291325
)
12301326
end
1231-
1232-
if File.exist?(site_start_el_file) &&
1233-
File.read(site_start_el_file).include?(PATH_PATCH)
1234-
return
1235-
end
1236-
1237-
debug "Patching '#{relative_app_path(site_start_el_file)}' to allow " \
1238-
'Emacs to find bundled C sources'
1239-
File.open(site_start_el_file, 'a') { |f| f.puts("\n#{PATH_PATCH}") }
12401327
end
12411328

12421329
private
@@ -1252,10 +1339,6 @@ class CSourcesEmbedder < AbstractEmbedder
12521339
run_cmd('cp', '-pRL', f, target)
12531340
end
12541341
end
1255-
1256-
def site_start_el_file
1257-
@site_start_el_file ||= File.join(resources_dir, 'lisp', 'site-start.el')
1258-
end
12591342
end
12601343

12611344
class LibEmbedder < AbstractEmbedder
@@ -1521,49 +1604,13 @@ class GccLibEmbedder < AbstractEmbedder
15211604

15221605
FileUtils.rm(Dir[File.join(target_dir, '**', '.DS_Store')], force: true)
15231606

1524-
if target_darwin_dir != sanitized_target_darwin_dir
1525-
run_cmd('mv', target_darwin_dir, sanitized_target_darwin_dir)
1526-
end
1527-
1528-
env_setup = ERB.new(NATIVE_COMP_ENV_VAR_TPL).result(gcc_info.get_binding)
1529-
if File.exist?(site_start_el_file) &&
1530-
File.read(site_start_el_file).include?(env_setup)
1531-
return
1532-
end
1607+
return unless target_darwin_dir != sanitized_target_darwin_dir
15331608

1534-
debug 'Setting up site-start.el for self-contained native-comp Emacs.app'
1535-
File.open(site_start_el_file, 'a') { |f| f.puts("\n#{env_setup}") }
1609+
run_cmd('mv', target_darwin_dir, sanitized_target_darwin_dir)
15361610
end
15371611

15381612
private
15391613

1540-
NATIVE_COMP_ENV_VAR_TPL = <<~ELISP
1541-
;; Set LIBRARY_PATH to point at bundled GCC and Xcode Command Line Tools to
1542-
;; ensure native-comp works.
1543-
(when (and (eq system-type 'darwin)
1544-
(string-match-p "\\.app\\/Contents\\/MacOS\\/?$"
1545-
invocation-directory))
1546-
(let* ((library-path-env (getenv "LIBRARY_PATH"))
1547-
(devtools-dir
1548-
"/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/lib")
1549-
(gcc-dir (expand-file-name
1550-
"<%= app_bundle_target_lib_dir %>"
1551-
invocation-directory))
1552-
(darwin-dir (expand-file-name
1553-
"<%= app_bundle_target_darwin_lib_dir %>"
1554-
invocation-directory))
1555-
(lib-paths (list)))
1556-
1557-
(if library-path-env
1558-
(push library-path-env lib-paths))
1559-
(if (file-directory-p devtools-dir)
1560-
(push devtools-dir lib-paths))
1561-
(push darwin-dir lib-paths)
1562-
(push gcc-dir lib-paths)
1563-
1564-
(setenv "LIBRARY_PATH" (mapconcat 'identity lib-paths ":"))))
1565-
ELISP
1566-
15671614
# Remove all rpaths from Mach-O library files except for @loader_path.
15681615
def tidy_lib_rpaths(directory)
15691616
Dir[File.join(directory, '**', '*.{dylib,so}')].each do |file_path|
@@ -1607,10 +1654,6 @@ class GccLibEmbedder < AbstractEmbedder
16071654
def source_darwin_dir
16081655
gcc_info.darwin_lib_dir
16091656
end
1610-
1611-
def site_start_el_file
1612-
@site_start_el_file ||= File.join(resources_dir, 'lisp', 'site-start.el')
1613-
end
16141657
end
16151658

16161659
class GccInfo

0 commit comments

Comments
 (0)