Compare commits

...

9 Commits

Author SHA1 Message Date
387c2c40aa docs: add CLAUDE.md for project guidance
- Introduced CLAUDE.md to provide comprehensive guidance on the macOS Emacs building system, including project overview, common commands, architecture details, and testing instructions.
2026-01-14 18:56:25 +00:00
527f855fbf chore(deps): add macOS SDK version 26
- Added support for macOS SDK version 26 to the list of supported SDKs.
- Reformatted flake.nix with nixfmt.
2026-01-14 18:53:56 +00:00
aafac57b1d chore(deps): update flake.lock and flake.pkgs for dependency versions
- Updated nixpkgs revision and narHash in flake.lock.
- Upgraded expat to version 2.7.3 and openssl to version 3.4.3 in flake.pkgs.
- Modified flake-package-versions command in Makefile for improved output.
2026-01-14 18:50:55 +00:00
jimehbot[bot]
4367d6b80e chore(master): release 0.6.63 (#149)
Co-authored-by: jimehbot[bot] <132453784+jimehbot[bot]@users.noreply.github.com>
2025-09-14 21:01:51 +01:00
b5ed3887c1 feat(icon): add support for Tahoe icons with --tahoe-icon-uri and --tahoe-icon-name options 2025-09-14 20:58:12 +01:00
607076a91b feat(icon): add support for custom application icons via --icon-uri option 2025-09-14 20:57:17 +01:00
1cda2cc3ff style(lint): resolve Naming/PredicateName rubocop complaint 2025-09-14 20:55:50 +01:00
jimehbot[bot]
39a4868313 chore(master): release 0.6.62 (#148)
Co-authored-by: jimehbot[bot] <132453784+jimehbot[bot]@users.noreply.github.com>
2025-09-14 12:25:30 +01:00
2086a773ae feat(deps/nix): upgrade build-time dependencies
In particular, this brings tree-sitter up to v0.25.3, enabling
tree-sitter ABI v15.
2025-09-14 12:23:27 +01:00
9 changed files with 366 additions and 120 deletions

View File

@@ -1,3 +1,3 @@
{
".": "0.6.61"
".": "0.6.63"
}

View File

@@ -1,5 +1,20 @@
# Changelog
## [0.6.63](https://github.com/jimeh/build-emacs-for-macos/compare/v0.6.62...v0.6.63) (2025-09-14)
### Features
* **icon:** add support for custom application icons via --icon-uri option ([607076a](https://github.com/jimeh/build-emacs-for-macos/commit/607076a91bf0f227d16c9404f01a64144290685a))
* **icon:** add support for Tahoe icons with --tahoe-icon-uri and --tahoe-icon-name options ([b5ed388](https://github.com/jimeh/build-emacs-for-macos/commit/b5ed3887c172540de4a6190072b1e15f5d5efe2c))
## [0.6.62](https://github.com/jimeh/build-emacs-for-macos/compare/v0.6.61...v0.6.62) (2025-09-14)
### Features
* **deps/nix:** upgrade build-time dependencies ([2086a77](https://github.com/jimeh/build-emacs-for-macos/commit/2086a773ae6bf5f4c2e6863d1b06a85acc082e6d))
## [0.6.61](https://github.com/jimeh/build-emacs-for-macos/compare/v0.6.60...v0.6.61) (2025-08-13)

80
CLAUDE.md Normal file
View File

@@ -0,0 +1,80 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with
code in this repository.
## Project Overview
macOS Emacs building system with Ruby and Go components:
- **Ruby script** (`build-emacs-for-macos`): Main build script for creating
self-contained Emacs.app bundles
- **Go CLI tool** (`emacs-builder`): Packaging, signing, notarization, and
release management
- **Dual dependency management**: Nix (preferred) or Homebrew
## Common Commands
```bash
# Environment setup (Nix preferred)
nix develop # Default macOS 11 SDK
nix develop .#macos{11-15,26} # Target specific SDK version
# Go development
make build # Build emacs-builder CLI
make test # Run tests with race detection
make lint # Run golangci-lint
make format # Format with gofumpt
# Ruby development
bundle exec rubocop # Lint (with development group)
# Build Emacs
./build-emacs-for-macos # Build from master
./build-emacs-for-macos emacs-29.4
```
## Architecture
### Ruby Build Script (`build-emacs-for-macos`)
Single-file Ruby script (~2500 lines) that:
- Downloads source tarballs from emacs-mirror/emacs on GitHub
- Configures and compiles Emacs with native-comp, tree-sitter support
- Creates self-contained .app bundles by embedding/relinking dependencies
- Uses `ruby-macho` gem for Mach-O binary manipulation (RPATH handling)
### Go CLI (`cmd/emacs-builder/`)
Uses `urfave/cli/v2` framework. Key packages in `pkg/`:
- `cli/`: Commands (plan, sign, sign-files, notarize, package, release, cask)
- `sign/`: macOS code signing via `codesign`
- `notarize/`: Apple notarization workflow via `notarytool`
- `release/`: GitHub release management
- `dmgbuild/`: DMG creation using Python dmgbuild
- `plan/`: Build plan JSON parsing and management
### Nix Environment (`flake.nix`)
- Multi-SDK support: macOS 11-15, 26 via `.#macos{11,12,13,14,15,26}`
- Excludes ncurses intentionally (links against system version for TUI)
- Sets `MACOSX_DEPLOYMENT_TARGET`, `DEVELOPER_DIR`, `NIX_LIBGCCJIT_*`
## Testing
```bash
make test # All Go tests
go test ./pkg/release/... # Single package
go test -run TestName ./pkg/... # Single test
```
Tests use `_test.go` suffix alongside source files.
## Working Directories
- `sources/`: Downloaded/extracted Emacs source (gitignored)
- `builds/`: Build outputs and .app bundles (gitignored)
- `patches/`: Emacs patches applied during build
- `bin/`: Built Go binaries (gitignored)

View File

@@ -141,10 +141,10 @@ nix-flake-update:
.SILENT: flake-package-versions
flake-package-versions:
nix develop --command -- bash -c \
nix develop --command bash -c \
'nix derivation show \
$$(echo $$PATH | tr ":" "\n" | grep "/nix/store" | sort -u) \
| jq -r ".[].name" | sort -u'
| jq -r ".derivations[].name" | sort -u'
flake.pkgs: flake.nix flake.lock
$(MAKE) flake-package-versions > "$@"

View File

@@ -141,7 +141,7 @@ Options:
--[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-]dbus Enable/disable dbus support (default: enabled)
--[no-]alpha-background Enable/disable experimental alpha-background patch when building Emacs 30.x - 31.x (default: disabled)
--alpha-background Apply experimental alpha-background patch when building Emacs 30.x - 31.x (default: disabled)
--no-frame-refocus Apply no-frame-refocus patch when building Emacs 27.x - 31.x (default: disabled)
--no-titlebar Apply no-titlebar patch when building Emacs 27.x - 28.x (default: disabled)
--[no-]xwidgets Enable/disable XWidgets when building Emacs 27.x (default: disabled)
@@ -155,6 +155,9 @@ Options:
-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)
--icon-uri URI Local path or URL to a .icns file to replace the default app icon
--tahoe-icon-uri URI Local path or URL to an Assets.car file for macOS 26 icons. Requires --tahoe-icon-name.
--tahoe-icon-name NAME Name of the icon in Assets.car to set as CFBundleIconName
--[no-]self-sign Enable/disable self-signing of Emacs.app (default: enabled)
--[no-]archive Enable/disable creating *.tbz archive (default: enabled)
--[no-]archive-keep-build-dir

View File

@@ -229,6 +229,14 @@ class Build
handle_native_lisp(app)
if options[:icon_uri] || options[:tahoe_icon_uri] || options[:tahoe_icon_name]
IconEmbedder.new(
app,
icon_uri: options[:icon_uri],
tahoe_icon_uri: options[:tahoe_icon_uri],
tahoe_icon_name: options[:tahoe_icon_name]
).embed
end
CLIHelperEmbedder.new(app).embed
CSourcesEmbedder.new(app, @source_dir).embed
LibEmbedder.new(
@@ -1413,6 +1421,98 @@ class CSourcesEmbedder < AbstractEmbedder
end
end
class IconEmbedder < AbstractEmbedder
include Helpers
def initialize(app, icon_uri: nil, tahoe_icon_uri: nil, tahoe_icon_name: nil)
super(app)
@icon_uri = icon_uri
@tahoe_icon_uri = tahoe_icon_uri
@tahoe_icon_name = tahoe_icon_name
end
def embed
handle_icns if present?(@icon_uri)
handle_tahoe if present?(@tahoe_icon_uri)
ensure
cleanup_tmpdir
end
private
def present?(val)
!val.nil? && !val.strip.empty?
end
def handle_icns
source = resolve_source(@icon_uri, '.icns', 'icon.icns')
target = File.join(resources_dir, 'Emacs.icns')
info 'Replacing application icon (Emacs.icns)...'
run_cmd('cp', '-pRL', source, target)
source
end
def handle_tahoe
fatal '--tahoe-icon-name is required with --tahoe-icon-uri' \
unless present?(@tahoe_icon_name)
source = resolve_source(@tahoe_icon_uri, '.car', 'Assets.car')
target = File.join(resources_dir, 'Assets.car')
info 'Placing Tahoe Assets.car into Resources...'
run_cmd('cp', '-pRL', source, target)
set_cf_bundle_icon_name(@tahoe_icon_name)
source
end
def set_cf_bundle_icon_name(name)
info 'Setting CFBundleIconName in Info.plist...'
info_plist = File.join(app, 'Contents', 'Info.plist')
fatal "Info.plist not found: #{info_plist}" unless File.exist?(info_plist)
# Use plutil which adds/replaces the key as needed
run_cmd(
'plutil', '-replace', 'CFBundleIconName', '-string',
name, info_plist
)
end
def resolve_source(uri, expected_ext, download_name)
file_path = if valid_url?(uri)
download_file(uri, download_name)
else
local = File.expand_path(uri)
unless File.exist?(local)
fatal "File does not exist: #{local}"
end
local
end
ext = File.extname(file_path).downcase
fatal "Unexpected file type: #{ext} (expected #{expected_ext})" \
unless ext == expected_ext
file_path
end
def tmpdir
@tmpdir ||= Dir.mktmpdir(%w[emacs-assets .tmp])
end
def download_file(url, name)
path = File.join(tmpdir, name)
info "Downloading asset from: #{url}"
run_cmd('curl', '-L#', url, '-o', path)
path
end
def cleanup_tmpdir
return unless @tmpdir
FileUtils.rm_rf(@tmpdir)
end
end
class LibEmbedder < AbstractEmbedder
attr_reader :lib_sources
attr_reader :extra_libs
@@ -1907,7 +2007,7 @@ class GccInfo
def clean_macho_binary(path)
debug "Checking for duplicate RPATHs in #{path}"
macho_cleaner = MachOCleaner.new(path)
return unless macho_cleaner.has_duplicate_rpaths?
return unless macho_cleaner.duplicate_rpaths?
begin
info "Removing duplicate RPATHs from #{path}"
@@ -1971,7 +2071,7 @@ class MachOCleaner
end
# Check if file has duplicate RPATH commands
def has_duplicate_rpaths?
def duplicate_rpaths?
count_duplicate_rpaths(macho_object).positive?
end
@@ -2101,6 +2201,14 @@ class CLIOptions
def parse!(args)
parser.parse!(args)
if options[:tahoe_icon_uri] &&
(
options[:tahoe_icon_name].nil? ||
options[:tahoe_icon_name].strip.empty?
)
fatal '--tahoe-icon-name is required when --tahoe-icon-uri is specified'
end
rescue OptionParser::InvalidOption => e
fatal e.message
end
@@ -2127,6 +2235,9 @@ class CLIOptions
github_src_repo: nil,
github_auth: true,
dist_include: ['COPYING', 'configure_output.txt'],
icon_uri: nil,
tahoe_icon_uri: nil,
tahoe_icon_name: nil,
self_sign: true,
archive: true,
archive_keep: false,
@@ -2323,6 +2434,22 @@ class CLIOptions
'folder/archive (default: COPYING)'
) { |v| options[:dist_include] = v }
opts.on(
'--icon-uri URI',
'Local path or URL to a .icns file to replace the default app icon'
) { |v| options[:icon_uri] = v }
opts.on(
'--tahoe-icon-uri URI',
'Local path or URL to an Assets.car file for macOS 26 icons. ' \
'Requires --tahoe-icon-name.'
) { |v| options[:tahoe_icon_uri] = v }
opts.on(
'--tahoe-icon-name NAME',
'Name of the icon in Assets.car to set as CFBundleIconName'
) { |v| options[:tahoe_icon_name] = v }
opts.on(
'--[no-]self-sign',
'Enable/disable self-signing of Emacs.app (default: enabled)'
@@ -2372,7 +2499,7 @@ if __FILE__ == $PROGRAM_NAME
elsif cli_options[:clean_macho_binary]
macho_cleaner = MachOCleaner.new(cli_options[:clean_macho_binary])
if macho_cleaner.has_duplicate_rpaths?
if macho_cleaner.duplicate_rpaths?
build.info 'Removing duplicate RPATHs from ' \
"#{cli_options[:clean_macho_binary]}..."
macho_cleaner.clean!

8
flake.lock generated
View File

@@ -20,16 +20,16 @@
},
"nixpkgs": {
"locked": {
"lastModified": 1750646418,
"narHash": "sha256-4UAN+W0Lp4xnUiHYXUXAPX18t+bn6c4Btry2RqM9JHY=",
"lastModified": 1767313136,
"narHash": "sha256-16KkgfdYqjaeRGBaYsNrhPRRENs0qzkQVUooNHtoy2w=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "1f426f65ac4e6bf808923eb6f8b8c2bfba3d18c5",
"rev": "ac62194c3917d5f474c1a844b6fd6da2db95077d",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-24.11",
"ref": "nixos-25.05",
"repo": "nixpkgs",
"type": "github"
}

164
flake.nix
View File

@@ -2,88 +2,104 @@
description = "Development environment flake";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.11";
nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.05";
flake-utils.url = "github:numtide/flake-utils";
};
outputs = { self, nixpkgs, flake-utils }:
flake-utils.lib.eachDefaultSystem (system:
outputs =
{
self,
nixpkgs,
flake-utils,
}:
flake-utils.lib.eachDefaultSystem (
system:
let
pkgs = nixpkgs.legacyPackages.${system};
# List of supported macOS SDK versions.
sdk_versions = [ "11" "12" "13" "14" "15" ];
sdk_versions = [
"11"
"12"
"13"
"14"
"15"
"26"
];
default_sdk_version = "11";
mkDevShell = { macos_version ? default_sdk_version }:
mkDevShell =
{
macos_version ? default_sdk_version,
}:
let
apple_sdk = pkgs.${"apple-sdk_${macos_version}"};
in
pkgs.mkShell {
# Package list specifically excludes ncurses, so that we link
# against the system version of ncurses. This ensures emacs' TUI
# works out of the box without the user having to manually set
# TERMINFO in the shell before launching emacs.
packages = with pkgs; [
apple_sdk
autoconf
bash
cairo
clang
coreutils
curl
darwin.DarwinTools # sw_vers
dbus
expat
findutils
gcc
gettext
giflib
git
gmp
gnumake
gnupatch
gnused
gnutar
gnutls
harfbuzz
jansson
jq
lcms2
libffi
libgccjit
libiconv
libjpeg
libpng
librsvg
libtasn1
libunistring
libwebp
libxml2
mailutils
nettle
pkg-config
python3
rsync
ruby_3_3
sqlite
texinfo
time
tree-sitter
which
xcbuild
zlib
];
pkgs.mkShell {
# Package list specifically excludes ncurses, so that we link
# against the system version of ncurses. This ensures emacs' TUI
# works out of the box without the user having to manually set
# TERMINFO in the shell before launching emacs.
packages = with pkgs; [
apple_sdk
autoconf
bash
cairo
clang
coreutils
curl
darwin.DarwinTools # sw_vers
dbus
expat
findutils
gcc
gettext
giflib
git
gmp
gnumake
gnupatch
gnused
gnutar
gnutls
harfbuzz
jansson
jq
lcms2
libffi
libgccjit
libiconv
libjpeg
libpng
librsvg
libtasn1
libunistring
libwebp
libxml2
mailutils
nettle
pkg-config
python313Packages.dmgbuild
rsync
ruby
sqlite
texinfo
time
tree-sitter
which
xcbuild
zlib
];
shellHook = ''
export CC=clang
export MACOSX_DEPLOYMENT_TARGET="${macos_version}.0"
export DEVELOPER_DIR="${apple_sdk}"
export NIX_LIBGCCJIT_VERSION="${pkgs.libgccjit.version}"
export NIX_LIBGCCJIT_ROOT="${pkgs.libgccjit.outPath}"
export BUNDLE_WITHOUT=development
'';
};
shellHook = ''
export CC=clang
export MACOSX_DEPLOYMENT_TARGET="${macos_version}.0"
export DEVELOPER_DIR="${apple_sdk}"
export NIX_LIBGCCJIT_VERSION="${pkgs.libgccjit.version}"
export NIX_LIBGCCJIT_ROOT="${pkgs.libgccjit.outPath}"
export BUNDLE_WITHOUT=development
'';
};
# Generate an attrset of shells for each macOS SDK version.
versionShells = builtins.listToAttrs (
@@ -93,10 +109,10 @@
}) sdk_versions
);
in
{
devShells = versionShells // {
default = mkDevShell {};
};
}
{
devShells = versionShells // {
default = mkDevShell { };
};
}
);
}

View File

@@ -1,67 +1,72 @@
DarwinTools-1
autoconf-2.72
bash-5.2p37
bash-interactive-5.2p37
brotli-1.1.0
bzip2-1.0.8
cairo-1.18.2
cctools-binutils-darwin-1010.6
cctools-binutils-darwin-wrapper-1010.6
clang-16.0.6
clang-wrapper-16.0.6
coreutils-9.5
curl-8.12.1
clang-19.1.7
clang-wrapper-19.1.7
coreutils-9.7
curl-8.14.1
dbus-1.14.10
diffutils-3.10
expat-2.7.1
diffutils-3.12
expat-2.7.3
file-5.45
findutils-4.10.0
fontconfig-2.15.0
fontconfig-2.16.0
freetype-2.13.3
gawk-5.3.1
gcc-13.3.0
gcc-wrapper-13.3.0
gawk-5.3.2
gcc-14.3.0
gcc-wrapper-14.3.0
gdk-pixbuf-2.42.12
gettext-0.21.1
gettext-0.22.5
giflib-5.2.2
git-2.47.2
glib-2.82.1
git-2.50.1
glib-2.84.3
gnugrep-3.11
gnumake-4.4.1
gnused-4.9
gnutar-1.35
gnutls-3.8.6
gnutls-3.8.9
graphite2-1.3.14
gzip-1.13
harfbuzz-10.0.1
gzip-1.14
harfbuzz-10.2.0
jq-1.7.1
krb5-1.21.3
lcms2-2.16
libdeflate-1.22
libgccjit-13.3.0
lcms2-2.17
libdeflate-1.23
libgccjit-14.3.0
libiconv-1.17
libiconv-109
libidn2-2.3.7
libidn2-2.3.8
libjpeg-turbo-3.0.4
libpng-apng-1.6.43
libpng-apng-1.6.46
libpsl-0.21.5
librsvg-2.58.3
librsvg-2.60.0
libtasn1-4.20.0
libtiff-4.7.0
libwebp-1.4.0
libwebp-1.5.0
libxml2-2.13.8
mailutils-3.17
nettle-3.10
nghttp2-1.64.0
openssl-3.3.3
mailutils-3.18
nettle-3.10.1
nghttp2-1.65.0
openssl-3.4.3
patch-2.7.6
pkg-config-wrapper-0.29.2
python3-3.12.8
python3-3.13.5
python3.13-dmgbuild-1.6.2
python3.13-ds-store-1.3.1
python3.13-mac-alias-2.2.2
rsync-3.4.1
ruby-3.3.8
sqlite-3.46.1
texinfo-7.1.1
ruby-3.3.9
sqlite-3.48.0
texinfo-7.2
time-1.9
tree-sitter-0.24.6
which-2.21
tree-sitter-0.25.3
which-2.23
xcbuild-0.1.1-unstable-2019-11-20
xz-5.6.3
zstd-1.5.6
xz-5.8.1
zstd-1.5.7