Unknown
Unknown

Reputation: 46773

How to Compile for OS X in Linux or Windows?

I would like to port my C/C++ apps to OS X.

I don't have a Mac, but I have Linux and Windows. Is there any tool for this?

Upvotes: 89

Views: 103118

Answers (10)

Cameron Lowell Palmer
Cameron Lowell Palmer

Reputation: 22225

Cross-compiling for Apple on Linux

For this project we will start by compiling LibreSSL. This is chosen because it offers a CMake build option and should generate a library, static and shared, from C-source that is generally cross-platform that will serve as a demonstration that it can be done.

Xcode SDKs

Xcode is Apple's toolchain and IDE. We however only need to grab the SDKs out of the bundle. Assuming you have access to a Mac with the Xcode you can quickly grab out the SDKs.

To determine which SDK version of for example iphoneos, iphonesimulator, or macosx you use xcrun.

% xcrun --sdk macosx --show-sdk-version
13.3

And where is Xcode installed?

% xcode-select --print-path
/Applications/Xcode.app/Contents/Developer

What platforms are available?

% ls -1 $(xcode-select --print-path)/Platforms
AppleTVOS.platform
AppleTVSimulator.platform
DriverKit.platform
MacOSX.platform
WatchOS.platform
WatchSimulator.platform
iPhoneOS.platform
iPhoneSimulator.platform

Then the SDKs for let's say iphoneos is in

% ls -1 $(xcode-select --print-path)/Platforms/MacOSX.platform/Developer/SDKs
MacOSX.sdk
MacOSX13.3.sdk
MacOSX13.sdk

The contents of MacOSX13.3.sdk (a symlink to MacOSX.sdk) are what will be referred to as Sysroot. Inside this folder you will find header files and TBDs (Text-based Definitions, which are YAML based stub libraries) that you compile and link against. These TBDs save a lot of space since the alternative might be shipping all of the various versions of the binaries. I created a tool to grab these SDKs out of an installed version of Xcode and create a gzipped tarball for convenience.

SDK Packager can be downloaded from GitHub

Move the tarball to your linux machine in any manner you see fit.

Building Clang/LLVM

Compiling Clang and LLVM on Linux is fairly straightforward. After checking out the project and switching to branch llvmorg-15.0.7 I created a script called build.sh in the root of the source directory.

#!/bin/bash

set -ex

function join_by { local IFS="$1"; shift; echo "$*"; }

PROJECTS=( clang lld clang-tools-extra ) 
RUNTIMES=( libcxx libcxxabi )
TARGETS=( X86 ARM AArch64 ) 

SOURCE_DIR=$( pwd )
BUILD_DIR=${SOURCE_DIR}/builddir
DIST_DIR=${SOURCE_DIR}/dist

rm -rf ${BUILD_DIR} ${DIST_DIR}
mkdir -p ${BUILD_DIR}
pushd ${BUILD_DIR}

cmake \
    -G Ninja \
    -D CMAKE_BUILD_TYPE=Release \
    -D CMAKE_INSTALL_PREFIX=${DIST_DIR} \
    -D CMAKE_VERBOSE_MAKEFILE=on \
    -D LLVM_TARGETS_TO_BUILD=$(join_by ";" "${TARGETS[@]}") \
    -D LLVM_ENABLE_PROJECTS=$(join_by ";" "${PROJECTS[@]}") \
    -D LLVM_ENABLE_RUNTIMES=$(join_by ";" "${RUNTIMES[@]}") \
    ${SOURCE_DIR}/llvm

cmake --build .
cmake --build . --target install

popd

Building Apple's cctools

Apple has a few 'special' tools that are used for manipulating binaries that are separate from those shipped with Clang/LLVM. Fortunately, these tools are open-source and the necessary porting work has been done. They are available at

Build libtapi and libxar first then build cctools.

#!/bin/bash

set -ex

