Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add iOS, tvOS, and watchOS support #528

Merged
merged 6 commits into from
Jul 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 49 additions & 8 deletions conan_provider.cmake
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
set(CONAN_MINIMUM_VERSION 2.0.5)


function(detect_os OS OS_API_LEVEL OS_VERSION OS_SUBSYSTEM)
function(detect_os OS OS_API_LEVEL OS_SDK OS_SUBSYSTEM OS_VERSION)
# it could be cross compilation
message(STATUS "CMake-Conan: cmake_system_name=${CMAKE_SYSTEM_NAME}")
if(CMAKE_SYSTEM_NAME AND NOT CMAKE_SYSTEM_NAME STREQUAL "Generic")
if(${CMAKE_SYSTEM_NAME} STREQUAL "Darwin")
set(${OS} Macos PARENT_SCOPE)
message(STATUS "CMake-Conan: cmake_osx_deployment_target=${CMAKE_OSX_DEPLOYMENT_TARGET}")
set(${OS_VERSION} ${CMAKE_OSX_DEPLOYMENT_TARGET} PARENT_SCOPE)
elseif(${CMAKE_SYSTEM_NAME} STREQUAL "QNX")
set(${OS} Neutrino PARENT_SCOPE)
elseif(${CMAKE_SYSTEM_NAME} STREQUAL "CYGWIN")
Expand All @@ -25,18 +23,58 @@ function(detect_os OS OS_API_LEVEL OS_VERSION OS_SUBSYSTEM)
message(STATUS "CMake-Conan: android_platform=${ANDROID_PLATFORM}")
set(${OS_API_LEVEL} ${_OS_API_LEVEL} PARENT_SCOPE)
endif()
if(CMAKE_SYSTEM_NAME MATCHES "Darwin|iOS|tvOS|watchOS")
# CMAKE_OSX_SYSROOT contains the full path to the SDK for MakeFile/Ninja
# generators, but just has the original input string for Xcode.
if(NOT IS_DIRECTORY ${CMAKE_OSX_SYSROOT})
set(_OS_SDK ${CMAKE_OSX_SYSROOT})
else()
if(CMAKE_OSX_SYSROOT MATCHES Simulator)
set(apple_platform_suffix simulator)
else()
set(apple_platform_suffix os)
endif()
if(CMAKE_OSX_SYSROOT MATCHES AppleTV)
set(_OS_SDK "appletv${apple_platform_suffix}")
elseif(CMAKE_OSX_SYSROOT MATCHES iPhone)
set(_OS_SDK "iphone${apple_platform_suffix}")
elseif(CMAKE_OSX_SYSROOT MATCHES Watch)
set(_OS_SDK "watch${apple_platform_suffix}")
endif()
endif()
if(DEFINED _OS_SDK)
message(STATUS "CMake-Conan: cmake_osx_sysroot=${CMAKE_OSX_SYSROOT}")
set(${OS_SDK} ${_OS_SDK} PARENT_SCOPE)
endif()
if(DEFINED CMAKE_OSX_DEPLOYMENT_TARGET)
message(STATUS "CMake-Conan: cmake_osx_deployment_target=${CMAKE_OSX_DEPLOYMENT_TARGET}")
set(${OS_VERSION} ${CMAKE_OSX_DEPLOYMENT_TARGET} PARENT_SCOPE)
endif()
endif()
endif()
endfunction()


