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

ios-cmake: migrate to Conan v2, improve test_package #21532

Draft
wants to merge 8 commits into
base: master
Choose a base branch
from
Draft
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
3 changes: 2 additions & 1 deletion recipes/ios-cmake/all/cmake-wrapper
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#!/usr/bin/env bash
set -eu

ALL_ARGS=$@

Expand All @@ -17,7 +18,7 @@ done
if [ $BUILD == "yes" ]; then
cmake "$@"
else
# TODO check if, based on log level, some configurable outbut of these values could be nice to have
# TODO check if, based on log level, some configurable output of these values could be nice to have
fix_cmake_flags="-DCMAKE_FIND_ROOT_PATH_MODE_PROGRAM=BOTH -DCMAKE_FIND_ROOT_PATH_MODE_INCLUDE=BOTH -DCMAKE_FIND_ROOT_PATH_MODE_LIBRARY=BOTH -DCMAKE_FIND_ROOT_PATH_MODE_PACKAGE=NEVER"
cmake "$@" ${CONAN_USER_CMAKE_FLAGS} ${fix_cmake_flags}
fi
3 changes: 3 additions & 0 deletions recipes/ios-cmake/all/conandata.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,6 @@ sources:
"4.2.0":
url: "https://github.com/leetal/ios-cmake/archive/4.2.0.zip"
sha256: "abcae0619751dd46d825f0f341d9a5c1a13bbe8fa84f9a62f09652087f64f87f"
"4.4.1":
url: "https://github.com/leetal/ios-cmake/archive/4.4.1.zip"
sha256: "9edfe43e96f1a08dd29d86c81f7ad7d65069fb6f681bbf002df85bb91c421b27"
217 changes: 127 additions & 90 deletions recipes/ios-cmake/all/conanfile.py
Original file line number Diff line number Diff line change
@@ -1,26 +1,49 @@
from conans import ConanFile, tools
from conans.errors import ConanInvalidConfiguration

import os

from conan import ConanFile
from conan.errors import ConanInvalidConfiguration
from conan.tools.apple import is_apple_os
from conan.tools.files import copy, get
from conan.tools.layout import basic_layout

required_conan_version = ">=1.52.0"


class IosCMakeConan(ConanFile):
name = "ios-cmake"
description = "iOS CMake toolchain to (cross) compile macOS/iOS/watchOS/tvOS"
license = "BSD-3-Clause"
settings = "os" , "arch"
url = "https://github.com/conan-io/conan-center-index"
homepage = "https://github.com/leetal/ios-cmake"
topics = ("apple", "ios", "cmake", "toolchain", "ios", "tvos", "watchos", "header-only")

