Skip to content

Commit 9817aba

Browse files
committed
Support for building universal x86 / Apple Silicon (arm64) app
This adds support for building MacVim as a fat binary (aka universal app) for x86_64 / arm64 in CI. The main challenge mostly lies in configuring the scripting language default search paths for the libraries, and linking against gettext. There are two possible approaches: 1. configure/build each arch completely separately, and then use `lipo` to stitch them back together. This is pretty annoying to set up, and kind of manual to do, and requires building the same thing twice, which is not great. 2. Build once with `--with-macarchs="x86_64 arm64` flag, which is what we do here. gettext: Homebrew doesn't support fat binaries, and we also need to build a custom x86 version of gettext to support down to macOS 10.9 anyway, so we manually download the bottle for arm64 gettext bottle, and then stitch it with the x86 version to create a unified binary under /usr/local/lib. This way we can just link against it in one go. Scripting languages: Add new ifdef's to load different libs under different architecture. Modify configure to support that (instead of hacking a patch in during CI like Ruby). This means while on x86_64 it will look under /usr/local/lib for Python 3, on arm64 it will look under /opt/homebrew instead (this is the recommended path for Homebrew installs for native arm64 packages). This new path is very specific to Homebrew which is not ideal, but we could change this later and maybe make the default search path logic for scripting languages smarter. Note that since there is no arm64 in CI right now, this just builds the app, but there will be no automatic testing to make sure it actually works. This is part of #1136.
1 parent eb3275e commit 9817aba

File tree

4 files changed

+199
-8
lines changed

4 files changed

+199
-8
lines changed

.github/workflows/ci-macvim.yaml

Lines changed: 63 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,10 @@ env:
1818
vi_cv_dll_name_perl: /System/Library/Perl/5.18/darwin-thread-multi-2level/CORE/libperl.dylib
1919
vi_cv_dll_name_python: /System/Library/Frameworks/Python.framework/Versions/2.7/Python
2020
vi_cv_dll_name_python3: /usr/local/Frameworks/Python.framework/Versions/3.9/Python
21+
vi_cv_dll_name_python3_arm64: /opt/homebrew/Frameworks/Python.framework/Versions/3.9/Python
2122
vi_cv_dll_name_ruby: /usr/local/opt/ruby/lib/libruby.dylib
23+
vi_cv_dll_name_ruby_arm64: /opt/homebrew/opt/ruby/lib/libruby.dylib
24+
vi_cv_dll_name_lua_arm64: /opt/homebrew/lib/liblua.dylib
2225

2326
VIM_BIN: src/MacVim/build/Release/MacVim.app/Contents/MacOS/Vim
2427
MACVIM_BIN: src/MacVim/build/Release/MacVim.app/Contents/MacOS/MacVim
@@ -38,18 +41,27 @@ jobs:
3841
- os: macos-10.15
3942
xcode: 11.7
4043
- os: macos-10.15
41-
publish: true
4244
- os: macos-11.0
45+
publish: true
4346

4447
runs-on: ${{ matrix.os }}
4548

4649
steps:
4750
- uses: actions/checkout@v2
4851

49-
# Set up and install gettext for localization.
52+
# Set up, install, and cache gettext library for localization.
53+
#
5054
# Instead of using the default binary installed by Homebrew, need to build our own because gettext is statically
5155
# linked in MacVim, and need to be built against MACOSX_DEPLOYMENT_TARGET to ensure the built binary will work on
5256
# supported macOS versions.
57+
#
58+
# In addition, to support building a universal MacVim, we need an arm64 version of gettext as well in order to
59+
# create a universal gettext binary to link against (Homebrew only distributes thin binaries and therefore this
60+
# has to be done manually). To do that, we will just pull the bottle directly from Homebrew and patch it in using
61+
# lipo. We can't use normal brew commands to get the bottle because brew doesn't natively support cross-compiling
62+
# and we are running CI on x86_64 Macs. We also don't need to worry about the min deployment target fix on arm64
63+
# because all Apple Silicon Macs have to run on macOS 11+.
64+
5365
- name: Set up gettext
5466
if: matrix.publish
5567
run: |
@@ -71,11 +83,12 @@ jobs:
7183
brew uninstall --ignore-dependencies gettext
7284
7385
- name: Cache gettext
86+
id: cache-gettext
7487
if: matrix.publish
7588
uses: actions/cache@v2
7689
with:
7790
path: /usr/local/Cellar/gettext
78-
key: gettext-homebrew-cache-${{ runner.os }}-${{ hashFiles('gettext.rb') }}
91+
key: gettext-homebrew-cache-patched-unified-${{ hashFiles('gettext.rb') }}
7992

