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 971b606
Showing 1 changed file with 114 additions and 61 deletions.
175 changes: 114 additions & 61 deletions build-emacs-for-macos
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,102 @@ require 'yaml'

require 'macho'

# Custom patch for startup of macOS, allowing Emacs .app bundles to correctly
# use bundled libgccjit libraries and C sources.
MACOS_STARTUP_PATCH_SOURCE = <<~PATCH
From 52b5b168b9db532a18538dc0acc8a1299152fd09 Mon Sep 17 00:00:00 2001
From: Jim Myhrberg <[email protected]>
Date: Sun, 1 Dec 2024 08:45:51 +0000
Subject: [PATCH] feat(macos/startup): add macos-startup.el
---
lisp/loadup.el | 1 +
lisp/macos-startup.el | 60 +++++++++++++++++++++++++++++++++++++++++++
2 files changed, 61 insertions(+)
create mode 100644 lisp/macos-startup.el
diff --git a/lisp/loadup.el b/lisp/loadup.el
index bd74a9d6aff..b1d29a3c929 100644
--- a/lisp/loadup.el
+++ b/lisp/loadup.el
@@ -268,6 +268,7 @@
(load "minibuffer") ; Needs cl-generic, seq (and define-minor-mode).
(load "frame")
(load "startup")
+(load "macos-startup")
(load "term/tty-colors")
(load "font-core")
(load "emacs-lisp/syntax")
diff --git a/lisp/macos-startup.el b/lisp/macos-startup.el
new file mode 100644
index 00000000000..9fb6887f10b
--- /dev/null
+++ b/lisp/macos-startup.el
@@ -0,0 +1,60 @@
+;;; 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 the bundled
+;; libgccjit, and for the 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
--
2.47.0
PATCH

class Error < StandardError
end

Expand Down Expand Up @@ -1038,6 +1134,8 @@ class Build
}
end

p << { source: MACOS_STARTUP_PATCH_SOURCE }

p.uniq
end

Expand Down Expand Up @@ -1068,6 +1166,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 +1310,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 +1334,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 +1349,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 +1614,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
return unless target_darwin_dir != sanitized_target_darwin_dir

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

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 +1664,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 971b606

Please sign in to comment.