function(detect_arch ARCH)
if(CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64|ARM64|arm64")
# CMAKE_OSX_ARCHITECTURES can contain multiple architectures, but Conan only supports one.
# Therefore this code only finds one. If the recipes support multiple architectures, the
# build will work. Otherwise, there will be a linker error for the missing architecture(s).
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

based on this comment and the logic, I'm not sure this is worth leaving here. If CMAKE_OSX_ARCHITECTURES is defined, then we cannot map to a single architecture. If I'm reading the conditional properly, with the proposed changes in this PR the conan architecture would be whichever matches CMAKE_SYSTEM_PROCESSOR or CMAKE_OSX_ARCHITECTURES when going in order (arm64, armv7-a, armv7, armv7s, x86_64) - not sure this makes for a predictable experience especially considering that unless the caller has made provisions to ensure that the binaries are fat binaries, there will be linker errors.

I would suggest falling back to issuing a warning when CMAKE_OSX_ARCHITECTURES is defined, and stick to following what CMAKE_SYSTEM_PROCESSOR says. In the future we can evaluate how to best handle this case but I have concerns this is fragile at the moment.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately, CMAKE_SYSTEM_PROCESSOR is blank for iOS/tvOS/watchOS builds and set to the build machine architecture for macOS builds. I don't see a way around using CMAKE_OSX_ARCHITECTURES for this.

I can add a warning if CMAKE_OSX_ARCHITECTURES contains more than one architecture, though.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @ssrobins!! Where would CMAKE_OSX_ARCHITECTURES be initialized? Does CMake give it default values if it is blank?
As for CMAKE_SYSTEM_PROCESSOR, this is typically set in the same toolchain file that also sets CMAKE_SYSTEM_NAME to iOS - in fact when cross-building this is one of the recommended variables to set in a toolchain, so I'm wondering what the most conventional way is to configure an iOS/tvOS/watchOS build with CMake? I think when Xcode is the generator possibly Cmake can configure the build without an architecture as one can be selected at build time instead with Xcode?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CMAKE_OSX_ARCHITECTURES would be set by the user, as shown in the documentation, which I would consider the conventional way to build iOS/tvOS/watchOS with CMake since it was added in 3.14. Before then, we had to rely on community-supported toolchains, which did set CMAKE_SYSTEM_PROCESSOR.

CMAKE_OSX_ARCHITECTURES is blank by default, but the generated build defaults to the build machine's architecture so in that case CMAKE_SYSTEM_PROCESSOR is helpful.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a warning if CMAKE_OSX_ARCHITECTURES contains more than one architecture.

if(DEFINED CMAKE_OSX_ARCHITECTURES)
string(REPLACE " " ";" apple_arch_list "${CMAKE_OSX_ARCHITECTURES}")
list(LENGTH apple_arch_list apple_arch_count)
if(apple_arch_count GREATER 1)
message(WARNING "CMake-Conan: Multiple architectures detected, this will only work if Conan recipe(s) produce fat binaries.")
endif()
endif()
if(CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64|ARM64|arm64" OR CMAKE_OSX_ARCHITECTURES MATCHES arm64)
set(_ARCH armv8)
elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "armv7-a|armv7l")
elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "armv7-a|armv7l" OR CMAKE_OSX_ARCHITECTURES MATCHES armv7)
set(_ARCH armv7)
elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "i686")
elseif(CMAKE_OSX_ARCHITECTURES MATCHES armv7s)
set(_ARCH armv7s)
elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "i686" OR CMAKE_OSX_ARCHITECTURES MATCHES i386)
set(_ARCH x86)
elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "AMD64|amd64|x86_64")
elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "AMD64|amd64|x86_64" OR CMAKE_OSX_ARCHITECTURES MATCHES x86_64)
set(_ARCH x86_64)
endif()
message(STATUS "CMake-Conan: cmake_system_processor=${_ARCH}")
Expand Down Expand Up @@ -109,7 +147,7 @@ endfunction()


function(detect_host_profile output_file)
detect_os(MYOS MYOS_API_LEVEL MYOS_VERSION MYOS_SUBSYSTEM)
detect_os(MYOS MYOS_API_LEVEL MYOS_SDK MYOS_SUBSYSTEM MYOS_VERSION)
detect_arch(MYARCH)
detect_compiler(MYCOMPILER MYCOMPILER_VERSION)
detect_cxx_standard(MYCXX_STANDARD)
Expand All @@ -131,6 +169,9 @@ function(detect_host_profile output_file)
if(MYOS_VERSION)
string(APPEND PROFILE os.version=${MYOS_VERSION} "\n")
endif()
if(MYOS_SDK)
string(APPEND PROFILE os.sdk=${MYOS_SDK} "\n")
endif()
if(MYOS_SUBSYSTEM)
string(APPEND PROFILE os.subsystem=${MYOS_SUBSYSTEM} "\n")
endif()
Expand Down
73 changes: 73 additions & 0 deletions tests/test_smoke.py
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@ def test_add_subdirectory(self, capfd, chdir_build):
out, _ = capfd.readouterr()
assert "subdir/0.1: Hello World Release!" in out


class TestOsVersion:
@darwin
def test_os_version(self, capfd, chdir_build):
Expand Down Expand Up @@ -242,3 +243,75 @@ def test_android_x86(self, capfd, chdir_build):
assert "os=Android" in out
assert "os.api_level=19" in out
assert "tools.android:ndk_path=" in out