8093
- name: Install gettext
8194
if: matrix.publish
@@ -85,6 +98,37 @@ jobs:
8598
brew install -s gettext.rb # This will be a no-op if gettext was cached
8699
brew link gettext # If gettext was cached, this step is necessary to relink it to /usr/local/
87100
101+
- name: Create universal gettext with arm64 bottle
102+
if: matrix.publish && steps.cache-gettext.outputs.cache-hit != 'true'
103+
env:
104+
HOMEBREW_NO_AUTO_UPDATE: 1
105+
run: |
106+
set -o verbose
107+
108+
# Manually download and extract gettext bottle for arm64
109+
gettext_url=$(brew info --json gettext | ruby -rjson -e 'j = JSON.parse(STDIN.read); puts j[0]["bottle"]["stable"]["files"]["arm64_big_sur"]["url"]')
110+
gettext_ver=$(brew info --json gettext | ruby -rjson -e 'j = JSON.parse(STDIN.read); puts j[0]["versions"]["stable"]')
111+
112+
mkdir gettext_download
113+
cd gettext_download
114+
wget --no-verbose ${gettext_url}
115+
tar xf gettext*.tar.gz
116+
117+
# Just for diagnostics, print out the old archs. This should be a thin binary (x86_64)
118+
lipo -info /usr/local/lib/libintl.a
119+
lipo -info /usr/local/lib/libintl.dylib
120+
121+
# Create a universal binary by patching the custom built x86_64 one with the downloaded arm64 one.
122+
# Modify the actual binaries in /usr/local/Cellar instead of the symlinks to allow caching to work.
123+
lipo -create -output /usr/local/Cellar/gettext/${gettext_ver}/lib/libintl.a /usr/local/Cellar/gettext/${gettext_ver}/lib/libintl.a ./gettext/${gettext_ver}/lib/libintl.a
124+
lipo -create -output /usr/local/Cellar/gettext/${gettext_ver}/lib/libintl.dylib /usr/local/Cellar/gettext/${gettext_ver}/lib/libintl.dylib ./gettext/${gettext_ver}/lib/libintl.dylib
125+
126+
# Print out the new archs and verify they are universal with 2 archs.
127+
lipo -info /usr/local/lib/libintl.a | grep 'x86_64 arm64'
128+
lipo -info /usr/local/lib/libintl.dylib | grep 'x86_64 arm64'
129+
130+
# Set up remaining packages and tools
131+
88132
- name: Install packages
89133
if: matrix.publish
90134
env:
@@ -101,6 +145,8 @@ jobs:
101145
sudo xcode-select -s /Applications/Xcode_${{ matrix.xcode }}.app/Contents/Developer
102146
xcode-select -p
103147
148+
# All set up steps are done. Build and test MacVim below.
149+
104150
- name: Configure
105151
run: |
106152
set -o verbose
@@ -111,7 +157,6 @@ jobs:
111157
--with-tlib=ncurses
112158
--enable-cscope
113159
--enable-gui=macvim
114-
--with-macarchs=x86_64
115160
--with-compiledby="GitHub Actions"
116161
)
117162
if ${{ matrix.publish == true }}; then
@@ -122,6 +167,11 @@ jobs:
122167
--enable-rubyinterp=dynamic
123168
--enable-luainterp=dynamic
124169
--with-lua-prefix=/usr/local
170+
--with-macarchs="x86_64 arm64"
171+
)
172+
else
173+
CONFOPT+=(
174+
--with-macarchs=x86_64
125175
)
126176
fi
127177
echo "CONFOPT: ${CONFOPT[@]}"
@@ -140,6 +190,12 @@ jobs:
140190
grep -q -- "-DDYNAMIC_PYTHON3_DLL=\\\\\"${vi_cv_dll_name_python3}\\\\\"" src/auto/config.mk
141191
grep -q -- "-DDYNAMIC_RUBY_DLL=\\\\\"${vi_cv_dll_name_ruby}\\\\\"" src/auto/config.mk
142192
193+
# Also search for the arm64 overrides for the default library locations, which are different from x86_64
194+
# because Homebrew puts them at a different place.
195+
grep -q -- "-DDYNAMIC_PYTHON3_DLL_ARM64=\\\\\"${vi_cv_dll_name_python3_arm64}\\\\\"" src/auto/config.mk
196+
grep -q -- "-DDYNAMIC_RUBY_DLL_ARM64=\\\\\"${vi_cv_dll_name_ruby_arm64}\\\\\"" src/auto/config.mk
197+
grep -q -- "-DDYNAMIC_LUA_DLL_ARM64=\\\\\"${vi_cv_dll_name_lua_arm64}\\\\\"" src/auto/config.mk
198+
143199
- name: Show configure output
144200
run: |
145201
cat src/auto/config.mk
@@ -185,12 +241,11 @@ jobs:
185241
echo 'Found external dynamic linkage!'; false
186242
fi
187243
188-
# Make sure we are building x86_64 only. arm64 builds don't work properly now, so we don't want to accidentally build
189-
# it as it will get prioritized by Apple Silicon Macs.
244+
# Make sure we are building universal x86_64 / arm64 builds and didn't accidentally create a thin app.
190245
check_arch() {
191246
local archs=($(lipo -archs "$1"))
192-
if [[ ${archs[@]} != x86_64 ]]; then
193-
echo "Found unexpected arch(s) in $1: ${archs[@]}"; false
247+
if [[ ${archs[@]} != "x86_64 arm64" ]]; then
248+
echo "Wrong arch(s) in $1: ${archs[@]}"; false
194249
fi
195250
}
196251
check_arch "${VIM_BIN}"

