├── .dockerignore ├── Dockerfile ├── README.md ├── setup-symlinks.sh ├── setup-toolchain.sh └── strip-xcode.sh /.dockerignore: -------------------------------------------------------------------------------- 1 | .git 2 | Dockerfile 3 | README.md 4 | **/*.swp 5 | .dockerignore 6 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:20.04 2 | 3 | RUN apt-get update -qq \ 4 | && DEBIAN_FRONTEND="noninteractive" apt-get install -qqy --no-install-recommends \ 5 | doxygen zip build-essential curl git cmake zlib1g-dev libpng-dev libxml2-dev \ 6 | gobjc python vim-tiny ca-certificates ninja-build \ 7 | && apt-get clean -y \ 8 | && rm -rf /var/lib/apt/lists/* 9 | 10 | WORKDIR /opt 11 | 12 | RUN set -x \ 13 | && curl -LO https://github.com/llvm/llvm-project/releases/download/llvmorg-11.0.0/clang+llvm-11.0.0-x86_64-linux-gnu-ubuntu-20.04.tar.xz \ 14 | && tar -Jxf clang+llvm-11.0.0-x86_64-linux-gnu-ubuntu-20.04.tar.xz \ 15 | && rm clang+llvm-11.0.0-x86_64-linux-gnu-ubuntu-20.04.tar.xz \ 16 | && mv clang+llvm-11.0.0-x86_64-linux-gnu-ubuntu-20.04 clang \ 17 | && cd clang \ 18 | && mkdir bin-new \ 19 | && mv bin/clang-11 bin/clang bin/clang++ bin/dsymutil bin/llvm-nm bin-new \ 20 | && rm -rf bin \ 21 | && mv bin-new bin \ 22 | && rm -rf lib/*.a lib/*.so lib/*.so.* lib/clang/11.0.0/lib/linux include 23 | 24 | RUN set -x \ 25 | && git clone https://github.com/facebook/xcbuild.git xcbuild-src \ 26 | && cd xcbuild-src \ 27 | && git checkout dbaee552d2f13640773eb1ad3c79c0d2aca7229c \ 28 | && git submodule sync \ 29 | && git submodule update --init \ 30 | && sed -i s/-Werror// CMakeLists.txt \ 31 | && cd .. \ 32 | && mkdir xcbuild-build \ 33 | && cd xcbuild-build \ 34 | && cmake -G Ninja -DCMAKE_INSTALL_PREFIX=/opt/xcbuild -DCMAKE_BUILD_TYPE=Release ../xcbuild-src \ 35 | && ninja \ 36 | && ninja install \ 37 | && cd .. \ 38 | && rm -rf xcbuild-build xcbuild-src 39 | 40 | RUN set -x \ 41 | && git clone https://github.com/tpoechtrager/apple-libtapi.git \ 42 | && cd apple-libtapi \ 43 | && git checkout 664b8414f89612f2dfd35a9b679c345aa5389026 \ 44 | && ln -s ../../clang/include/clang src/llvm/projects/libtapi/include \ 45 | && cd .. \ 46 | && mkdir apple-libtapi-build \ 47 | && cd apple-libtapi-build \ 48 | && cmake -G Ninja -DCMAKE_INSTALL_PREFIX=/opt/cctools -DCMAKE_BUILD_TYPE=Release -DLLVM_INCLUDE_TESTS=OFF ../apple-libtapi/src/llvm \ 49 | && ln -s ../../clang/include/clang projects/libtapi/include \ 50 | && ninja clang-tablegen-targets \ 51 | && ninja libtapi \ 52 | && ninja install-libtapi install-tapi-headers \ 53 | && cd .. \ 54 | && rm -rf apple-libtapi apple-libtapi-build 55 | 56 | RUN set -x \ 57 | && git clone https://github.com/tpoechtrager/cctools-port.git \ 58 | && cd cctools-port \ 59 | && git checkout f28fb5e9c31efd3d0552afcce2d2c03cae25c1ca \ 60 | && cd cctools \ 61 | && PATH=/opt/clang/bin:$PATH ./configure --prefix=/opt/cctools --with-libtapi=/opt/cctools \ 62 | && PATH=/opt/clang/bin:$PATH make -j$(nproc) \ 63 | && make -j$(nproc) install \ 64 | && cd ../.. \ 65 | && rm -rf cctools-port 66 | 67 | ARG XCODE_URL 68 | 69 | RUN set -x \ 70 | && curl -LO $XCODE_URL \ 71 | && tar --warning=no-unknown-keyword -Jxf $(basename $XCODE_URL) \ 72 | && rm $(basename $XCODE_URL) 73 | 74 | ARG XCODE_CROSS_SRC_DIR=. 75 | ADD $XCODE_CROSS_SRC_DIR/setup-toolchain.sh $XCODE_CROSS_SRC_DIR/setup-symlinks.sh /opt/xcode-cross/ 76 | 77 | RUN set -x \ 78 | && cd Xcode.app \ 79 | && /opt/xcode-cross/setup-toolchain.sh /opt/cctools /opt/clang 80 | 81 | ENV DEVELOPER_DIR=/opt/Xcode.app 82 | 83 | RUN /opt/xcode-cross/setup-symlinks.sh /opt/xcode-cross $DEVELOPER_DIR /opt/cctools 84 | 85 | # Add the Xcode toolchain to the path, but after the normal path directories, 86 | # to allow using the host compiler as usual (for cases that require compilation 87 | # both for host and target at the same time). 88 | ENV PATH=/opt/xcode-cross/bin:$PATH:$DEVELOPER_DIR/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin:/opt/xcbuild/usr/bin:/opt/cctools/bin 89 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Cross compilation with Xcode tools 2 | ================================== 3 | 4 | This is a reproducible Dockerfile for cross compiling for Apple platforms 5 | from Linux, usable as base image for CI style setups. 6 | 7 | This builds on [cctools-port](https://github.com/tpoechtrager/cctools-port) 8 | for providing the basic tools for cross compilation, together with 9 | [xcbuild](https://github.com/facebook/xcbuild) for building xcode project 10 | files, wrapping it up into a easy to use and reproducible Dockerfile. 11 | 12 | Not all build tools used by Xcode project files are available; some 13 | are stubbed out (like `ibtool`), making it usable as a CI tool for 14 | testing compilation, while other Xcode project file features makes 15 | the build fail altogether. 16 | 17 | This setup currently uses a vanilla LLVM/Clang release instead 18 | of the Apple provided clang sources. 19 | 20 | This requires an original Xcode.app bundle (which can't be redistributed 21 | freely). It has been tested with and should work with Xcode versions 7, 22 | 8, 9 and 10. 23 | Older versions of Xcode can be downloaded from [Apple](https://developer.apple.com/download/more/). 24 | 25 | Using an Xcode 11 or 12 bundle works somewhat; building with generic build 26 | systems works fine, but there's a few known issues if building Xcode 27 | project files with xcbuild, for other than the simplest project files. 28 | (For Xcode 12, xcbuild doesn't seem to work at all.) 29 | 30 | The Xcode bundle can be stripped down to more manageable sizes for use 31 | with this cross compilation setup, since very little of the bundle 32 | actually is used: 33 | 34 | cp -a /Applications/Xcode.app . 35 | ./strip-xcode.sh Xcode.app 36 | tar -Jcvf xcode.tar.xz Xcode.app 37 | rm -rf Xcode.app 38 | 39 | Host the resulting xcode.tar.xz file somewhere, then build the docker 40 | image like this: 41 | 42 | docker build --build-arg XCODE_URL=http://path/to/your/xcode.tar.xz . 43 | 44 | The docker image has got all the necessary tools added to the path, but 45 | at the end of the path, so that e.g. `gcc` still resolves to the normal 46 | host compiler (for cases when projects need to compile things for both 47 | the host and the target). The cross tools can be used with normal cross 48 | compilation prefixes like `x86_64-apple-darwin-gcc` (which invokes clang, 49 | not gcc), or less architecturally biased like `apple-darwin-gcc` (for cases 50 | when the architecture will be specified with an `-arch` parameter. 51 | -------------------------------------------------------------------------------- /setup-symlinks.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | if [ $# -lt 3 ]; then 6 | echo $0 prefix developer-dir cctools 7 | exit 1 8 | fi 9 | PREFIX="$1" 10 | DEVELOPER_DIR="$2" 11 | CCTOOLS="$3" 12 | 13 | ARCHS="i386 x86_64 armv7 arm64" 14 | mkdir -p $PREFIX/bin 15 | for tool in clang clang++ cc gcc c++ g++ nm; do 16 | for arch in $ARCHS; do 17 | ln -sf $DEVELOPER_DIR/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/$tool $PREFIX/bin/$arch-apple-darwin-$tool 18 | done 19 | ln -sf $DEVELOPER_DIR/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/$tool $PREFIX/bin/apple-darwin-$tool 20 | done 21 | for tool in ar as ld ranlib strings strip; do 22 | for arch in $ARCHS; do 23 | ln -sf $CCTOOLS/bin/$tool $PREFIX/bin/$arch-apple-darwin-$tool 24 | done 25 | ln -sf $CCTOOLS/bin/$tool $PREFIX/bin/apple-darwin-$tool 26 | done 27 | -------------------------------------------------------------------------------- /setup-toolchain.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if [ $# -lt 2 ]; then 4 | echo $0 cctools clang 5 | exit 1 6 | fi 7 | 8 | if [ ! -d Contents/Developer/usr/bin ]; then 9 | echo Execute this in the xcode dir root 10 | exit 1 11 | fi 12 | 13 | CCTOOLS=$1 14 | CLANG=$2 15 | 16 | if [ ! -e $CCTOOLS/bin/lipo ]; then 17 | echo lipo not found in $CCTOOLS 18 | exit 1 19 | fi 20 | 21 | if [ ! -e $CLANG/bin/clang ]; then 22 | echo clang not found in $CLANG 23 | exit 1 24 | fi 25 | 26 | set -e 27 | 28 | cd Contents/Developer/usr/bin 29 | ln -sf /bin/true copypng 30 | ln -sf /bin/true ibtool 31 | 32 | cd ../../Toolchains/XcodeDefault.xctoolchain/usr/bin 33 | 34 | TOOLS_SYMLINK="ar as cmpdylib ctf_insert dyldinfo install_name_tool ld libtool" 35 | TOOLS_SYMLINK="$TOOLS_SYMLINK lipo nmedit pagestuff ranlib segedit size string" 36 | TOOLS_SYMLINK="$TOOLS_SYMLINK strip unwinddump vtool" 37 | 38 | for tool in $TOOLS_SYMLINK; do 39 | ln -sf $CCTOOLS/bin/$tool . 40 | done 41 | 42 | cat< clang 43 | #!/bin/bash 44 | ARGS=() 45 | TARGET_SET="" 46 | SYSROOT_SET="" 47 | TOOL=\$(basename \$0 | sed 's/.*-\([^-]*\)/\1/') 48 | ARCH=\$(basename \$0 | sed 's/-.*//') 49 | case \$ARCH in 50 | i386|x86_64|arm*) 51 | ARGS+=(-target \$ARCH-apple-darwin16) 52 | TARGET_SET=1 53 | ;; 54 | *) 55 | ;; 56 | esac 57 | while [ \$# -gt 0 ]; do 58 | a=\$1 59 | if [ "\$a" = "-arch" ]; then 60 | shift 61 | ARGS+=(-target \$1-apple-darwin16 -arch \$1) 62 | TARGET_SET=1 63 | shift 64 | else 65 | if [ "\$a" = "-isysroot" ]; then 66 | SYSROOT_SET=1 67 | elif [ "\$a" = "-target" ]; then 68 | TARGET_SET=1 69 | fi 70 | ARGS+=("\$a") 71 | shift 72 | fi 73 | done 74 | if [ -z "\$TARGET_SET" ]; then 75 | ARGS+=(-target x86_64-apple-darwin16) 76 | fi 77 | if [ -z "\$SYSROOT_SET" ]; then 78 | # Is there a better way to find the default sdk? 79 | SDKS=\$DEVELOPER_DIR/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs 80 | SDK=\$SDKS/\$(ls \$SDKS | grep MacOSX1 | head -1) 81 | ARGS+=(-isysroot \$SDK) 82 | fi 83 | export PATH=$CCTOOLS/bin:\$PATH 84 | if [ "\$TOOL" = "clang++" ] || [ "\$TOOL" = "c++" ] || [ "\$TOOL" = "g++" ]; then 85 | ARGS+=(--driver-mode=g++) 86 | fi 87 | ARGS+=(-no-canonical-prefixes) 88 | EXE=\$(dirname \$(readlink -f \$0))/clang-exe 89 | \$EXE "\${ARGS[@]}" 90 | EOF 91 | chmod a+x clang 92 | ln -sf clang clang++ 93 | ln -sf clang cc 94 | ln -sf clang c89 95 | ln -sf clang c99 96 | ln -sf clang c++ 97 | ln -sf clang gcc 98 | ln -sf clang g++ 99 | 100 | ln -s $CLANG/bin/dsymutil . 101 | ln -s $CLANG/bin/llvm-nm nm 102 | ln -s $CLANG/bin/clang clang-exe 103 | 104 | cd ../lib/clang 105 | # Rename the original bundled clang resource directory to "orig", to avoid 106 | # potential version number clashes. 107 | mv * orig 108 | # Fetch the version number of the resource directory of the clang we're 109 | # using. 110 | VER=$(cd $CLANG/lib/clang && ls) 111 | # Link in the clang resource directory from the external installation into 112 | # this directory. 113 | ln -s $CLANG/lib/clang/$VER . 114 | # Provide the original libraries in the external clang's resource directory. 115 | ln -s $(pwd)/orig/lib/darwin $VER/lib 116 | -------------------------------------------------------------------------------- /strip-xcode.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | if [ -n "$1" ]; then 6 | cd "$1" 7 | fi 8 | 9 | cd Contents 10 | 11 | mkdir PlugIns-new 12 | mv PlugIns/Xcode3Core.ideplugin PlugIns/IDEiOSSupportCore.ideplugin PlugIns-new 13 | rm -rf PlugIns 14 | mv PlugIns-new PlugIns 15 | rm -rf Applications _CodeSignature Frameworks Library MacOS OtherFrameworks Resources SharedFrameworks XPCServices _MASReceipt Info.plist PkgInfo version.plist 16 | 17 | cd Developer 18 | rm -rf Applications Documentation Library Makefiles Tools usr 19 | mkdir -p usr/bin 20 | 21 | cd Toolchains/XcodeDefault.xctoolchain/usr 22 | rm -rf bin 23 | mkdir bin 24 | # Fill in usr/bin later with wrappers/scripts 25 | rm -rf lib/*swift* lib/*.dylib lib/*.framework 26 | rm -rf metal 27 | cd ../../.. 28 | mkdir Toolchains-new 29 | mv Toolchains/XcodeDefault.xctoolchain Toolchains-new 30 | rm -rf Toolchains 31 | mv Toolchains-new Toolchains 32 | 33 | cd Platforms 34 | cd MacOSX.platform 35 | rm -rf usr _CodeSignature 36 | cd Developer/SDKs/MacOSX10.*sdk 37 | rm -rf usr/share 38 | cd ../../../.. 39 | 40 | for sdk in iPhone Watch AppleTV; do 41 | OS=${sdk}OS 42 | SIM=${sdk}Simulator 43 | cd $OS.platform 44 | rm -rf DeviceSupport usr _CodeSignature Developer/Library/CoreSimulator Library/Developer/CoreSimulator 45 | cd .. 46 | 47 | cd $SIM.platform 48 | rm -rf Developer/Library/CoreSimulator Developer/Library/Frameworks Developer/Library/PrivateFrameworks _CodeSignature 49 | cd Developer/SDKs/$SIM.sdk 50 | rm -rf usr/share usr/libexec Library Developer Applications 51 | # On Xcode 9.x and newer, usr/lib contains tbd files for the libraries, 52 | # and linking to them for simulator builds succeeds. On Xcode 8.x and 53 | # older, the usr/lib dir contains large dylib files, and linking 54 | # against them doesn't succeed anyway (it's missing 55 | # /usr/lib/system/libsystem_kernel.dylib). Thus remove the large files 56 | # from older SDKs, while keeping enough for linking to succeed on 57 | # newer SDks. 58 | rm -rf usr/lib/system usr/lib/*.dylib 59 | if [ ! -e System/Library/Frameworks/Foundation.framework/Foundation.tbd ]; then 60 | # Xcode 7.x and 8.x has got full frameworks for the simulator here, 61 | # replace them with thin frameworks with TBD files for the target. 62 | # Xcode 9 has got TBD files for the simulator as well. 63 | # On Xcode 9/iOS 11 SDK, building for the simulatorh with header 64 | # files taken from the target breaks. 65 | rm -rf System 66 | cp -a ../../../../$OS.platform/Developer/SDKs/$OS.sdk/System . 67 | fi 68 | cd ../../../.. 69 | done 70 | --------------------------------------------------------------------------------