package_type = "build-scripts"
settings = "os", "arch", "compiler", "build_type"
# Note: you need to use `-o:b ...` to set these options due to package_type
options = {
"enable_bitcode": [True, False],
"enable_arc": [True, False],
"enable_visibility": [True, False],
"enable_strict_try_compile": [True, False],
"toolchain_target": ["auto", "OS", "OS64", "OS64COMBINED",
"SIMULATOR", "SIMULATOR64", "SIMULATORARM64",
"TVOS", "TVOSCOMBINED",
"SIMULATOR_TVOS", "WATCHOS",
"WATCHOSCOMBINED", "SIMULATOR_WATCHOS",
"MAC", "MAC_ARM64", "MAC_CATALYST", "MAC_CATALYST_ARM64"]
"toolchain_target": [
"auto",
Copy link
Member

Choose a reason for hiding this comment

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

I think we can use the fact that we're migrating this to Conan 2 to remove the auto value altogether so we don't need to check for it later? wdyt @valgur?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I don't think that would work. The logic in config_options() sets the default value to an appropriate one based on self.settings.os and falls back to auto if it cannot be determined. The self._default_toolchain_target property does not cover several Apple OS-s, notably Macos.

Copy link
Member

Choose a reason for hiding this comment

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

Oh so then the idea is that if it's not able to be guessed, we fail later in the validate if it's still set to auto? My only concern is that then running conan create ... -o="&:toolchain_target=auto" will always fail, the CLI value is overriding the decision in config_options

Copy link
Member

Choose a reason for hiding this comment

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

Oh I see - I've pushed a minor change to let users pass auto and have it be still auto-guessed, let me know if it works for you :)

Copy link
Contributor Author

@valgur valgur Jul 16, 2024

Choose a reason for hiding this comment

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

Uhm... self.options always report their default value in configure(), even if the user overrides them. But only if packge_type = "build-scripts" for some reason.

Second, it always fails with:

ConanException: Incorrect attempt to modify option 'toolchain_target' from 'auto' to 'xyz'

It would be great if the Conan client got rid of that artificial limitation. :/

The VTK PR (#10776) actually manages to work around that limitation through some black magic, basically doing:

def configure(self):
    overrides = {}
    if self.options.toolchain_target == "auto":
        overrides["toolchain_target"] = self._default_toolchain_target
    self.options.update(self.options.possible_values, dict(self.options.items(), **overrides))

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The build-scripts package type is super-buggy or simply inconsistent with the options handling overall, for some reason. The user-supplied option values are simply ignored and not even validated to be one of the allowed values.

Copy link
Contributor Author

@valgur valgur Jul 16, 2024

Choose a reason for hiding this comment

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

Oooh... the options actually work, but only if I use -o:b toolchain_target=.... This could maybe be documented better.

"OS",
"OS64",
"OS64COMBINED",
"SIMULATOR",
"SIMULATOR64",
"SIMULATORARM64",
"TVOS",
"TVOSCOMBINED",
"SIMULATOR_TVOS",
"WATCHOS",
"WATCHOSCOMBINED",
"SIMULATOR_WATCHOS",
"MAC",
"MAC_ARM64",
"MAC_CATALYST",
"MAC_CATALYST_ARM64",
],
}
default_options = {
"enable_bitcode": True,
Expand All @@ -29,121 +52,135 @@ class IosCMakeConan(ConanFile):
"enable_strict_try_compile": False,
"toolchain_target": "auto",
}
description = "ios Cmake toolchain to (cross) compile macOS/iOS/watchOS/tvOS"
topics = "conan", "apple", "ios", "cmake", "toolchain", "ios", "tvos", "watchos"
exports_sources = "cmake-wrapper"

@property
def _source_subfolder(self):
return os.path.join(self.source_folder, "source_subfolder")

@staticmethod
def _chmod_plus_x(filename):
if os.name == 'posix':
os.chmod(filename, os.stat(filename).st_mode | 0o111)
def config_options(self):
if os.getenv("CONAN_CENTER_BUILD_SERVICE") is not None:
# To not simply skip the build in C3I due to a missing toolchain_target value
self.options.toolchain_target = "OS64"

def configure(self):
if not tools.is_apple_os(self.settings.os):
raise ConanInvalidConfiguration("This package only supports Apple operating systems")

def _guess_toolchain_target(self, os, arch):
if os == "iOS":
if arch in ["armv8", "armv8.3"]:
@property
def _default_toolchain_target(self):
if self.settings.os == "iOS":
if self.settings.arch in ["armv8", "armv8.3"]:
return "OS64"
if arch == "x86_64":
if self.settings.arch == "x86_64":
return "SIMULATOR64"
# 32bit is dead, don't care
elif os == "watchOS":
if arch == "x86_64":
elif self.settings.os == "watchOS":
if self.settings.arch == "x86_64":
return "SIMULATOR_WATCHOS"
else:
return "WATCHOS"
elif os == "tvOS":
if arch == "x86_64":
elif self.settings.os == "tvOS":
if self.settings.arch == "x86_64":
return "TVOS"
else:
return "SIMULATOR_TVOS"
raise ConanInvalidConfiguration("Can not guess toolchain_target. Please set the option explicit (or check our os settings)")
return None

@property
def _toolchain_target(self):
if self.options.toolchain_target == "auto":
return self._default_toolchain_target
return self.options.toolchain_target

def export_sources(self):
copy(self, "cmake-wrapper", self.recipe_folder, self.export_sources_folder)

def layout(self):
basic_layout(self, src_folder="src")

def validate(self):
if not is_apple_os(self):
raise ConanInvalidConfiguration("This package only supports Apple operating systems")
if self._toolchain_target is None:
raise ConanInvalidConfiguration(
"Cannot guess toolchain target type. "
f"Please set the option explicitly with '-o:b {self.name}/*:toolchain_target=...'."
)

def package_id(self):
self.info.clear()

def source(self):
tools.get(**self.conan_data["sources"][self.version])
os.rename("ios-cmake-{}".format(self.version), self._source_subfolder)
get(self, **self.conan_data["sources"][self.version], strip_root=True)

def build(self):
pass # there is nothing to build
pass # there is nothing to build

@staticmethod
def _chmod_plus_x(filename):
if os.name == "posix":
os.chmod(filename, os.stat(filename).st_mode | 0o111)

def package(self):
self.copy("cmake-wrapper", dst="bin")
self.copy("ios.toolchain.cmake",
src=self._source_subfolder,
dst=os.path.join("lib", "cmake", "ios-cmake"),
keep_path=False)
copy(self, "cmake-wrapper",
src=self.export_sources_folder,
dst=os.path.join(self.package_folder, "bin"))
copy(self, "ios.toolchain.cmake",
src=self.source_folder,
dst=os.path.join(self.package_folder, "lib", "cmake", "ios-cmake"),
keep_path=False)
self._chmod_plus_x(os.path.join(self.package_folder, "bin", "cmake-wrapper"))

self.copy("LICENSE.md", dst="licenses", src=self._source_subfolder, keep_path=False)
# satisfy KB-H014 (header_only recipes require headers)
tools.save(os.path.join(self.package_folder, "include", "dummy_header.h"), "\n")
copy(self, "LICENSE.md",
dst=os.path.join(self.package_folder, "licenses"),
src=self.source_folder,
keep_path=False)

def package_info(self):
if self.settings.os == "Macos":
if not getattr(self, "settings_target", None):
# not a build_require , but can be fine since its build as a ppr:b, but nothing to do
return
# this is where I want to be, expecting this as a build_require for a host
target_os = str(self.settings_target.os)
arch_flag = self.settings_target.arch
target_version= self.settings_target.os.version
elif self.settings.os == "iOS": # old style 1 profile, don't use
target_os = str(self.settings.os)
arch_flag = self.settings.arch
target_version = self.settings.os.version
else:
#hackingtosh ? hu
raise ConanInvalidConfiguration("Building for iOS on a non Mac platform? Please tell me how!")

if self.options.toolchain_target == "auto":
toolchain_target = self._guess_toolchain_target(target_os, arch_flag)
else:
toolchain_target = self.options.toolchain_target
settings = getattr(self, "settings_target", self.settings)

if not settings.os:
return

arch_flag = str(settings.arch)
if arch_flag == "armv8":
arch_flag = "arm64"
elif arch_flag == "armv8.3":
arch_flag = "arm64e"

cmake_options = "-DENABLE_BITCODE={} -DENABLE_ARC={} -DENABLE_VISIBILITY={} -DENABLE_STRICT_TRY_COMPILE={}".format(
self.options.enable_bitcode,
self.options.enable_arc,
self.options.enable_visibility,
self.options.enable_strict_try_compile
)
cmake_options = " ".join([
f"-DENABLE_BITCODE={self.options.enable_bitcode}",
f"-DENABLE_ARC={self.options.enable_arc}",
f"-DENABLE_VISIBILITY={self.options.enable_visibility}",
f"-DENABLE_STRICT_TRY_COMPILE={self.options.enable_strict_try_compile}"
])
# Note that this, as long as we specify (overwrite) the ARCHS, PLATFORM has just limited effect,
# but PLATFORM need to be set in the profile so it makes sense, see ios-cmake docs for more info
cmake_flags = "-DPLATFORM={} -DDEPLOYMENT_TARGET={} -DARCHS={} {}".format(
toolchain_target, target_version, arch_flag, cmake_options
)
cmake_flags = " ".join([
f"-DPLATFORM={self._toolchain_target}",
f"-DDEPLOYMENT_TARGET={str(settings.os.version)}",
f"-DARCHS={arch_flag}",
f"{cmake_options}",
])
self.output.info(f"Setting toolchain options to: {cmake_flags}")
cmake_wrapper = os.path.join(self.package_folder, "bin", "cmake-wrapper")
self.output.info(f"Setting cmake-wrapper path to: {cmake_wrapper}")
toolchain = os.path.join(self.package_folder, "lib", "cmake", "ios-cmake", "ios.toolchain.cmake")

self.conf_info.define_path("user.ios-cmake:cmake_program", cmake_wrapper)
self.conf_info.define_path("user.ios-cmake:cmake_toolchain_file", toolchain)
self.conf_info.define("user.ios-cmake:cmake_flags", cmake_flags)
self.conf_info.define("user.ios-cmake:enable_bitcode", bool(self.options.enable_bitcode))
self.conf_info.define("user.ios-cmake:enable_arc", bool(self.options.enable_arc))
self.conf_info.define("user.ios-cmake:enable_visibility", bool(self.options.enable_visibility))

self.buildenv_info.define("CONAN_CMAKE_PROGRAM", cmake_wrapper)
self.buildenv_info.define("CONAN_CMAKE_TOOLCHAIN_FILE", toolchain)
self.buildenv_info.define("CONAN_USER_CMAKE_FLAGS", cmake_flags)
# add some more env_info, for the case users generate a toolchain file via conan and want to access that info
self.buildenv_info.define("CONAN_ENABLE_BITCODE_FLAG", str(self.options.enable_bitcode))
self.buildenv_info.define("CONAN_ENABLE_ARC_FLAG", str(self.options.enable_arc))
self.buildenv_info.define("CONAN_ENABLE_VISIBILITY_FLAG", str(self.options.enable_visibility))
self.buildenv_info.define("CONAN_ENABLE_STRICT_TRY_COMPILE_FLAG", str(self.options.enable_strict_try_compile))
# the rest should be exported from profile info anyway

# TODO: Legacy, remove in Conan 2.0
self.env_info.CONAN_USER_CMAKE_FLAGS = cmake_flags
self.output.info("Setting toolchain options to: {}".format(cmake_flags))
cmake_wrapper = os.path.join(self.package_folder, "bin", "cmake-wrapper")
self.output.info("Setting CONAN_CMAKE_PROGRAM to: {}".format(cmake_wrapper))
self.env_info.CONAN_CMAKE_PROGRAM = cmake_wrapper
tool_chain = os.path.join(self.package_folder,
"lib",
"cmake",
"ios-cmake",
"ios.toolchain.cmake")
self.env_info.CONAN_CMAKE_TOOLCHAIN_FILE = tool_chain
# add some more env_info, for the case users generate a toolchain file via conan and want to access that info
self.env_info.CONAN_CMAKE_TOOLCHAIN_FILE = toolchain
self.env_info.CONAN_ENABLE_BITCODE_FLAG = str(self.options.enable_bitcode)
self.env_info.CONAN_ENABLE_ARC_FLAG = str(self.options.enable_arc)
self.env_info.CONAN_ENABLE_VISIBILITY_FLAG = str(self.options.enable_visibility)
self.env_info.CONAN_ENABLE_STRICT_TRY_COMPILE_FLAG = str(self.options.enable_strict_try_compile)
# the rest should be exported from profile info anyway

def package_id(self):
self.info.header_only()
# TODO , since we have 2 profiles I am not sure that this is still required
# since this will always be / has to be a build profile
2 changes: 2 additions & 0 deletions recipes/ios-cmake/all/test_package/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
cmake_minimum_required(VERSION 3.15)
project(empty)
33 changes: 25 additions & 8 deletions recipes/ios-cmake/all/test_package/conanfile.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,31 @@

import os
from conans import ConanFile

from conan import ConanFile
from conan.tools.build import can_run
from conan.tools.cmake import cmake_layout


class TestPackageConan(ConanFile):
settings = "os"
settings = "os", "arch", "compiler", "build_type"
generators = "VirtualBuildEnv"
test_type = "explicit"

def layout(self):
cmake_layout(self)

def build_requirements(self):
self.tool_requires(self.tested_reference_str)

def generate(self):
info = self.dependencies.build["ios-cmake"].conf_info
cmake_prog = info.get("user.ios-cmake:cmake_program", check_type=str)
toolchain = info.get("user.ios-cmake:cmake_toolchain_file", check_type=str)
assert os.path.basename(cmake_prog) == "cmake-wrapper"
assert os.path.isfile(cmake_prog)
assert os.path.basename(toolchain) == "ios.toolchain.cmake"
assert os.path.isfile(toolchain)

def test(self):
if self.settings.os == "iOS":
cmake_prog = os.environ.get("CONAN_CMAKE_PROGRAM")
toolchain = os.environ.get("CONAN_CMAKE_TOOLCHAIN_FILE")
assert (os.path.basename(cmake_prog) == "cmake-wrapper")
assert (os.path.basename(toolchain) == "ios.toolchain.cmake")
if can_run(self):
self.run(f"cmake-wrapper {self.source_folder}")
self.run("cmake-wrapper --build .")
2 changes: 2 additions & 0 deletions recipes/ios-cmake/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,5 @@ versions:
folder: all
"4.2.0":
folder: all
"4.4.1":
folder: all