src/auto/configure

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5673,6 +5673,20 @@ $as_echo "yes" >&6; }
56735673

56745674
LUA_LIBS=""
56755675
LUA_CFLAGS="-DDYNAMIC_LUA_DLL=\\\"${vi_cv_dll_name_lua}\\\" $LUA_CFLAGS"
5676+
5677+
# MacVim patch to hack in a different default dynamic lib path for
5678+
# arm64. We don't test that it links here so this has to be binary
5679+
# compatible with DYNAMIC_LUA_DLL
5680+
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking liblua${luajit}*.${ext}* (arm64)" >&5
5681+
$as_echo_n "checking liblua${luajit}*.${ext}* (arm64)... " >&6; }
5682+
if test -n "${vi_cv_dll_name_lua_arm64}"; then
5683+
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: ${vi_cv_dll_name_lua_arm64}" >&5
5684+
$as_echo "${vi_cv_dll_name_lua_arm64}" >&6; }
5685+
LUA_CFLAGS+=" -DDYNAMIC_LUA_DLL_ARM64=\\\"${vi_cv_dll_name_lua_arm64}\\\""
5686+
else
5687+
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: <none>" >&5
5688+
$as_echo "<none>" >&6; }
5689+
fi
56765690
fi
56775691
if test "X$LUA_CFLAGS$LUA_LIBS" != "X" && \
56785692
test "x$MACOS_X" = "xyes" && test "x$vi_cv_with_luajit" != "xno" && \
@@ -7178,6 +7192,20 @@ fi
71787192
PYTHON3_OBJ="objects/if_python3.o"
71797193
PYTHON3_CFLAGS="$PYTHON3_CFLAGS -DDYNAMIC_PYTHON3_DLL=\\\"${vi_cv_dll_name_python3}\\\""
71807194
PYTHON3_LIBS=
7195+
7196+
# MacVim patch to hack in a different default dynamic lib path for arm64.
7197+
# We don't test that it links here so this has to be binary compatible with
7198+
# DYNAMIC_PYTHON3_DLL
7199+
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking Python3's dll name (arm64)" >&5
7200+
$as_echo_n "checking Python3's dll name (arm64)... " >&6; }
7201+
if test -n "${vi_cv_dll_name_python3_arm64}"; then
7202+
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: ${vi_cv_dll_name_python3_arm64}" >&5
7203+
$as_echo "${vi_cv_dll_name_python3_arm64}" >&6; }
7204+
PYTHON3_CFLAGS+=" -DDYNAMIC_PYTHON3_DLL_ARM64=\\\"${vi_cv_dll_name_python3_arm64}\\\""
7205+
else
7206+
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: <none>" >&5
7207+
$as_echo "<none>" >&6; }
7208+
fi
71817209
elif test "$python_ok" = yes && test "$enable_pythoninterp" = "dynamic"; then
71827210
$as_echo "#define DYNAMIC_PYTHON 1" >>confdefs.h
71837211

