Skip to content

Commit 6e3664b

Browse files
jaimergpisuruf
andcommitted
add blog/2020-10-29-macos-arm64.md
Co-authored-by: isuruf <[email protected]>
1 parent f0163cf commit 6e3664b

File tree

1 file changed

+282
-0
lines changed

1 file changed

+282
-0
lines changed

blog/2020-10-29-macos-arm64.md

Lines changed: 282 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,282 @@
1+
---
2+
authors:
3+
- isuruf
4+
tags: [conda-forge]
5+
---
6+
7+
# macOS ARM builds on conda-forge
8+
9+
A new platform `osx-arm64` has been added to the build matrix of
10+
conda-forge. `osx-arm64` packages are built to run on upcoming macOS
11+
arm64 processors marketed as `Apple Silicon`. An installer for this
12+
platform can be found
13+
[here](https://github.com/conda-forge/miniforge/releases/latest/download/Miniforge3-MacOSX-arm64.sh).
14+
15+
<!--truncate-->
16+
17+
This will install a conda environment with python and conda in it.
18+
Installed conda will be able to install packages like `numpy, scipy`.
19+
Currently there are about 100 packages out of 10000 packages pre-built
20+
for this platform.
21+
22+
All these packages are built on conda-forge's current macOS `x86_64`
23+
infrastructure. In order to do so, we have made lots of changes to the
24+
infrastructure including,
25+
`conda, conda-build, conda-smithy, constructor, conda-forge-ci-setup` to
26+
facilitate cross-compiling which is the process of compiling a package
27+
that will run on a `host` platform (`osx-arm64` in our case), with the
28+
compilation done on a `build` platform (`osx-64` or `linux-64` in our
29+
case).
30+
31+
`osx-arm64` is the first conda platform that is completely bootstrapped
32+
using conda-build's cross-compiling facility. Previously, when adding a
33+
new platform, conda-build was built with an existing python and pip
34+
environment on the new platform. With cross-compiling, when the
35+
compilers and a sysroot is set up on a different platform, an existing
36+
conda-build installation (on `osx-64` and `linux-64` in this case) will
37+
be able to start building packages right away.
38+
39+
## Cross-compiling builds for `osx-arm64`
40+
41+
In order to cross compile packages for `osx-arm64` we need compilers.
42+
So, we first built `clang=11.0.0.rc1` which has support for targetting
43+
`osx-arm64`. We also built `compiler-rt=11.0.0.rc1` as a universal build
44+
support both `osx-64` and `osx-arm64`.
45+
46+
Linker, archiver, `otool`, `install_name_tool` was built using the
47+
[cctools-port project](https://github.com/tpoechtrager/cctools-port) by
48+
Thomas Pöchtrager.
49+
50+
One issue we ran into was that the macOS 11.0 Big Sur Beta 7 required
51+
that all executables and shared libraries be ad-hoc signed which is
52+
signing without a verified signature. On suggestion of `cctools-port`
53+
developer we added support to `cctools-port` to sign these executables
54+
using `ldid` which can be used on Linux as well as macOS to sign.
55+
56+
Using these, the first cross compiled package we built was `libcxx` to
57+
facilitate C++ builds. For the `osx-arm64` sysroot we used the
58+
`MacOSX11.0.sdk` already installed on Azure pipelines and Travis-CI. Due
59+
to licensing issues, we cannot distribute this, but it can be downloaded
60+
from the Apple developer website even on Linux.
61+
62+
With clang we have a C/C++ compiler, but lack a Fortran compiler. We
63+
used the [GCC fork for
64+
darwin-arm64](https://github.com/iains/gcc-darwin-arm64). First, a cross
65+
compiler (`build == host != target`) was built. Using that compiler, we
66+
built a `cross-native` compiler (`build != host == target`) which gave
67+
use the shared libraries like `libgfortran.dylib`.
68+
69+
We also added support for cross compiling rust programs to the rust
70+
packages in conda and installing `rust_osx-64` on Linux will give you a
71+
compiler that will build packages for `osx-64`.
72+
73+
As we haven't done cross-compilation before, many packages needed to be
74+
updated. Most were trivial changes that we automated later on. These
75+
included getting a newer `config.sub` to identify the new autotools
76+
platform `arm64-apple-darwin20.0.0`, adding options to CMake with the
77+
environment variable `CMAKE_ARGS` to correctly set up the toolchain and
78+
recipes were update to use `cmake ${CMAKE_ARGS} ..`. Running tests when
79+
building were also disabled by guarding commands like `make check`,
80+
`make test`, `ctest` with the env variable
81+
`CONDA_BUILD_CROSS_COMPILATION`.
82+
83+
Cross-compiling python extensions is quite tricky as `distutils` is not
84+
really setup to do this. Thanks to the project
85+
[crossenv](https://github.com/benfogle/crossenv) this is unofficially
86+
supported with a few quirks. With `crossenv`, we can run a python on the
87+
build machine (`osx-64` or `linux-64` in this case) that acts like it is
88+
on `osx-arm64`. `crossenv` monkey-patches a few functions like
89+
`os.uname` and sets up values like `_PYTHON_SYSCONFIG_DATA` to make
90+
python running on `osx-64` or `linux-64` behave like `osx-arm64`. One
91+
issue is that, monkey-patching `sys.platform` doesn't work and
92+
therefore if a python package in it's `setup.py` uses `sys.platform` to
93+
differentiate OSes this will lead to unintended consequences if you are
94+
cross-compiling from `linux-64`. Therefore, we have to use `osx-64` as
95+
our `build` system when cross-compiling for `osx-arm64`. Note that
96+
packages using `sysconfig.get_platform()` will get the correct platform.
97+
98+
For creating an installer for conda, we needed a standalone conda
99+
executable to bootstrap the conda environment. For other platforms we
100+
relied on `conda-standalone` which is a standalone conda executable
101+
created using `pyinstaller`. Since `pyinstaller` does not support
102+
cross-compile, we decided to use `micromamba` as the bootstrapper and
103+
added features to `micromamba` so that it can function as the
104+
bootstrapper.
105+
106+
## How to add a `osx-arm64` build to a feedstock
107+
108+
All the below changes will be done by a bot and the packages the bot
109+
will send PRs to is determined by the list of packages at
110+
[conda-forge-pinning](https://github.com/conda-forge/conda-forge-pinning-feedstock/blob/master/recipe/migrations/osx_arm64.txt)
111+
and their dependences. If you would like to add support, please send a
112+
PR adding the feedstock name to the above list. After that PR is merged,
113+
you can monitor the status at [conda-forge
114+
status-page](https://conda-forge.org/status/#armosxaddition) and if a
115+
particular PR is stalled you can send a PR to the feedstock to fix it.
116+
117+
Following instructions are for when you want to add support manually.
118+
119+
Add the following to `conda-forge.yml` (on Linux or OSX),
120+
121+
```yaml
122+
build_platform:
123+
osx_arm64: osx_64
124+
test: native_and_emulated
125+
```
126+
127+
You can rerender using,
128+
129+
```bash
130+
conda smithy rerender
131+
```
132+
133+
For python packages, add one or more of the following to
134+
`recipe/meta.yaml` as needed, noting that you *must* only add
135+
`numpy`, `cython`, and/or `pybind11`
136+
if they are used in `host:` as well,
137+
138+
```yaml
139+
requirements:
140+
build:
141+
- python # [build_platform != target_platform]
142+
- cross-python_{{ target_platform }} # [build_platform != target_platform]
143+
- cython # [build_platform != target_platform]
144+
- numpy # [build_platform != target_platform]
145+
- pybind11 # [build_platform != target_platform]
146+
```
147+
148+
For autotools package, add the following to `recipe/meta.yaml`,
149+
150+
```yaml
151+
requirements:
152+
build:
153+
- gnuconfig # [unix]
154+
```
155+
156+
and to `recipe/build.sh`,
157+
158+
```bash
159+
# Get an updated config.sub and config.guess
160+
cp $BUILD_PREFIX/share/gnuconfig/config.* .
161+
```
162+
163+
For cmake packages, add the following to `recipe/build.sh`,
164+
165+
```bash
166+
cmake ${CMAKE_ARGS} ..
167+
```
168+
169+
For `meson` packages, add the following to `recipe/build.sh`,
170+
171+
```bash
172+
meson ${MESON_ARGS} builddir/
173+
```
174+
175+
:::note
176+
Conda automatically creates a [cross build definition
177+
file](https://mesonbuild.com/Cross-compilation.html) when
178+
cross-compiling, and adds the necessary argument to `${MESON_ARGS}` to
179+
point `meson` to that file. `${MESON_ARGS}` is only defined when
180+
cross-compiling, not for normal builds.
181+
:::
182+
183+
For rust packages, add the following to `recipe/meta.yaml`,
184+
185+
```bash
186+
requirements:
187+
build:
188+
- {{ compiler('rust') }}
189+
```
190+
191+
If there's a line like `make check` in `recipe/build.sh` that cannot be
192+
run when cross-compiling, do the following,
193+
194+
```bash
195+
if [[ "$CONDA_BUILD_CROSS_COMPILATION" != "1" ]]; then
196+
make check
197+
fi
198+
```
199+
200+
After these changes, another rerendering might be required.
201+
202+
Some useful jinja variables,
203+
204+
1. `build_platform` - conda subdir for `BUILD_PREFIX`. eg:
205+
`linux-64`
206+
2. `target_platform` - conda subdir for `PREFIX`. eg: `osx-arm64`
207+
208+
Some useful environment variables,
209+
210+
1. `build_platform`
211+
2. `target_platform`
212+
3. `CONDA_BUILD_CROSS_COMPILATION` - 1 if cross compiling
213+
4. `CMAKE_ARGS` - arguments to pass to cmake
214+
5. `CC_FOR_BUILD` - C compiler for build platform
215+
6. `CXX_FOR_BUILD` - C++ compiler for build platform
216+
7. `HOST` - a triplet for host passed to autoconf. eg:
217+
`arm64-apple-darwin20.0.0`
218+
8. `BUILD` - a triplet for build passed to autoconf. eg:
219+
`x86_64-conda-linux-gnu`
220+
221+
Some useful configure options in `conda-forge.yml`
222+
223+
1. `build_platform` - a dictionary mapping `build` subdir to `host` subdir. eg:
224+
225+
```yaml
226+
build_platform:
227+
osx_arm64: osx_64
228+
linux_ppc64le: linux_64
229+
linux_aarch64: linux_64
230+
```
231+
232+
2. `test_on_native_only` - a boolean to turn off testing on cross
233+
compiling. If the tests don't require emulation (for eg: check
234+
that a file exists), then `test_on_native_only: false` will run
235+
the tests even when cross compiling.
236+
237+
## Building locally
238+
239+
For building locally add the following in
240+
`$HOME/conda_build_config.yaml`.
241+
242+
```yaml
243+
SDKROOT:
244+
- /path/to/MacOSX11.0.sdk
245+
```
246+
247+
After that, look for the config you want to run in `.ci_support` folder
248+
in the root of the feedstock For eg: `.ci_support/osx_arm64_.yaml`. Then
249+
run,
250+
251+
```bash
252+
conda build recipe -m .ci_support/osx_arm64_.yaml -c conda-forge -c conda-forge/label/rust_dev
253+
```
254+
255+
This should start a new build for `osx-arm64`.
256+
257+
## Testing packages
258+
259+
In order to test packages intended to run on future Apple Silicon
260+
hardware, Apple provides a machine called Developer Transition Kit
261+
(DTK). Jonathan Helmus and Eli Rykoff has helped with testing these
262+
packages on DTKs. Thanks to Eli Rykoff, we are now running tests for
263+
these packages as a daily cron job which has led to finding several bugs
264+
in our cross compiling infrastructure and also bugs in our recipes.
265+
266+
To test cross compiled recipes, transfer the built conda package to the
267+
`host` and run,
268+
269+
```bash
270+
conda build --test /path/to/package -c conda-forge
271+
```
272+
273+
This work would not have been possible without the help of many people
274+
including the upstream maintainers of compiler infrastructure (which
275+
includes conda, conda-build, cctools, tapi, cctools-port, ldid, llvm,
276+
clang, compiler-rt, openmp, libcxx, crossenv, rust, gcc-darwin-arm64),
277+
`conda-forge/help-osx-arm64` team including Matt Becker, Eli Rykoff and
278+
Uwe Korn who sent PRs to fix recipes, `conda-forge/bot` team and also
279+
all the conda-forge maintainers of the 100 feedstocks who reviewed and
280+
fixed PRs.
281+
282+
Isuru Fernando

0 commit comments

Comments
 (0)