|
| 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