Skip to content

Commit

Permalink
feat(startup): replace bundled site-start.el approach with a custom s…
Browse files Browse the repository at this point in the history
…ource patch

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.
  • Loading branch information
jimeh committed Dec 1, 2024
1 parent 9d98b63 commit 7fcf41b
Showing 1 changed file with 104 additions and 61 deletions.
165 changes: 104 additions & 61 deletions build-emacs-for-macos
Original file line number Diff line number Diff line change
Expand Up @@ -408,6 +408,7 @@ class Build
fatal 'Tarball extraction failed.' unless result

patches.each { |patch| apply_patch(patch, target) }
apply_macos_startup_patch(target)

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

MACOS_STARTUP_EL_CONTENT = <<~ELISP
;;; macos-startup.el --- macOS specific startup actions -*- lexical-binding: t -*-
;; Maintainer: Jim Myhrberg <[email protected]>
;; Keywords: macos, internal
;; Homepage: https://github.com/jimeh/build-emacs-for-macos
;; This file is not part of GNU Emacs.
;;; Commentary:
;; This file contains macOS specific startup actions for self-contained
;; macOS *.app bundles. It enables native-compilation via a bundled
;; libgccjit, and for bundled C-sources to be found for documentation
;; purposes,
;;; Code:
(defun macos-startup--in-app-bundle-p ()
"Check if invoked from a macOS .app bundle."
(and (eq system-type 'darwin)
invocation-directory
(string-match-p ".+\\.app/Contents/MacOS/?$" invocation-directory)))
(defun macos-startup--set-source-directory ()
"Set `source-directory' so that C-sources can be located."
(let* ((src-dir (expand-file-name "../Resources/src" invocation-directory)))
(when (file-directory-p src-dir)
(setq source-directory (file-name-directory src-dir)))))
(defun macos-startup--setup-library-path ()
"Configure LIBRARY_PATH env var for native compilation on macOS.
Ensures LIBRARY_PATH includes paths to the libgccjit and gcc libraries
which are bundled into the .app bundle. This allows native compilation
to work without any external system dependencies aside from Xcode."
(let* ((new-paths
(list (expand-file-name "../Frameworks/gcc/lib" invocation-directory)
(expand-file-name "../Frameworks/gcc/lib/apple-darwin" invocation-directory)
"/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/lib"))
(valid-paths (delq nil (mapcar (lambda (path)
(when (file-directory-p path)
path))
new-paths)))
(existing-paths (split-string (or (getenv "LIBRARY_PATH") "") ":" t))
(unique-paths (delete-dups (append valid-paths existing-paths))))
(when unique-paths
(setenv "LIBRARY_PATH" (mapconcat 'identity unique-paths path-separator)))))
(defun macos-startup--init ()
"Perform macOS specific startup operations."
(when (macos-startup--in-app-bundle-p)
(macos-startup--set-source-directory)
(when (and (fboundp 'native-comp-available-p)
(native-comp-available-p))
(macos-startup--setup-library-path))))
(add-hook 'after-pdump-load-hook #'macos-startup--init)
;;; macos-startup.el ends here
ELISP

def apply_macos_startup_patch(target)
macos_startup_el = File.join(target, 'lisp', 'macos-startup.el')

unless File.exist?(macos_startup_el)
info 'Adding macos-startup.el to lisp sources...'
FileUtils.mkdir_p(File.dirname(macos_startup_el))
File.write(macos_startup_el, MACOS_STARTUP_EL_CONTENT)
end

loadup_el = File.join(target, 'lisp', 'loadup.el')
loadup_content = File.read(loadup_el)

return if loadup_content.include?('(load "macos-startup")')

info 'Patching loadup.el to load macos-startup.el...'
File.write(
loadup_el,
loadup_content.gsub(
'(load "startup")',
"(load \"startup\")\n(load \"macos-startup\")"
)
)
end

def meta
return @meta if @meta

Expand Down Expand Up @@ -1068,6 +1156,20 @@ class Build
else
apply_patch({ file: patch_file }, target)
end
elsif patch[:source]
patch_dir = "#{target}/macos_patches"
run_cmd('mkdir', '-p', patch_dir)

patch_file = File.join(patch_dir, 'patch-{num}.diff')
num = 1
while File.exist?(patch_file.gsub('{num}', num.to_s.rjust(3, '0')))
num += 1
end
patch_file = patch_file.gsub('{num}', num.to_s.rjust(3, '0'))

File.write(patch_file, patch[:source])

apply_patch({ file: patch_file }, target)
elsif patch[:replace]
fatal 'Patch replace input error' unless patch[:replace].size == 3

Expand Down Expand Up @@ -1198,12 +1300,6 @@ class CLIHelperEmbedder < AbstractEmbedder
end

class CSourcesEmbedder < AbstractEmbedder
PATH_PATCH = <<~ELISP
;; Allow Emacs to find bundled C sources.
(setq source-directory
(expand-file-name ".." (file-name-directory load-file-name)))
ELISP

attr_reader :source_dir

def initialize(app, source_dir)
Expand All @@ -1228,15 +1324,6 @@ class CSourcesEmbedder < AbstractEmbedder
src_dir, target_dir, File.join('**', '*.{awk,c,cc,h,in,m,mk}')
)
end

if File.exist?(site_start_el_file) &&
File.read(site_start_el_file).include?(PATH_PATCH)
return
end

debug "Patching '#{relative_app_path(site_start_el_file)}' to allow " \
'Emacs to find bundled C sources'
File.open(site_start_el_file, 'a') { |f| f.puts("\n#{PATH_PATCH}") }
end

private
Expand All @@ -1252,10 +1339,6 @@ class CSourcesEmbedder < AbstractEmbedder
run_cmd('cp', '-pRL', f, target)
end
end

def site_start_el_file
@site_start_el_file ||= File.join(resources_dir, 'lisp', 'site-start.el')
end
end

class LibEmbedder < AbstractEmbedder
Expand Down Expand Up @@ -1521,49 +1604,13 @@ class GccLibEmbedder < AbstractEmbedder

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

if target_darwin_dir != sanitized_target_darwin_dir
run_cmd('mv', target_darwin_dir, sanitized_target_darwin_dir)
end

env_setup = ERB.new(NATIVE_COMP_ENV_VAR_TPL).result(gcc_info.get_binding)
if File.exist?(site_start_el_file) &&
File.read(site_start_el_file).include?(env_setup)
return
end
return unless target_darwin_dir != sanitized_target_darwin_dir

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

private

NATIVE_COMP_ENV_VAR_TPL = <<~ELISP
;; Set LIBRARY_PATH to point at bundled GCC and Xcode Command Line Tools to
;; ensure native-comp works.
(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
"<%= app_bundle_target_lib_dir %>"
invocation-directory))
(darwin-dir (expand-file-name
"<%= app_bundle_target_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 ":"))))
ELISP

# Remove all rpaths from Mach-O library files except for @loader_path.
def tidy_lib_rpaths(directory)
Dir[File.join(directory, '**', '*.{dylib,so}')].each do |file_path|
Expand Down Expand Up @@ -1607,10 +1654,6 @@ class GccLibEmbedder < AbstractEmbedder
def source_darwin_dir
gcc_info.darwin_lib_dir
end

def site_start_el_file
@site_start_el_file ||= File.join(resources_dir, 'lisp', 'site-start.el')
end
end

class GccInfo
Expand Down

0 comments on commit 7fcf41b

Please sign in to comment.