@@ -7224,6 +7252,20 @@ elif test "$python3_ok" = yes && test "$enable_python3interp" = "dynamic"; then
72247252
PYTHON3_OBJ="objects/if_python3.o"
72257253
PYTHON3_CFLAGS="$PYTHON3_CFLAGS -DDYNAMIC_PYTHON3_DLL=\\\"${vi_cv_dll_name_python3}\\\""
72267254
PYTHON3_LIBS=
7255+
7256+
# MacVim patch to hack in a different default dynamic lib path for arm64.
7257+
# We don't test that it links here so this has to be binary compatible with
7258+
# DYNAMIC_PYTHON3_DLL
7259+
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking Python3's dll name (arm64)" >&5
7260+
$as_echo_n "checking Python3's dll name (arm64)... " >&6; }
7261+
if test -n "${vi_cv_dll_name_python3_arm64}"; then
7262+
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: ${vi_cv_dll_name_python3_arm64}" >&5
7263+
$as_echo "${vi_cv_dll_name_python3_arm64}" >&6; }
7264+
PYTHON3_CFLAGS+=" -DDYNAMIC_PYTHON3_DLL_ARM64=\\\"${vi_cv_dll_name_python3_arm64}\\\""
7265+
else
7266+
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: <none>" >&5
7267+
$as_echo "<none>" >&6; }
7268+
fi
72277269
elif test "$python3_ok" = yes; then
72287270
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking if -fPIE can be added for Python3" >&5
72297271
$as_echo_n "checking if -fPIE can be added for Python3... " >&6; }
@@ -7763,6 +7805,23 @@ $as_echo "$rubyhdrdir" >&6; }
77637805