./configure \
    --prefix=${HOME}/cctools \
    --with-libtapi=${HOME}/cctools \
    --with-libxar=${HOME}/cctools \
    --with-llvm-config=${HOME}/Development/llvm-project/dist/bin/llvm-config

make -j8
make install

The tools we need are ar, install_name_tool, libtool, lipo, ranlib, and ld. I suspect this additional step is no longer needed in part or in its entirety, but for this version of the tutorial we will stick with them.

Add the installed bin directory to your search path.

PATH=${HOME}/cctools/bin:${PATH}

Building LibreSSL for macOS

We will start by building for LibreSSL macOS for x86 and arm so we can run the 'apps' that ship with LibreSSL so we can prove it works.

It is not practical to check out LibreSSL from git since the project is not in the form of a release per se and relies upon OpenBSD. Hence, the tarball.

Download https://ftp.openbsd.org/pub/OpenBSD/LibreSSL/libressl-3.7.2.tar.gz

Below is a bash script example on how to compile LibreSSL. Two additional files, the toolchain and launcher script follow.

#!/bin/bash

 APPLE_SDK_SYSROOT="${HOME}/Development/apple_sdks/MacOSX13.3.sdk"
LLVM_DIST="${HOME}/Development/llvm-project/dist"
CCTOOLS_DIST="${HOME}/cctools"

SOURCE_DIR=$( pwd )
BUILD_DIR=${SOURCE_DIR}/builddir
INSTALL_DIR=${SOURCE_DIR}/dist
CMAKE_DIR=${SOURCE_DIR}/cmake

rm -rf ${BUILD_DIR} ${DIST_DIR}
mkdir -p ${BUILD_DIR}
pushd ${BUILD_DIR}
    cmake \
        -G Ninja \
        -D CMAKE_VERBOSE_MAKEFILE=on \
        -D CMAKE_TOOLCHAIN_FILE=${CMAKE_DIR}/macosx.toolchain.cmake \
        -D LINKER_LAUNCHER=${SOURCE_DIR}/launcher.sh \
        -D LLVM_DIST=${LLVM_DIST} \
        -D CCTOOLS_DIST=${CCTOOLS_DIST} \
        -D APPLE_SDK_SYSROOT=${APPLE_SDK_SYSROOT} \
        -D CMAKE_INSTALL_PREFIX=${INSTALL_DIR} \
        -D BUILD_SHARED_LIBS=OFF \
        -D LIBRESSL_APPS=YES \
        -D LIBRESSL_TESTS=NO \
        ${SOURCE_DIR}
    cmake --build .
    cmake --build . --target install
popd

One of the truly great things about Clang/LLVM and CMake is the ease with which you can generate a cross-compiled build. The key component in cross-compiled builds is the CMake toolchain file. Most developers are used to installing a compiler / IDE environment like Xcode and relying 100% on the IDE to know how compilation and linking occurs. However, compiling without this environment is possible.

Using shared libraries AKA dylibs

If you decide to enable BUILD_SHARED_LIBS you will need to codesign the binaries and dylibs in order for them to run.