class TestiOS:
@darwin
def test_ios(self, capfd, chdir_build):
run("cmake .. --fresh -DCMAKE_PROJECT_TOP_LEVEL_INCLUDES=conan_provider.cmake -DCMAKE_BUILD_TYPE=Release "
"-DCMAKE_OSX_ARCHITECTURES=arm64 -DCMAKE_SYSTEM_NAME=iOS "
"-DCMAKE_OSX_SYSROOT=iphoneos -DCMAKE_OSX_DEPLOYMENT_TARGET=11.0")
out, _ = capfd.readouterr()
assert "arch=armv8" in out
assert "os=iOS" in out
assert "os.sdk=iphoneos" in out
assert "os.version=11.0" in out

@darwin
def test_ios_simulator(self, capfd, chdir_build):
run("cmake .. --fresh -DCMAKE_PROJECT_TOP_LEVEL_INCLUDES=conan_provider.cmake -G Xcode "
"-DCMAKE_OSX_ARCHITECTURES=x86_64 -DCMAKE_SYSTEM_NAME=iOS "
"-DCMAKE_OSX_SYSROOT=iphonesimulator -DCMAKE_OSX_DEPLOYMENT_TARGET=11.0")
out, _ = capfd.readouterr()
assert "arch=x86_64" in out
assert "os=iOS" in out
assert "os.sdk=iphonesimulator" in out
assert "os.version=11.0" in out


class TestTvOS:
@darwin
def test_tvos(self, capfd, chdir_build):
run("cmake .. --fresh -DCMAKE_PROJECT_TOP_LEVEL_INCLUDES=conan_provider.cmake -G Xcode "
"-DCMAKE_OSX_ARCHITECTURES=arm64 -DCMAKE_SYSTEM_NAME=tvOS "
"-DCMAKE_OSX_SYSROOT=appletvos -DCMAKE_OSX_DEPLOYMENT_TARGET=15.0")
out, _ = capfd.readouterr()
assert "arch=armv8" in out
assert "os=tvOS" in out
assert "os.sdk=appletvos" in out
assert "os.version=15.0" in out

@darwin
def test_tvos_simulator(self, capfd, chdir_build):
run("cmake .. --fresh -DCMAKE_PROJECT_TOP_LEVEL_INCLUDES=conan_provider.cmake -DCMAKE_BUILD_TYPE=Release "
"-DCMAKE_OSX_ARCHITECTURES=arm64 -DCMAKE_SYSTEM_NAME=tvOS "
"-DCMAKE_OSX_SYSROOT=appletvsimulator -DCMAKE_OSX_DEPLOYMENT_TARGET=15.0")
out, _ = capfd.readouterr()
assert "arch=armv8" in out
assert "os=tvOS" in out
assert "os.sdk=appletvsimulator" in out
assert "os.version=15.0" in out


class TestWatchOS:
@darwin
def test_watchos(self, capfd, chdir_build):
run("cmake .. --fresh -DCMAKE_PROJECT_TOP_LEVEL_INCLUDES=conan_provider.cmake -DCMAKE_BUILD_TYPE=Release -G Ninja "
"-DCMAKE_OSX_ARCHITECTURES=arm64 -DCMAKE_SYSTEM_NAME=watchOS "
"-DCMAKE_OSX_SYSROOT=watchos -DCMAKE_OSX_DEPLOYMENT_TARGET=7.0")
out, _ = capfd.readouterr()
assert "arch=armv8" in out
assert "os=watchOS" in out
assert "os.sdk=watchos" in out
assert "os.version=7.0" in out

@darwin
def test_watchos_simulator(self, capfd, chdir_build):
run("cmake .. --fresh -DCMAKE_PROJECT_TOP_LEVEL_INCLUDES=conan_provider.cmake -G Xcode "
"-DCMAKE_OSX_ARCHITECTURES=x86_64 -DCMAKE_SYSTEM_NAME=watchOS "
"-DCMAKE_OSX_SYSROOT=watchsimulator -DCMAKE_OSX_DEPLOYMENT_TARGET=7.0")
out, _ = capfd.readouterr()
assert "arch=x86_64" in out
assert "os=watchOS" in out
assert "os.sdk=watchsimulator" in out
assert "os.version=7.0" in out