Files
build-emacs-for-macos/build-emacs-for-macos
Jim Myhrberg 707bc9e0d1 fix(download): don't use GitHub API to get tarball URL
The API tarball endpoint is subject to API rate limits, while the
non-API tarball endpoint is not. This should reduce the risk of rate
limit errors for people who don't have a GITHUB_TOKEN environment
variable set.
2021-06-20 17:52:17 +01:00

1004 lines
26 KiB
Ruby
Executable File

#!/usr/bin/env ruby
# frozen_string_literal: true
require 'English'
require 'date'
require 'erb'
require 'etc'
require 'fileutils'
require 'json'
require 'net/http'
require 'optparse'
require 'pathname'
require 'time'
require 'uri'
require 'yaml'
class Error < StandardError; end
module Output
def info(msg, newline: true)
out "INFO: #{msg}", newline: newline
end
def out(msg, newline: true)
if newline
warn "==> #{msg}"
else
STDERR.print "==> #{msg}"
end
end
def err(msg = nil)
raise Error, msg
end
end
class OS
def self.version
@version ||= OSVersion.new
end
def self.arch
@arch ||= `uname -m`.strip
end
end
class OSVersion
def initialize
@version = `sw_vers -productVersion`.match(
/(?<major>\d+)(?:\.(?<minor>\d+)(:?\.(?<patch>\d+))?)?/
)
end
def to_s
@to_s ||= "#{major}.#{minor}"
end
def major
@major ||= @version[:major]&.to_i
end
def minor
@minor ||= @version[:minor]&.to_i
end
def patch
@patch ||= @version[:patch]&.to_i
end
end
class Build
include Output
EMACS_MIRROR_REPO = 'emacs-mirror/emacs'
DOWNLOAD_URL = 'https://github.com/emacs-mirror/emacs/tarball/%s'
attr_reader :root_dir
attr_reader :source_dir
attr_reader :ref
attr_reader :options
attr_reader :gcc_info
def initialize(root_dir, ref = nil, options = {})
@root_dir = root_dir
@ref = ref || 'master'
@options = options
@gcc_info = GccInfo.new
end
def build
load_plan(options[:plan]) if options[:plan]
unless meta[:sha] && meta[:date]
err 'Failed to get commit info from GitHub.'
end
tarball = download_tarball(meta[:sha])
@source_dir = extract_tarball(tarball, patches(options))
autogen
detect_native_comp if options[:native_comp].nil?
app = compile_source(@source_dir)
symlink_internals(app)
add_cli_helper(app)
LibEmbedder.new(app, brew_dir, extra_libs).embed
GccLibEmbedder.new(app, gcc_info).embed if options[:native_comp]
archive_app(app)
end
private
def load_plan(filename)
plan = YAML.safe_load(File.read(filename), [:Time])
@meta = {
sha: plan.dig('commit', 'sha'),
ref: plan.dig('commit', 'ref'),
date: plan.dig('commit', 'date')
}
@archive_filename = plan['archive']
end
def tarballs_dir
@tarballs_dir ||= File.join(root_dir, 'tarballs')
end
def sources_dir
@sources_dir ||= File.join(root_dir, 'sources')
end
def builds_dir
@builds_dir ||= File.join(root_dir, 'builds')
end
def brew_dir
@brew_dir ||= `brew --prefix`.chomp
end
def extra_libs
@extra_libs ||= [
File.join(brew_dir, 'opt/expat/lib/libexpat.1.dylib'),
File.join(brew_dir, 'opt/libiconv/lib/libiconv.2.dylib'),
File.join(brew_dir, 'opt/zlib/lib/libz.1.dylib')
]
end
def download_tarball(sha)
FileUtils.mkdir_p(tarballs_dir)
url = (DOWNLOAD_URL % sha)
filename = "emacs-mirror-emacs-#{sha[0..6]}.tgz"
target = File.join(tarballs_dir, filename)
if File.exist?(target)
info "#{filename} already exists locally, attempting to use."
return target
end
info 'Downloading tarball from GitHub. This could take a while, ' \
'please be patient.'
args = ['curl', '-L', url, '-o', target]
log_args = args.clone
if options[:github_auth] && ENV['GITHUB_TOKEN']
args = [args[0]] +
['-H', "Authorization: Token #{ENV['GITHUB_TOKEN']}"] + args[1..-1]
log_args = [log_args[0]] +
['-H', '"Authorization: Token $GITHUB_TOKEN"'] +
log_args[1..-1]
end
out "CMD: #{log_args.join(' ')}"
system(*args) || err("Exit code: #{$CHILD_STATUS.exitstatus}")
target
end
def extract_tarball(filename, patches = [])
FileUtils.mkdir_p(sources_dir)
dirname = File.basename(filename).gsub(/\.\w+$/, '')
target = File.join(sources_dir, dirname)
if File.exist?(target)
info "#{dirname} source tree exists, attempting to use."
return target
end
info 'Extracting tarball...'
result = run_cmd('tar', '-xzf', filename, '-C', sources_dir)
err 'Tarball extraction failed.' unless result
patches.each { |patch| apply_patch(patch, target) }
target
end
def configure_help
return @configure_help if @configure_help
FileUtils.cd(source_dir) { @configure_help = `./configure --help` }
@configure_help
end
def supports_xwidgets?
@supports_xwidgets ||= !!configure_help.match(/\s+--with-xwidgets\s+/)
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
info 'Detecting native-comp support: ', newline: false
options[:native_comp] = supports_native_comp?
puts options[:native_comp] ? 'Supported' : 'Not supported'
end
def verify_native_comp
return if supports_native_comp?
err 'This emacs source tree does not support native-comp'
end
def autogen
FileUtils.cd(source_dir) do
if File.exist?('autogen/copy_autogen')
run_cmd 'autogen/copy_autogen'
elsif File.exist?('autogen.sh')
run_cmd './autogen.sh'
end
end
end
def compile_source(source)
target = File.join(source, 'nextstep')
emacs_app = File.join(target, 'Emacs.app')
if File.exist?(emacs_app)
info 'Emacs.app already exists in ' \
"\"#{target.gsub(root_dir + '/', '')}\", attempting to use."
return emacs_app
end
info 'Compiling from source. This will take a while...'
FileUtils.cd(source) do
if options[:native_comp]
info 'Compiling with native-comp enabled'
verify_native_comp
gcc_info.verify_libgccjit
apply_native_comp_env_setup_patch(source)
ENV['CFLAGS'] = [
"-I#{File.join(gcc_info.root_dir, 'include')}",
"-I#{File.join(gcc_info.libgccjit_root_dir, 'include')}",
'-O2',
(options[:native_march] ? '-march=native' : nil),
ENV['CFLAGS']
].compact.join(' ')
ENV['LDFLAGS'] = [
"-L#{gcc_info.lib_dir}",
"-L#{gcc_info.darwin_lib_dir}",
"-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'] = [
gcc_info.lib_dir,
gcc_info.darwin_lib_dir,
gcc_info.libgccjit_lib_dir,
ENV['LIBRARY_PATH']
].compact.join(':')
end
ENV['CC'] = 'clang'
ENV['PKG_CONFIG_PATH'] = [
File.join(brew_dir, 'lib/pkgconfig'),
File.join(brew_dir, 'share/pkgconfig'),
File.join(brew_dir, 'opt/expat/lib/pkgconfig'),
File.join(brew_dir, 'opt/libxml2/lib/pkgconfig'),
File.join(brew_dir, 'opt/ncurses/lib/pkgconfig'),
File.join(brew_dir, 'opt/zlib/lib/pkgconfig'),
File.join(brew_dir, 'Homebrew/Library/Homebrew/os/mac/pkgconfig',
OS.version.to_s),
ENV['PKG_CONFIG_PATH']
].compact.join(':')
ENV['PATH'] = [
File.join(brew_dir, 'opt/make/libexec/gnubin'),
File.join(brew_dir, 'opt/coreutils/libexec/gnubin'),
File.join(brew_dir, 'opt/gnu-sed/libexec/gnubin'),
File.join(brew_dir, 'bin'),
File.join(brew_dir, 'opt/texinfo/bin'),
ENV['PATH']
].compact.join(':')
ENV['LIBRARY_PATH'] = [
ENV['LIBRARY_PATH'],
'/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/lib'
].compact.join(':')
configure_flags = [
'--with-ns',
'--with-modules',
'--enable-locallisppath=' \
'/Library/Application Support/Emacs/${version}/site-lisp:' \
'/Library/Application Support/Emacs/site-lisp'
]
if options[:xwidgets] && supports_xwidgets?
configure_flags << '--with-xwidgets'
end
configure_flags << native_comp_configure_flag if options[:native_comp]
configure_flags << '--without-rsvg' if options[:rsvg] == false
run_cmd './configure', *configure_flags
# Disable aligned_alloc on Mojave and below. See issue:
# https://github.com/daviderestivo/homebrew-emacs-head/issues/15
if OS.version.major <= 10 && OS.version.minor <= 14
info 'Force disabling of aligned_alloc on macOS Mojave (10.14.x) ' \
'and earlier'
disable_alligned_alloc
end
make_flags = []
make_flags += ['-j', options[:parallel].to_s] if options[:parallel]
if options[:native_comp]
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
run_cmd 'make', *make_flags
run_cmd 'make', 'install'
end
err 'Build failed.' unless File.exist?(emacs_app)
emacs_app
end
def symlink_internals(app)
return unless options[:native_comp]
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')
source = Dir['MacOS/libexec/emacs/**/eln-cache',
'MacOS/lib/emacs/**/native-lisp'].first
err 'Failed to find native-lisp cache directory for symlink creation.'
target = File.basename(source)
FileUtils.ln_s(source, target) unless File.exist?(target)
end
end
def add_cli_helper(app)
source = File.join(__dir__, 'helper', 'emacs-cli.bash')
target = File.join(app, 'Contents', 'MacOS', 'bin', 'emacs')
dir = File.dirname(target)
info "Adding \"emacs\" CLI helper to #{dir}"
FileUtils.mkdir_p(dir)
FileUtils.cp(source, target)
FileUtils.chmod('+w', target)
end
def archive_filename
return @archive_filename if @archive_filename
metadata = [
meta[:ref]&.gsub(/\W/, '-'),
meta[:date]&.strftime('%Y-%m-%d'),
meta[:sha][0..6],
"macOS-#{OS.version}",
OS.arch
].compact
filename = "Emacs.app-[#{metadata.join('][')}].tbz"
@archive_filename = File.join(builds_dir, filename)
end
def archive_app(app)
filename = File.basename(archive_filename)
target_dir = File.dirname(archive_filename)
relative_target_dir = target_dir.gsub(root_dir + '/', '')
FileUtils.mkdir_p(target_dir)
app_base = File.basename(app)
app_dir = File.dirname(app)
if !File.exist?(archive_filename)
info "Creating #{filename} archive in \"#{relative_target_dir}\"..."
FileUtils.cd(app_dir) do
system('tar', '-cjf', archive_filename, app_base)
end
else
info "#{filename} archive exists in " \
"#{relative_target_dir}, skipping archving."
end
end
def disable_alligned_alloc
filename = 'src/config.h'
content = File.read(filename)
.gsub('#define HAVE_ALIGNED_ALLOC 1',
'#undef HAVE_ALIGNED_ALLOC')
.gsub('#define HAVE_DECL_ALIGNED_ALLOC 1',
'#undef HAVE_DECL_ALIGNED_ALLOC')
.gsub('#define HAVE_ALLOCA 1',
'#undef HAVE_ALLOCA')
.gsub('#define HAVE_ALLOCA_H 1',
'#undef HAVE_ALLOCA_H')
File.open(filename, 'w') { |f| f.write(content) }
end
def meta
return @meta if @meta
ref_sha = options[:git_sha] || ref
info "Fetching info for git ref: #{ref_sha}"
commit_json = github_api_get(
"/repos/#{EMACS_MIRROR_REPO}/commits/#{ref_sha}"
)
err "Failed to get commit info about: #{ref_sha}" if commit_json.nil?
commit = JSON.parse(commit_json)
meta = {
sha: commit['sha'],
date: Time.parse(commit['commit']['committer']['date'])
}
meta[:ref] = ref if ref && ref[0..6] != meta[:sha][0..6]
@meta = meta
end
def github_api_get(uri)
uri = URI.join('https://api.github.com/', uri)
http = Net::HTTP.new(uri.hostname, uri.port)
http.use_ssl = true if uri.scheme == 'https'
request = Net::HTTP::Get.new(uri)
if options[:github_auth] && ENV['GITHUB_TOKEN']
request['Authorization'] = "Token #{ENV['GITHUB_TOKEN']}"
end
response = http.request(request)
return unless response.code == '200'
response.body
end
def run_cmd(*args)
out "CMD: #{args.join(' ')}"
system(*args) || err("Exit code: #{$CHILD_STATUS.exitstatus}")
end
def apply_native_comp_env_setup_patch(source)
term = 'native-compile-setup-environment-variables'
file = 'lisp/emacs-lisp/comp.el'
return if `grep '#{term}' '#{file}'`.strip.size.positive?
template = File.read(
File.join(__dir__, 'patches/native-comp-env-setup.diff.erb')
)
patch = ERB.new(template).result(gcc_info.get_binding)
patch_file = File.join(source, 'macos_patches/native-comp-env-setup.diff')
File.write(patch_file, patch)
apply_patch({ file: patch_file }, source)
end
def effective_version
@effective_version ||= begin
case ref
when /^emacs-26.*/
'emacs-26'
when /^emacs-27.*/
'emacs-27'
else
'emacs-28'
end
end
end
def patches(opts = {})
p = []
if %w[emacs-26 emacs-27 emacs-28].include?(effective_version)
p << {
url: 'https://github.com/d12frosted/homebrew-emacs-plus/raw/master/' \
"patches/#{effective_version}/fix-window-role.patch"
}
end
if %w[emacs-27 emacs-28].include?(effective_version)
p << {
url: 'https://github.com/d12frosted/homebrew-emacs-plus/raw/master/' \
"patches/#{effective_version}/system-appearance.patch"
}
if options[:no_titlebar]
p << {
url: 'https://github.com/d12frosted/homebrew-emacs-plus/raw/master/' \
"patches/#{effective_version}/no-titlebar.patch"
}
end
if options[:no_frame_refocus]
p << {
url: 'https://github.com/d12frosted/homebrew-emacs-plus/raw/master/' \
"patches/#{effective_version}/no-frame-refocus-cocoa.patch"
}
end
end
if effective_version == 'emacs-27'
p << {
url: 'https://github.com/d12frosted/homebrew-emacs-plus/raw/master/' \
"patches/#{effective_version}/ligatures-freeze-fix.patch"
}
if opts[:xwidgets]
p << {
url: 'https://github.com/d12frosted/homebrew-emacs-plus/raw/master/' \
"patches/#{effective_version}/xwidgets_webkit_in_cocoa.patch"
}
end
end
p
end
def apply_patch(patch, target)
err "\"#{target}\" does not exist." unless File.exist?(target)
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"
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'))
info "Downloading patch: #{patch[:url]}"
run_cmd('curl', '-L#', patch[:url], '-o', patch_file)
apply_patch({ file: patch_file }, target)
elsif patch[:replace]
err 'Patch replace input error' unless patch[:replace].size == 3
file, before, after = patch[:replace]
filepath = File.join(target, file)
err "\"#{file}\" does not exist in #{target}" unless File.exist?(filepath)
f = File.open(filepath, 'rb')
s = f.read
sub = s.gsub!(before, after)
err "Replacement filed in #{file}" if sub.nil?
f.reopen(filepath, 'wb').write(s)
f.close
end
end
end
class AbstractEmbedder
include Output
attr_reader :app
def initialize(app)
err "#{app} does not exist" unless File.exist?(app)
@app = app
end
private
def invocation_dir
File.join(app, 'Contents', 'MacOS')
end
def bin
File.join(invocation_dir, 'Emacs')
end
def lib_dir
File.join(invocation_dir, 'lib')
end
end
class LibEmbedder < AbstractEmbedder
attr_reader :lib_source
attr_reader :extra_libs
def initialize(app, lib_source, extra_libs = [])
super(app)
@lib_source = lib_source
@extra_libs = extra_libs
end
def embed
info 'Embedding libraries into Emacs.app'
binary = "#{bin}-bin" if File.exist?("#{bin}-bin")
binary ||= bin
FileUtils.cd(File.dirname(app)) do
copy_libs(binary)
copy_extra_libs(extra_libs, binary) if extra_libs.any?
if eln_files.any?
info "Embedding libraries for #{eln_files.size} *.eln files " \
'within Emacs.app'
rel_path = Pathname.new(lib_dir).relative_path_from(
Pathname.new(File.dirname(binary))
).to_s
eln_files.each { |f| copy_libs(f, rel_path) }
end
end
end
private
def eln_files
@eln_files ||= Dir[
File.join(
app, 'Contents', 'Resources', 'native-lisp', '**', '*.eln'
),
File.join(
app, 'Contents', 'MacOS', 'lib', 'emacs', '**',
'native-lisp', '**', '*.eln'
),
File.join(
app, 'Contents', 'MacOS', 'libexec', 'emacs', '**',
'eln-cache', '**', '*.eln'
)
]
end
def copy_libs(exe, rel_path = nil)
exe_file = File.basename(exe)
rel_path ||= Pathname.new(lib_dir).relative_path_from(
Pathname.new(File.dirname(exe))
).to_s
rpath = File.join('@executable_path', rel_path)
rpaths = `otool -l "#{exe}" | grep -A 2 'cmd LC_RPATH' | grep 'path'`
unless rpaths.include?(rpath)
while_writable(exe) do
system('install_name_tool', '-add_rpath',
File.join('@executable_path', rel_path), exe)
end
end
`otool -L "#{exe}"`.split("\n")[1..-1].each do |line|
match = line.match(%r{^\s+(.+/(lib[^/ ]+))\s})
next unless match && match[1].start_with?(lib_source)
while_writable(exe) do
if match[2] == exe_file
system('install_name_tool', '-id',
File.join('@executable_path', rel_path, match[2].to_s), exe)
else
system('install_name_tool', '-change', match[1],
File.join('@executable_path', rel_path, match[2].to_s), exe)
end
end
next if match[2] == exe_file || File.exist?(File.join(lib_dir, match[2]))
FileUtils.mkdir_p(lib_dir)
FileUtils.cp(match[1], lib_dir)
copy_libs(File.join(lib_dir, match[2].to_s), rel_path)
end
end
def copy_extra_libs(extra_libs, exe, rel_path = nil)
rel_path ||= Pathname.new(lib_dir).relative_path_from(
Pathname.new(File.dirname(exe))
).to_s
extra_libs.each do |lib|
lib_file = File.basename(lib)
target = "#{lib_dir}/#{lib_file}"
unless File.exist?(target)
FileUtils.mkdir_p(lib_dir)
FileUtils.cp(lib, lib_dir)
end
while_writable(target) do
system('install_name_tool', '-id',
File.join('@executable_path', rel_path, lib_file), target)
end
copy_libs(target, rel_path)
end
end
def while_writable(file)
mode = File.stat(file).mode
File.chmod(0o775, file)
yield
ensure
File.chmod(mode, file)
end
end
class GccLibEmbedder < AbstractEmbedder
attr_reader :gcc_info
def initialize(app, gcc_info)
super(app)
@gcc_info = gcc_info
end
def embed
if embedded?
info 'libgccjit already embedded in Emacs.app'
return
end
info 'Embedding libgccjit into Emacs.app'
if gcc_info.lib_dir.empty?
err "No suitable GCC lib dir found in #{gcc_info.root_dir}"
end
FileUtils.mkdir_p(File.dirname(target_dir))
FileUtils.cp_r(source_dir, target_dir)
FileUtils.rm(Dir[File.join(target_dir, '**', '.DS_Store')], force: true)
FileUtils.chmod_R('u+w', target_dir)
FileUtils.mv(source_darwin_dir, target_darwin_dir)
end
private
def embedded?
Dir[File.join(target_dir, 'libgcc*')].any?
end
def target_dir
File.join(invocation_dir, gcc_info.relative_lib_dir)
end
def source_darwin_dir
File.join(invocation_dir, gcc_info.relative_darwin_lib_dir)
end
def target_darwin_dir
File.join(invocation_dir, gcc_info.sanitized_relative_darwin_lib_dir)
end
def source_dir
gcc_info.lib_dir
end
def relative_dir(path, root)
Pathname.new(path).relative_path_from(Pathname.new(root)).to_s
end
end
class GccInfo
include Output
def root_dir
@root_dir ||= `brew --prefix gcc`.chomp
end
def major_version
@major_version ||= File.basename(lib_dir)
end
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
# Sanitize folder name with full "MAJOR.MINOR.PATCH" version number to just
# the MAJOR version. Apple's codesign CLI tool throws a "bundle format
# unrecognized" error if there are any folders with two dots in their name
# within the Emacs.app application bundle.
def sanitized_relative_darwin_lib_dir
@sanitized_relative_darwin_lib_dir ||= File.join(
File.dirname(relative_darwin_lib_dir),
File.basename(relative_darwin_lib_dir).split('.').first
)
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
return if major_version == libgccjit_major_version
err <<~TEXT
Detected GCC and libgccjit library paths do not belong to the same major
version of GCC. Detected paths:
- #{lib_dir}
- #{libgccjit_lib_dir}
TEXT
end
def get_binding
binding
end
private
def relative_dir(path, root)
Pathname.new(path).relative_path_from(Pathname.new(root)).to_s
end
end
if __FILE__ == $PROGRAM_NAME
cli_options = {
work_dir: File.expand_path(__dir__),
native_full_aot: false,
native_march: false,
parallel: Etc.nprocessors,
rsvg: true,
xwidgets: true,
github_auth: true
}
begin
OptionParser.new do |opts|
opts.banner = <<~DOC
Usage: ./build-emacs-for-macos [options] <branch/tag/sha>
Branch, tag, and SHA are from the emacs-mirror/emacs/emacs Github repo,
available here: https://github.com/emacs-mirror/emacs
Options:
DOC
opts.on('-j', '--parallel COUNT',
'Compile using COUNT parallel processes ' \
"(detected: #{cli_options[:parallel]})") do |v|
cli_options[:parallel] = v
end
opts.on('--git-sha SHA',
'Override detected git SHA of specified ' \
'branch allowing builds of old commits') do |v|
cli_options[:git_sha] = v
end
opts.on('--[no-]xwidgets',
'Enable/disable XWidgets if supported ' \
'(default: enabled)') do |v|
cli_options[:xwidgets] = v
end
opts.on('--[no-]native-comp',
'Enable/disable native-comp ' \
'(default: enabled if supported)') do |v|
cli_options[:native_comp] = v
end
opts.on('--[no-]native-march',
'Enable/disable -march=native CFLAG' \
'(default: disabled)') do |v|
cli_options[:native_march] = v
end
opts.on('--[no-]native-full-aot',
'Enable/disable NATIVE_FULL_AOT / Ahead of Time compilation ' \
'(default: disabled)') do |v|
cli_options[:native_full_aot] = v
end
opts.on('--[no-]rsvg',
'Enable/disable SVG image support via librsvg ' \
'(default: enabled)') do |v|
cli_options[:rsvg] = v
end
opts.on('--no-titlebar', 'Apply no-titlebar patch (default: disabled)') do
cli_options[:no_titlebar] = true
end
opts.on('--no-frame-refocus',
'Apply no-frame-refocus patch (default: disabled)') do
cli_options[:no_frame_refocus] = true
end
opts.on('--[no-]github-auth',
'Make authenticated GitHub API requests if GITHUB_TOKEN ' \
'environment variable is set.' \
'(default: enabled)') do |v|
cli_options[:github_auth] = v
end
opts.on('--work-dir DIR',
'Specify a working directory where tarballs, sources, and ' \
'builds will be stored and worked with') do |v|
cli_options[:work_dir] = v
end
opts.on(
'--plan FILE',
'Follow given plan file, instead of using given git ref/sha'
) do |v|
cli_options[:plan] = v
end
end.parse!
work_dir = cli_options.delete(:work_dir)
Build.new(work_dir, ARGV.shift, cli_options).build
rescue Error => e
warn "ERROR: #{e.message}"
exit 1
end
end