codesign -f -s - bin/openssl
for LIB in lib/*.dylib; do
    codesign -f -s - $LIB
done

When building the dylib version of the libraries you normally would adhere to some conventions regarding where the dynamic linker will look for them. In this case we will need to clue dyld into where they live since we don't want to install our experiment into /usr/local/lib or /usr/lib. On a Mac we use the DYLD_LIBRARY_PATH variable.

DYLD_LIBRARY_PATH=$(pwd)/lib; export DYLD_LIBRARY_PATH

The linker launcher

We use a tiny shell script to append the path to the linker

#!/bin/bash

set -ex

# -fuse-ld=/path/to/ld if pre-clang 12
"$@" --ld-path=${HOME}/cctools/bin/ld

The toolchain file

The compiler flag -arch x86_64 -arch arm64 is what convinces clang to create, in this case, a FAT Mach-O binary with x86_64 and arm64 architectures. The triple will then be generated in two invocations of clang (behind the scenes) and then lipo called to combine the objects.

cmake_minimum_required(VERSION 3.16)

# APPLE_SDK_SYSROOT, LLVM_DIST, CCTOOLS_DIST, LINKER_LAUNCHER

set(SDK_NAME macosx)
list(APPEND ARCHS x86_64 arm64)
set(DEPLOYMENT_TARGET "10.13")

# If you want clang to output universal binaries you must use
# multiple -arch flags. Doing so results in clang calling 
# itself multiple times with different target triples. Finally,    
# it will call lipo to combine the two object files into one.  
# Clang also complains if you don't use -isysroot on these
# Apple SDK builds. This leaves -m<sdk>-version-min=x.xx. 
# You can cover it in the triple or pass them through 
# CMAKE_OSX_DEPLOYMENT_TARGET to generate the version min 
# string. The target triple is necessary if you want to build
# Mac Catalyst and you'll need to add -macabi to the end of the
# triple and it will look like <arch>-apple-ios<version>-macabi
set(CMAKE_SYSTEM_NAME Darwin) # iOS if you're iOS or Simulator
set(CMAKE_OSX_SYSROOT "${APPLE_SDK_SYSROOT}" CACHE PATH "")
set(CMAKE_OSX_DEPLOYMENT_TARGET ${DEPLOYMENT_TARGET})
set(CMAKE_OSX_ARCHITECTURES ${ARCHS})

list(JOIN ARCHS "-" ARCHS_DASH)
set(APPLE_TARGET_TRIPLE ${ARCHS_DASH}-apple-${SDK_NAME}${DEPLOYMENT_TARGET})
set(CMAKE_C_COMPILER_TARGET ${APPLE_TARGET_TRIPLE})
set(CMAKE_ASM_COMPILER_TARGET ${APPLE_TARGET_TRIPLE})

set(CMAKE_C_COMPILER ${LLVM_DIST}/bin/clang CACHE FILEPATH "")

set(CMAKE_C_LINKER_LAUNCHER ${LINKER_LAUNCHER} CACHE FILEPATH "")
set(CMAKE_AR ${CCTOOLS_DIST}/bin/ar CACHE FILEPATH "" FORCE)
set(CMAKE_RANLIB ${CCTOOLS_DIST}/bin/ranlib CACHE FILEPATH "" FORCE)
set(CMAKE_STRIP ${CCTOOLS_DIST}/bin/strip CACHE FILEPATH "" FORCE)
set(BUILD_LIBTOOL ${CCTOOLS_DIST}/bin/libtool CACHE FILEPATH "")

set(CMAKE_INSTALL_NAME_TOOL ${CCTOOLS_DIST}/bin/install_name_tool CACHE FILEPATH "")
set(CMAKE_C_CREATE_STATIC_LIBRARY "${BUILD_LIBTOOL} -static -o <TARGET> <LINK_FLAGS> <OBJECTS> " CACHE INTERNAL "")

Testing with the OpenSSL command-line tool

Zip up and copy the install directory to a Mac and you can then test the binaries to see if they work.

The simplest thing to do is to output the -help, but for a more interesting test we can dump the DERs from an existing .app bundle and then pretty print them.

codesign -d --extract-certificates MyApp.app
/path/to/openssl x509 -inform der -in codesign0 -text

Upvotes: 7

Viktor Latypov
Viktor Latypov

Reputation: 14467

For Linux, there is a prebuilt GCC cross-compiler (from publicly available Apple's modified GCC sources).

https://launchpad.net/~flosoft/+archive/cross-apple

Update for 2015

  1. After so many years, the industry-standard IDE now supports OSX/iOS/Android.

http://channel9.msdn.com/Events/Visual-Studio/Connect-event-2014/311

  1. Embarcadero's RadStudio also supports building OSX/iOS/Android apps on Windows.

  2. This answer by Thomas also provides a cross-compilation tool.

For all these options you still need a real mac/i-device to test the application.

Upvotes: 55

Thomas
Thomas

Reputation: 3225

I have created a project called OSXCross which aims to target OS X (10.4-10.9) from Linux.

It currently supports clang 3.2 up to 3.8 (trunk) (you can use your dist's clang).
In addition you can build an up-to-date vanilla GCC as well (4.6+).

LTO works as well, for both, clang and GCC.

Currently using cctools-870 with ld64-242.

https://github.com/tpoechtrager/osxcross

Upvotes: 37

64_
64_

Reputation: 533

You can hire a mac in the cloud from this website. You can hire them from $1, which should be enough (unless you need root access, then you are looking at $49+).

Upvotes: 2

Pablo Santa Cruz
Pablo Santa Cruz

Reputation: 181270

You will definitely need OS X somehow. Even if you don't own a Mac, you can still try some alternatives.

Upvotes: 3

Albert Zeyer
Albert Zeyer

Reputation:

I found this small documentation on the net: http://devs.openttd.org/~truebrain/compile-farm/apple-darwin9.txt

This describes exactly what you want. I am interested in it myself, haven't tested it yet though (found it 10 minutes ago). The documentation seems good, though.

Upvotes: 2

Trevor Boyd Smith
Trevor Boyd Smith

Reputation: 19223

  1. Get "VMware Player"
  2. Get "Mac OS X vm image"
  3. Compile/Debug/Integrate-and-test your code on the new OS to make sure everything works

When you are trying to get something working on multiple platforms you absolutely must compile/run/integrate/test on the intended platform. You can not just compile/run on one platform and then say "oh it should work the same on the other platform".

Even with the a really good cross-platform language like Java you will run into problems where it won't work exactly the same on the other platform.

The only way I have found that respects my time/productivity/ability-to-rapidly iterate on multiple platforms is to use a VM of the other platforms.

There are other solutions like dual-boot and ones that I haven't mentioned but I find that they don't respect my productivity/time.

Take dual-booting as an example:

  1. I make a change on OS 1
  2. reboot into OS 2
  3. forget something on OS 1
  4. reboot into OS 1
  5. make a change on OS 1
  6. reboot into OS 2 ... AGAIN...

BAM there goes 30 minutes of my time and I haven't done anything productive.

Upvotes: 7

Adam Hawes
Adam Hawes

Reputation: 5449

Apple development is a strange beast unto itself. OS X uses a port of GCC with some modifications to make it 'appley'. In theory, it's possible to the the sources to the Apple GCC and toolchain as well as the Apple kernel and library headers and build a cross compiler on your Windows machine.

Why you'd want to go down this path is beyond me. You can have a cheap Mac mini from $600. The time you invest getting a cross compiler working right (particularly with a Windows host for Unix tools) will probably cost more than the $600 anyway.

If you're really keen to make your app cross platform look into Qt, wxWidgets or FLTK. All provide cross-platform support with minimal changes to the base code. At least that way all you need to do is find a Mac to compile your app on, and that's not too hard to do if you have some technically minded friends who don't mind giving you SSH access to their Mac.

Upvotes: 3

Brian Campbell
Brian Campbell

Reputation: 332736

There appears to be some scripts that have been written to help get you set up cross compiling for the Mac; I can't say how good they are, or how applicable to your project. In the documentation, they refer to these instructions for cross-compiling for 10.4, and these ones for cross compiling for 10.5; those instructions may be more helpful than the script, depending on how well the script fits your needs.

If your program is free or open source software, then you may wish instead to create a MacPorts portfile (documentation here), and allow your users to build your program using MacPorts; that is generally the preferred way to install portable free or open source software on Mac OS X. MacPorts has been known to run on Linux in the past, so it may be possible to develop and test your Portfile on Linux (though it will obviously need to be tested on a Mac).

Upvotes: 24

Joel Levin
Joel Levin

Reputation: 2888

You would need a toolchain to cross compile for mach-o, but even if you had that, you won't have the Apple libraries around to develop with. I'm not sure how you would be able to port without them, unfortunately.

Upvotes: 3

Related Questions