From fe460a824ee57b602d29854167feab1b9f032aef Mon Sep 17 00:00:00 2001 From: Jim Myhrberg Date: Tue, 18 Aug 2020 01:49:24 +0100 Subject: [PATCH] feat(native_comp): Add support for --with-nativecomp See https://akrl.sdf.org/gccemacs.html for more info. --- Brewfile | 1 + Formulas/gcc.rb | 155 +++++++++++++++++++++++++++++++++++++ build-emacs-for-macos | 173 ++++++++++++++++++++++++++++++------------ install-patched-gcc | 4 + 4 files changed, 283 insertions(+), 50 deletions(-) create mode 100644 Formulas/gcc.rb create mode 100755 install-patched-gcc diff --git a/Brewfile b/Brewfile index 218a835..0300bf0 100644 --- a/Brewfile +++ b/Brewfile @@ -3,6 +3,7 @@ brew 'curl' brew 'expat' brew 'gmp' +brew 'gnu-sed' brew 'gnutls' brew 'jansson' brew 'libffi' diff --git a/Formulas/gcc.rb b/Formulas/gcc.rb new file mode 100644 index 0000000..c361709 --- /dev/null +++ b/Formulas/gcc.rb @@ -0,0 +1,155 @@ +# frozen_string_literal: true + +class Gcc < Formula + desc 'GNU compiler collection' + homepage 'https://gcc.gnu.org/' + url 'https://ftp.gnu.org/gnu/gcc/gcc-10.2.0/gcc-10.2.0.tar.xz' + mirror 'https://ftpmirror.gnu.org/gcc/gcc-10.2.0/gcc-10.2.0.tar.xz' + sha256 'b8dd4368bb9c7f0b98188317ee0254dd8cc99d1e3a18d0ff146c855fe16c1d8c' + license 'GPL-3.0' + head 'https://gcc.gnu.org/git/gcc.git' + + bottle do + sha256 '8dbccea194c20b1037b7e8180986e98a8ee3e37eaac12c7d223c89be3deaac6a' => :catalina + sha256 '79d2293ce912dc46af961f30927b31eb06844292927be497015496f79ac41557' => :mojave + sha256 '5ed870a39571614dc5d83be26d73a4164911f4356b80d9345258a4c1dc3f1b70' => :high_sierra + end + + # The bottles are built on systems with the CLT installed, and do not work + # out of the box on Xcode-only systems due to an incorrect sysroot. + pour_bottle? do + reason 'The bottle needs the Xcode CLT to be installed.' + satisfy { MacOS::CLT.installed? } + end + + depends_on 'gmp' + depends_on 'isl' + depends_on 'libmpc' + depends_on 'mpfr' + + uses_from_macos 'zlib' + + # GCC bootstraps itself, so it is OK to have an incompatible C++ stdlib + cxxstdlib_check :skip + + def version_suffix + if build.head? + 'HEAD' + else + version.to_s.slice(/\d+/) + end + end + + def install + # GCC will suffer build errors if forced to use a particular linker. + ENV.delete 'LD' + + # We avoiding building: + # - Ada, which requires a pre-existing GCC Ada compiler to bootstrap + # - Go, currently not supported on macOS + # - BRIG + languages = %w[c c++ objc obj-c++ fortran jit] + + osmajor = `uname -r`.split('.').first + pkgversion = "Homebrew GCC #{pkg_version} #{build.used_options * ' '}".strip + + args = %W[ + --build=x86_64-apple-darwin#{osmajor} + --prefix=#{prefix} + --libdir=#{lib}/gcc/#{version_suffix} + --disable-nls + --enable-checking=release + --enable-languages=#{languages.join(',')} + --program-suffix=-#{version_suffix} + --with-gmp=#{Formula['gmp'].opt_prefix} + --with-mpfr=#{Formula['mpfr'].opt_prefix} + --with-mpc=#{Formula['libmpc'].opt_prefix} + --with-isl=#{Formula['isl'].opt_prefix} + --with-system-zlib + --with-pkgversion=#{pkgversion} + --with-bugurl=https://github.com/Homebrew/homebrew-core/issues + --enable-host-shared + ] + + # Xcode 10 dropped 32-bit support + args << '--disable-multilib' if DevelopmentTools.clang_build_version >= 1000 + + # System headers may not be in /usr/include + sdk = MacOS.sdk_path_if_needed + if sdk + args << '--with-native-system-header-dir=/usr/include' + args << "--with-sysroot=#{sdk}" + end + + # Avoid reference to sed shim + args << 'SED=/usr/bin/sed' + + # Ensure correct install names when linking against libgcc_s; + # see discussion in https://github.com/Homebrew/legacy-homebrew/pull/34303 + inreplace 'libgcc/config/t-slibgcc-darwin', '@shlib_slibdir@', "#{HOMEBREW_PREFIX}/lib/gcc/#{version_suffix}" + + mkdir 'build' do + system '../configure', *args + + # Use -headerpad_max_install_names in the build, + # otherwise updated load commands won't fit in the Mach-O header. + # This is needed because `gcc` avoids the superenv shim. + system 'make', 'BOOT_LDFLAGS=-Wl,-headerpad_max_install_names' + system 'make', 'install' + + bin.install_symlink bin / "gfortran-#{version_suffix}" => 'gfortran' + end + + # Handle conflicts between GCC formulae and avoid interfering + # with system compilers. + # Rename man7. + Dir.glob(man7 / '*.7') { |file| add_suffix file, version_suffix } + # Even when we disable building info pages some are still installed. + info.rmtree + end + + def add_suffix(file, suffix) + dir = File.dirname(file) + ext = File.extname(file) + base = File.basename(file, ext) + File.rename file, "#{dir}/#{base}-#{suffix}#{ext}" + end + + test do + (testpath / 'hello-c.c').write <<~EOS + #include + int main() + { + puts("Hello, world!"); + return 0; + } + EOS + system "#{bin}/gcc-#{version_suffix}", '-o', 'hello-c', 'hello-c.c' + assert_equal "Hello, world!\n", `./hello-c` + + (testpath / 'hello-cc.cc').write <<~EOS + #include + int main() + { + std::cout << "Hello, world!" << std::endl; + return 0; + } + EOS + system "#{bin}/g++-#{version_suffix}", '-o', 'hello-cc', 'hello-cc.cc' + assert_equal "Hello, world!\n", `./hello-cc` + + (testpath / 'test.f90').write <<~EOS + integer,parameter::m=10000 + real::a(m), b(m) + real::fact=0.5 + + do concurrent (i=1:m) + a(i) = a(i) + fact*b(i) + end do + write(*,"(A)") "Done" + end + EOS + system "#{bin}/gfortran", '-o', 'test', 'test.f90' + assert_equal "Done\n", `./test` + end +end diff --git a/build-emacs-for-macos b/build-emacs-for-macos index e9dddba..b02245b 100755 --- a/build-emacs-for-macos +++ b/build-emacs-for-macos @@ -7,10 +7,16 @@ require 'json' require 'optparse' require 'pathname' +def err(msg = nil) + warn("ERROR: #{msg}") if msg + exit 1 +end + class Build DOWNLOAD_URL = 'https://github.com/emacs-mirror/emacs/tarball/%s' LATEST_URL = 'https://api.github.com/repos/' \ - 'emacs-mirror/emacs/commits?sha=%s' + 'emacs-mirror/emacs/commits?sha=%s' + NATIVE_COMP_REF_REGEXP = %r{^feature/native-comp}.freeze attr_reader :root_dir attr_reader :ref @@ -24,7 +30,7 @@ class Build def build unless meta[:sha] && meta[:date] - raise 'ERROR: Failed to get commit info from GitHub API.' + err 'Failed to get commit info from GitHub API.' end tarball = download_tarball(meta[:sha]) @@ -54,6 +60,10 @@ class Build @brew_dir ||= `brew --prefix`.chomp end + def gcc_dir + @gcc_dir ||= `brew --prefix gcc`.chomp + end + def extra_libs @extra_libs ||= [ "#{brew_dir}/opt/expat/lib/libexpat.1.dylib", @@ -77,7 +87,7 @@ class Build puts 'Downloading tarball from GitHub. This could take a while, ' \ 'please be patient.' result = run_cmd('curl', '-L', url, '-o', target) - raise 'ERROR: Download failed.' unless result + err 'Download failed.' unless result target end @@ -95,13 +105,28 @@ class Build puts 'Extracting tarball...' result = run_cmd('tar', '-xzf', filename, '-C', source_dir) - raise 'ERROR: Tarball extraction failed.' unless result + err 'Tarball extraction failed.' unless result patches.each { |patch| apply_patch(patch, target) } target end + def detect_native_comp + return if `./configure --help | grep -- '--with-nativecomp'`.strip != '' + + err 'This emacs source tree does not support native-comp' + end + + def detect_libgccjit + err 'gcc not installed' unless Dir.exist?(gcc_dir) + + return if Dir["#{gcc_dir}/lib/**/libgccjit.so*"].any? + + err "Detected GCC (#{gcc_dir}) does not have libgccjit. Ensure patched " \ + 'gcc brew formula has been installed via ./install-patched-gcc' + end + def compile_source(source) target = "#{source}/nextstep" emacs_app = "#{target}/Emacs.app" @@ -114,35 +139,6 @@ class Build puts 'Compiling from source. This will take a while...' - ENV['CC'] = 'cc' - ENV['PKG_CONFIG_PATH'] = [ - "#{brew_dir}/lib/pkgconfig", - "#{brew_dir}/share/pkgconfig", - "#{brew_dir}/opt/expat/lib/pkgconfig", - "#{brew_dir}/opt/libxml2/lib/pkgconfig", - "#{brew_dir}/opt/ncurses/lib/pkgconfig", - "#{brew_dir}/opt/zlib/lib/pkgconfig", - "#{brew_dir}/Homebrew/Library/Homebrew/os/mac/pkgconfig/#{os.version}", - ENV['PKG_CONFIG_PATH'] - ].compact.join(':') - - ENV['PATH'] = [ - "#{brew_dir}/bin", - "#{brew_dir}/opt/texinfo/bin", - ENV['PATH'] - ].compact.join(':') - - configure_flags = [ - '--with-ns', - '--with-modules', - '--enable-locallisppath=' \ - '/Library/Application Support/Emacs/${version}/site-lisp:' \ - '/Library/Application Support/Emacs/site-lisp' - ] - configure_flags << '--with-xwidgets' if options[:xwidgets] - - parallel_flags = options[:parallel] ? ['-j', options[:parallel]] : [] - FileUtils.cd(source) do if File.exist?('autogen/copy_autogen') run_cmd 'autogen/copy_autogen' @@ -150,6 +146,63 @@ class Build run_cmd './autogen.sh' end + if options[:native_comp] + detect_native_comp + detect_libgccjit + + ENV['NATIVE_FAST_BOOT'] = '1' if options[:native_fast_boot] + ENV['CFLAGS'] = [ + "-I#{gcc_dir}/include", + '-O2', + '-march=native' + ].compact.join(' ') + + ENV['LDFLAGS'] = [ + "-L#{gcc_dir}/lib/gcc/10", + "-I#{gcc_dir}/include" + ].compact.join(' ') + + ENV['LIBRARY_PATH'] = [ + "#{gcc_dir}/lib/gcc/10", + ENV['LIBRARY_PATH'] + ].compact.join(':') + end + + ENV['CC'] = 'clang' + ENV['PKG_CONFIG_PATH'] = [ + "#{brew_dir}/lib/pkgconfig", + "#{brew_dir}/share/pkgconfig", + "#{brew_dir}/opt/expat/lib/pkgconfig", + "#{brew_dir}/opt/libxml2/lib/pkgconfig", + "#{brew_dir}/opt/ncurses/lib/pkgconfig", + "#{brew_dir}/opt/zlib/lib/pkgconfig", + "#{brew_dir}/Homebrew/Library/Homebrew/os/mac/pkgconfig/#{os.version}", + ENV['PKG_CONFIG_PATH'] + ].compact.join(':') + + ENV['PATH'] = [ + "#{brew_dir}/opt/gnu-sed/libexec/gnubin", + "#{brew_dir}/bin", + "#{brew_dir}/opt/texinfo/bin", + ENV['PATH'] + ].compact.join(':') + + configure_flags = [ + '--with-ns', + '--with-modules', + '--enable-locallisppath=' \ + '/Library/Application Support/Emacs/${version}/site-lisp:' \ + '/Library/Application Support/Emacs/site-lisp' + ] + configure_flags << '--with-xwidgets' if options[:xwidgets] + configure_flags << '--with-nativecomp' if options[:native_comp] + + make_flags = [] + make_flags += ['-j', options[:parallel]] if options[:parallel] + if options[:native_comp] + make_flags << "BYTE_COMPILE_EXTRA_FLAGS=--eval '(setq comp-speed 2)'" + end + run_cmd './configure', *configure_flags # Disable aligned_alloc on Mojave and below. See issue: @@ -159,11 +212,11 @@ class Build disable_alligned_alloc end - run_cmd 'make', *parallel_flags + run_cmd 'make', *make_flags run_cmd 'make', 'install' end - raise 'ERROR: Build failed.' unless File.exist?(emacs_app) + err 'Build failed.' unless File.exist?(emacs_app) emacs_app end @@ -171,7 +224,13 @@ class Build def archive_app(app) FileUtils.mkdir_p(build_dir) - metadata = [ref, meta[:date], meta[:sha][0..6], "macOS-#{os.version}"] + metadata = [ + ref.gsub(/\W/, '-'), + meta[:date], + meta[:sha][0..6], + "macOS-#{os.version}", + arch + ] filename = "Emacs.app-[#{metadata.join('][')}].tbz" target = "#{build_dir}/#{filename}" @@ -204,6 +263,10 @@ class Build end end + def arch + @arch = `uname -m`.strip + end + def disable_alligned_alloc filename = 'src/config.h' content = File.read(filename) @@ -233,7 +296,7 @@ class Build def run_cmd(*args) puts '==> ' + args.join(' ') - system(*args) + system(*args) || exit($CHILD_STATUS.exitstatus) end def patch_version @@ -241,7 +304,7 @@ class Build case ref when /^emacs-27.*/ 'emacs-27' - when /^emacs-28.*/, 'master' + when /^emacs-28.*/, NATIVE_COMP_REF_REGEXP, 'master' 'emacs-28' end end @@ -251,7 +314,7 @@ class Build p = [] if patch_version - if opts[:xwidgets] + if opts[:xwidgets] && patch_version == 'emacs-27' p << { url: 'https://github.com/d12frosted/homebrew-emacs-plus/raw/master/' \ "patches/#{patch_version}/xwidgets_webkit_in_cocoa.patch" @@ -272,10 +335,10 @@ class Build end def apply_patch(patch, target) - raise "ERROR: \"#{target}\" does not exist." unless File.exist?(target) + err "\"#{target}\" does not exist." unless File.exist?(target) if patch[:url] - system('mkdir', '-p', "#{target}/patches") + run_cmd('mkdir', '-p', "#{target}/patches") patch_file = "#{target}/patches/patch-{num}.diff" num = 1 @@ -285,24 +348,22 @@ class Build patch_file = patch_file.gsub('{num}', num.to_s.rjust(3, '0')) puts "Downloading patch: #{patch[:url]}" - system('curl', '-L#', patch[:url], '-o', patch_file) + run_cmd('curl', '-L#', patch[:url], '-o', patch_file) puts 'Applying patch...' - FileUtils.cd(target) { system('patch', '-f', '-p1', '-i', patch_file) } + FileUtils.cd(target) { run_cmd('patch', '-f', '-p1', '-i', patch_file) } elsif patch[:replace] - raise 'ERROR: Patch replace input error' unless patch[:replace].size == 3 + err 'Patch replace input error' unless patch[:replace].size == 3 file, before, after = patch[:replace] filepath = File.join(target, file) - unless File.exist?(filepath) - raise "ERROR: \"#{file}\" does not exist in #{target}" - end + err "\"#{file}\" does not exist in #{target}" unless File.exist?(filepath) f = File.open(filepath, 'rb') s = f.read sub = s.gsub!(before, after) - raise "ERROR: Replacement filed in #{file}" if sub.nil? + err "Replacement filed in #{file}" if sub.nil? f.reopen(filepath, 'wb').write(s) f.close @@ -317,7 +378,7 @@ class LibEmbedder attr_reader :extra_libs def initialize(app, lib_source, macos_version, extra_libs = []) - raise "ERROR: #{app} does not exist" unless File.exist?(app) + err "#{app} does not exist" unless File.exist?(app) @app = app @lib_source = lib_source @@ -337,12 +398,16 @@ class LibEmbedder private + def arch + @arch = `uname -m`.strip + end + def bin "#{app}/Contents/MacOS/Emacs" end def lib_dir - "#{app}/Contents/MacOS/lib-x86_64-#{macos_version}" + "#{app}/Contents/MacOS/lib-#{arch}-#{macos_version}" end def copy_libs(exe, rel_path = nil) @@ -444,6 +509,14 @@ if __FILE__ == $PROGRAM_NAME opts.on('-x', '--xwidgets', 'Apply XWidgets patch for Emacs 27') do cli_options[:xwidgets] = true end + + opts.on('--native-comp', 'Enable native-comp') do + cli_options[:native_comp] = true + end + + opts.on('--native-fast-boot', 'Only relevant with --native-comp') do + cli_options[:native_fast_boot] = true + end end.parse! Build.new(File.expand_path(__dir__), ARGV.shift, cli_options).build diff --git a/install-patched-gcc b/install-patched-gcc new file mode 100755 index 0000000..5f2edae --- /dev/null +++ b/install-patched-gcc @@ -0,0 +1,4 @@ +#! /usr/bin/env bash + +export HOMEBREW_NO_AUTO_UPDATE=1 +brew install ./Formulas/gcc.rb --build-from-source --force