77647806
RUBY_CFLAGS="-DDYNAMIC_RUBY_DLL=\\\"$libruby_soname\\\" $RUBY_CFLAGS"
77657807
RUBY_LIBS=
7808+
7809+
# MacVim patch to hack in a different default dynamic lib path for
7810+
# arm64. We don't test that it links here so this has to be binary
7811+
# compatible with DYNAMIC_RUBY_DLL
7812+
# Note: Apple does ship with a default Ruby lib, but it's usually older
7813+
# than Homebrew, and since on x86_64 we use the Homebrew version, we
7814+
# should use that as well for Apple Silicon.
7815+
{ $as_echo "$as_me:${as_lineno-$LINENO}: checking ${libruby_soname} (arm64)" >&5
7816+
$as_echo_n "checking ${libruby_soname} (arm64)... " >&6; }
7817+
if test -n "${vi_cv_dll_name_ruby_arm64}"; then
7818+
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: ${vi_cv_dll_name_ruby_arm64}" >&5
7819+
$as_echo "${vi_cv_dll_name_ruby_arm64}" >&6; }
7820+
RUBY_CFLAGS+=" -DDYNAMIC_RUBY_DLL_ARM64=\\\"${vi_cv_dll_name_ruby_arm64}\\\""
7821+
else
7822+
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: <none>" >&5
7823+
$as_echo "<none>" >&6; }
7824+
fi
77667825
fi
77677826
else
77687827
{ $as_echo "$as_me:${as_lineno-$LINENO}: result: not found; disabling Ruby" >&5

src/configure.ac

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -777,6 +777,17 @@ if test "$enable_luainterp" = "yes" -o "$enable_luainterp" = "dynamic"; then
777777
AC_DEFINE(DYNAMIC_LUA)
778778
LUA_LIBS=""
779779
LUA_CFLAGS="-DDYNAMIC_LUA_DLL=\\\"${vi_cv_dll_name_lua}\\\" $LUA_CFLAGS"
780+
781+
# MacVim patch to hack in a different default dynamic lib path for
782+
# arm64. We don't test that it links here so this has to be binary
783+
# compatible with DYNAMIC_LUA_DLL
784+
AC_MSG_CHECKING([liblua${luajit}*.${ext}* (arm64)])
785+
if test -n "${vi_cv_dll_name_lua_arm64}"; then
786+
AC_MSG_RESULT([${vi_cv_dll_name_lua_arm64}])
787+
LUA_CFLAGS+=" -DDYNAMIC_LUA_DLL_ARM64=\\\"${vi_cv_dll_name_lua_arm64}\\\""
788+
else
789+
AC_MSG_RESULT([<none>])
790+
fi
780791
fi
781792
if test "X$LUA_CFLAGS$LUA_LIBS" != "X" && \
782793
test "x$MACOS_X" = "xyes" && test "x$vi_cv_with_luajit" != "xno" && \
@@ -1799,6 +1810,17 @@ if test "$python_ok" = yes && test "$python3_ok" = yes; then
17991810
PYTHON3_OBJ="objects/if_python3.o"
18001811
PYTHON3_CFLAGS="$PYTHON3_CFLAGS -DDYNAMIC_PYTHON3_DLL=\\\"${vi_cv_dll_name_python3}\\\""
18011812
PYTHON3_LIBS=
1813+
1814+
# MacVim patch to hack in a different default dynamic lib path for arm64.
1815+
# We don't test that it links here so this has to be binary compatible with
1816+
# DYNAMIC_PYTHON3_DLL
1817+
AC_MSG_CHECKING([Python3's dll name (arm64)])
1818+
if test -n "${vi_cv_dll_name_python3_arm64}"; then
1819+
AC_MSG_RESULT([${vi_cv_dll_name_python3_arm64}])
1820+
PYTHON3_CFLAGS+=" -DDYNAMIC_PYTHON3_DLL_ARM64=\\\"${vi_cv_dll_name_python3_arm64}\\\""
1821+
else
1822+
AC_MSG_RESULT([<none>])
1823+
fi
18021824
elif test "$python_ok" = yes && test "$enable_pythoninterp" = "dynamic"; then
18031825
AC_DEFINE(DYNAMIC_PYTHON)
18041826
PYTHON_SRC="if_python.c"
@@ -1827,6 +1849,17 @@ elif test "$python3_ok" = yes && test "$enable_python3interp" = "dynamic"; then
18271849
PYTHON3_OBJ="objects/if_python3.o"
18281850
PYTHON3_CFLAGS="$PYTHON3_CFLAGS -DDYNAMIC_PYTHON3_DLL=\\\"${vi_cv_dll_name_python3}\\\""
18291851
PYTHON3_LIBS=
1852+
1853+
# MacVim patch to hack in a different default dynamic lib path for arm64.
1854+
# We don't test that it links here so this has to be binary compatible with
1855+
# DYNAMIC_PYTHON3_DLL
1856+
AC_MSG_CHECKING([Python3's dll name (arm64)])
1857+
if test -n "${vi_cv_dll_name_python3_arm64}"; then
1858+
AC_MSG_RESULT([${vi_cv_dll_name_python3_arm64}])
1859+
PYTHON3_CFLAGS+=" -DDYNAMIC_PYTHON3_DLL_ARM64=\\\"${vi_cv_dll_name_python3_arm64}\\\""
1860+
else
1861+
AC_MSG_RESULT([<none>])
1862+
fi
18301863
elif test "$python3_ok" = yes; then
18311864
dnl Check that adding -fPIE works. It may be needed when using a static
18321865
dnl Python library.
@@ -2084,6 +2117,20 @@ if test "$enable_rubyinterp" = "yes" -o "$enable_rubyinterp" = "dynamic"; then
20842117
AC_DEFINE(DYNAMIC_RUBY)
20852118
RUBY_CFLAGS="-DDYNAMIC_RUBY_DLL=\\\"$libruby_soname\\\" $RUBY_CFLAGS"
20862119
RUBY_LIBS=
2120+
2121+
# MacVim patch to hack in a different default dynamic lib path for
2122+
# arm64. We don't test that it links here so this has to be binary
2123+
# compatible with DYNAMIC_RUBY_DLL
2124+
# Note: Apple does ship with a default Ruby lib, but it's usually older
2125+
# than Homebrew, and since on x86_64 we use the Homebrew version, we
2126+
# should use that as well for Apple Silicon.
2127+
AC_MSG_CHECKING([${libruby_soname} (arm64)])
2128+
if test -n "${vi_cv_dll_name_ruby_arm64}"; then
2129+
AC_MSG_RESULT([${vi_cv_dll_name_ruby_arm64}])
2130+
RUBY_CFLAGS+=" -DDYNAMIC_RUBY_DLL_ARM64=\\\"${vi_cv_dll_name_ruby_arm64}\\\""
2131+
else
2132+
AC_MSG_RESULT([<none>])
2133+
fi
20872134
fi
20882135
else
20892136
AC_MSG_RESULT(not found; disabling Ruby)

src/optiondefs.h

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,36 @@ struct vimoption
309309
# define DEFAULT_PYTHON_VER 0
310310
#endif
311311

312+
// Support targeting different dynamic linkages for scripting languages based on
313+
// arch on macOS. This is necessary because package managers such as Homebrew
314+
// distributes thin binaries, and therefore the x86_64 and arm64 libraries are
315+
// located in different places.
316+
#ifdef MACOS_X
317+
# if defined(DYNAMIC_PYTHON3_DLL_X86_64) && defined(__x86_64__)
318+
# undef DYNAMIC_PYTHON3_DLL
319+
# define DYNAMIC_PYTHON3_DLL DYNAMIC_PYTHON3_DLL_X86_64
320+
# elif defined(DYNAMIC_PYTHON3_DLL_ARM64) && defined(__arm64__)
321+
# undef DYNAMIC_PYTHON3_DLL
322+
# define DYNAMIC_PYTHON3_DLL DYNAMIC_PYTHON3_DLL_ARM64
323+
# endif
324+
325+
# if defined(DYNAMIC_RUBY_DLL_X86_64) && defined(__x86_64__)
326+
# undef DYNAMIC_RUBY_DLL
327+
# define DYNAMIC_RUBY_DLL DYNAMIC_RUBY_DLL_X86_64
328+
# elif defined(DYNAMIC_RUBY_DLL_ARM64) && defined(__arm64__)
329+
# undef DYNAMIC_RUBY_DLL
330+
# define DYNAMIC_RUBY_DLL DYNAMIC_RUBY_DLL_ARM64
331+
# endif
332+
333+
# if defined(DYNAMIC_LUA_DLL_X86_64) && defined(__x86_64__)
334+
# undef DYNAMIC_LUA_DLL
335+
# define DYNAMIC_LUA_DLL DYNAMIC_LUA_DLL_X86_64
336+
# elif defined(DYNAMIC_LUA_DLL_ARM64) && defined(__arm64__)
337+
# undef DYNAMIC_LUA_DLL
338+
# define DYNAMIC_LUA_DLL DYNAMIC_LUA_DLL_ARM64
339+
# endif
340+
#endif
341+
312342
// used for 'cinkeys' and 'indentkeys'
313343
#define INDENTKEYS_DEFAULT (char_u *)"0{,0},0),0],:,0#,!^F,o,O,e"
314344

0 commit comments

Comments
 (0)