Merge topic 'ios-universal'

565d080a Xcode: Add support for combined install on iOS
34f5ef56 iOS: Fix App Bundle layout
This commit is contained in:
Brad King 2015-12-11 09:47:18 -05:00 committed by CMake Topic Stage
commit a680211ca7
16 changed files with 539 additions and 1 deletions

View File

@ -191,6 +191,7 @@ Properties on Targets
/prop_tgt/INTERFACE_SYSTEM_INCLUDE_DIRECTORIES
/prop_tgt/INTERPROCEDURAL_OPTIMIZATION_CONFIG
/prop_tgt/INTERPROCEDURAL_OPTIMIZATION
/prop_tgt/IOS_INSTALL_COMBINED
/prop_tgt/JOB_POOL_COMPILE
/prop_tgt/JOB_POOL_LINK
/prop_tgt/LABELS

View File

@ -257,6 +257,7 @@ Variables that Control the Build
/variable/CMAKE_INSTALL_NAME_DIR
/variable/CMAKE_INSTALL_RPATH
/variable/CMAKE_INSTALL_RPATH_USE_LINK_PATH
/variable/CMAKE_IOS_INSTALL_COMBINED
/variable/CMAKE_LANG_COMPILER_LAUNCHER
/variable/CMAKE_LANG_INCLUDE_WHAT_YOU_USE
/variable/CMAKE_LANG_VISIBILITY_PRESET

View File

@ -0,0 +1,11 @@
IOS_INSTALL_COMBINED
--------------------
Build a combined (device and simulator) target when installing.
When this property is set to set to false (which is the default) then it will
either be built with the device SDK or the simulator SDK depending on the SDK
set. But if this property is set to true then the target will at install time
also be built for the corresponding SDK and combined into one library.
This feature requires at least Xcode version 6.

View File

@ -0,0 +1,7 @@
ios-universal
-------------
* When building for embedded Apple platforms like iOS CMake learned to build and
install combined targets which contain both a device and a simulator build.
This behavior can be enabled by setting the :prop_tgt:`IOS_INSTALL_COMBINED`
target property.

View File

@ -0,0 +1,8 @@
CMAKE_IOS_INSTALL_COMBINED
--------------------------
Default value for :prop_tgt:`IOS_INSTALL_COMBINED` of targets.
This variable is used to initialize the :prop_tgt:`IOS_INSTALL_COMBINED`
property on all the targets. See that target property for additional
information.

View File

@ -0,0 +1,297 @@
#=============================================================================
# Copyright 2014-2015 Ruslan Baratov, Gregor Jasny
#
# Distributed under the OSI-approved BSD License (the "License");
# see accompanying file Copyright.txt for details.
#
# This software is distributed WITHOUT ANY WARRANTY; without even the
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the License for more information.
#=============================================================================
# (To distribute this file outside of CMake, substitute the full
# License text for the above reference.)
# Function to print messages of this module
function(_ios_install_combined_message)
message("[iOS combined] " ${ARGN})
endfunction()
# Get build settings for the current target/config/SDK by running
# `xcodebuild -sdk ... -showBuildSettings` and parsing it's output
function(_ios_install_combined_get_build_setting sdk variable resultvar)
if("${sdk}" STREQUAL "")
message(FATAL_ERROR "`sdk` is empty")
endif()
if("${variable}" STREQUAL "")
message(FATAL_ERROR "`variable` is empty")
endif()
if("${resultvar}" STREQUAL "")
message(FATAL_ERROR "`resultvar` is empty")
endif()
set(
cmd
xcodebuild -showBuildSettings
-sdk "${sdk}"
-target "${CURRENT_TARGET}"
-config "${CURRENT_CONFIG}"
)
execute_process(
COMMAND ${cmd}
WORKING_DIRECTORY "${CMAKE_BINARY_DIR}"
RESULT_VARIABLE result
OUTPUT_VARIABLE output
)
if(NOT result EQUAL 0)
message(FATAL_ERROR "Command failed (${result}): ${cmd}")
endif()
if(NOT output MATCHES " ${variable} = ([^\n]*)")
message(FATAL_ERROR "${variable} not found.")
endif()
set("${resultvar}" "${CMAKE_MATCH_1}" PARENT_SCOPE)
endfunction()
# Get architectures of given SDK (iphonesimulator/iphoneos)
function(_ios_install_combined_get_valid_archs sdk resultvar)
cmake_policy(SET CMP0007 NEW)
if("${resultvar}" STREQUAL "")
message(FATAL_ERROR "`resultvar` is empty")
endif()
_ios_install_combined_get_build_setting("${sdk}" "VALID_ARCHS" valid_archs)
separate_arguments(valid_archs)
list(REMOVE_ITEM valid_archs "") # remove empty elements
list(REMOVE_DUPLICATES valid_archs)
set("${resultvar}" "${valid_archs}" PARENT_SCOPE)
endfunction()
# Final target can contain more architectures that specified by SDK. This
# function will run 'lipo -info' and parse output. Result will be returned
# as a CMake list.
function(_ios_install_combined_get_real_archs filename resultvar)
set(cmd "${_lipo_path}" -info "${filename}")
execute_process(
COMMAND ${cmd}
RESULT_VARIABLE result
OUTPUT_VARIABLE output
ERROR_VARIABLE output
OUTPUT_STRIP_TRAILING_WHITESPACE
ERROR_STRIP_TRAILING_WHITESPACE
)
if(NOT result EQUAL 0)
message(
FATAL_ERROR "Command failed (${result}): ${cmd}\n\nOutput:\n${output}"
)
endif()
if(NOT output MATCHES "(Architectures in the fat file: [^\n]+ are|Non-fat file: [^\n]+ is architecture): ([^\n]*)")
message(FATAL_ERROR "Could not detect architecture from: ${output}")
endif()
separate_arguments(CMAKE_MATCH_2)
set(${resultvar} ${CMAKE_MATCH_2} PARENT_SCOPE)
endfunction()
# Run build command for the given SDK
function(_ios_install_combined_build sdk)
if("${sdk}" STREQUAL "")
message(FATAL_ERROR "`sdk` is empty")
endif()
_ios_install_combined_message("Build `${CURRENT_TARGET}` for `${sdk}`")
execute_process(
COMMAND
"${CMAKE_COMMAND}"
--build
.
--target "${CURRENT_TARGET}"
--config ${CURRENT_CONFIG}
--
-sdk "${sdk}"
WORKING_DIRECTORY "${CMAKE_BINARY_DIR}"
RESULT_VARIABLE result
)
if(NOT result EQUAL 0)
message(FATAL_ERROR "Build failed")
endif()
endfunction()
# Remove given architecture from file. This step needed only in rare cases
# when target was built in "unusual" way. Emit warning message.
function(_ios_install_combined_remove_arch lib arch)
_ios_install_combined_message(
"Warning! Unexpected architecture `${arch}` detected and will be removed "
"from file `${lib}`")
set(cmd "${_lipo_path}" -remove ${arch} -output ${lib} ${lib})
execute_process(
COMMAND ${cmd}
RESULT_VARIABLE result
OUTPUT_VARIABLE output
ERROR_VARIABLE output
OUTPUT_STRIP_TRAILING_WHITESPACE
ERROR_STRIP_TRAILING_WHITESPACE
)
if(NOT result EQUAL 0)
message(
FATAL_ERROR "Command failed (${result}): ${cmd}\n\nOutput:\n${output}"
)
endif()
endfunction()
# Check that 'lib' contains only 'archs' architectures (remove others).
function(_ios_install_combined_keep_archs lib archs)
_ios_install_combined_get_real_archs("${lib}" real_archs)
set(archs_to_remove ${real_archs})
list(REMOVE_ITEM archs_to_remove ${archs})
foreach(x ${archs_to_remove})
_ios_install_combined_remove_arch("${lib}" "${x}")
endforeach()
endfunction()
function(_ios_install_combined_detect_sdks this_sdk_var corr_sdk_var)
cmake_policy(SET CMP0057 NEW)
set(this_sdk "$ENV{PLATFORM_NAME}")
if("${this_sdk}" STREQUAL "")
message(FATAL_ERROR "Environment variable PLATFORM_NAME is empty")
endif()
set(all_platforms "$ENV{SUPPORTED_PLATFORMS}")
if("${all_platforms}" STREQUAL "")
message(FATAL_ERROR "Environment variable SUPPORTED_PLATFORMS is empty")
endif()
separate_arguments(all_platforms)
if(NOT this_sdk IN_LIST all_platforms)
message(FATAL_ERROR "`${this_sdk}` not found in `${all_platforms}`")
endif()
list(REMOVE_ITEM all_platforms "" "${this_sdk}")
list(LENGTH all_platforms all_platforms_length)
if(NOT all_platforms_length EQUAL 1)
message(FATAL_ERROR "Expected one element: ${all_platforms}")
endif()
set(${this_sdk_var} "${this_sdk}" PARENT_SCOPE)
set(${corr_sdk_var} "${all_platforms}" PARENT_SCOPE)
endfunction()
# Create combined binary for the given target.
#
# Preconditions:
# * Target already installed at ${destination}
# for the ${PLATFORM_NAME} platform
#
# This function will:
# * Run build for the lacking platform, i.e. opposite to the ${PLATFORM_NAME}
# * Fuse both libraries by running lipo
function(ios_install_combined target destination)
if("${target}" STREQUAL "")
message(FATAL_ERROR "`target` is empty")
endif()
if("${destination}" STREQUAL "")
message(FATAL_ERROR "`destination` is empty")
endif()
if(NOT IS_ABSOLUTE "${destination}")
message(FATAL_ERROR "`destination` is not absolute: ${destination}")
endif()
if(IS_DIRECTORY "${destination}" OR IS_SYMLINK "${destination}")
message(FATAL_ERROR "`destination` is no regular file: ${destination}")
endif()
if("${CMAKE_BINARY_DIR}" STREQUAL "")
message(FATAL_ERROR "`CMAKE_BINARY_DIR` is empty")
endif()
if(NOT IS_DIRECTORY "${CMAKE_BINARY_DIR}")
message(FATAL_ERROR "Is not a directory: ${CMAKE_BINARY_DIR}")
endif()
if("${CMAKE_INSTALL_CONFIG_NAME}" STREQUAL "")
message(FATAL_ERROR "CMAKE_INSTALL_CONFIG_NAME is empty")
endif()
set(cmd xcrun -f lipo)
execute_process(
COMMAND ${cmd}
RESULT_VARIABLE result
OUTPUT_VARIABLE output
ERROR_VARIABLE output
OUTPUT_STRIP_TRAILING_WHITESPACE
ERROR_STRIP_TRAILING_WHITESPACE
)
if(NOT result EQUAL 0)
message(
FATAL_ERROR "Command failed (${result}): ${cmd}\n\nOutput:\n${output}"
)
endif()
set(_lipo_path ${output})
set(CURRENT_CONFIG "${CMAKE_INSTALL_CONFIG_NAME}")
set(CURRENT_TARGET "${target}")
_ios_install_combined_message("Target: ${CURRENT_TARGET}")
_ios_install_combined_message("Config: ${CURRENT_CONFIG}")
_ios_install_combined_message("Destination: ${destination}")
# Get SDKs
_ios_install_combined_detect_sdks(this_sdk corr_sdk)
# Get architectures of the target
_ios_install_combined_get_valid_archs("${corr_sdk}" corr_valid_archs)
_ios_install_combined_get_valid_archs("${this_sdk}" this_valid_archs)
# Return if there are no valid architectures for the SDK.
# (note that library already installed)
if("${corr_valid_archs}" STREQUAL "")
_ios_install_combined_message(
"No architectures detected for `${corr_sdk}` (skip)"
)
return()
endif()
# Trigger build of corresponding target
_ios_install_combined_build("${corr_sdk}")
# Get location of the library in build directory
_ios_install_combined_get_build_setting(
"${corr_sdk}" "CONFIGURATION_BUILD_DIR" corr_build_dir)
_ios_install_combined_get_build_setting(
"${corr_sdk}" "EXECUTABLE_PATH" corr_executable_path)
set(corr "${corr_build_dir}/${corr_executable_path}")
_ios_install_combined_keep_archs("${corr}" "${corr_valid_archs}")
_ios_install_combined_keep_archs("${destination}" "${this_valid_archs}")
_ios_install_combined_message("Current: ${destination}")
_ios_install_combined_message("Corresponding: ${corr}")
set(cmd "${_lipo_path}" -create ${corr} ${destination} -output ${destination})
execute_process(
COMMAND ${cmd}
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
RESULT_VARIABLE result
)
if(NOT result EQUAL 0)
message(FATAL_ERROR "Command failed: ${cmd}")
endif()
_ios_install_combined_message("Install done: ${destination}")
endfunction()

View File

@ -152,13 +152,19 @@ void cmInstallTargetGenerator::GenerateScriptForConfig(std::ostream& os,
// Handle OSX Bundles.
if(this->Target->IsAppBundleOnApple())
{
cmMakefile const* mf = this->Target->Target->GetMakefile();
// Install the whole app bundle directory.
type = cmInstallType_DIRECTORY;
literal_args += " USE_SOURCE_PERMISSIONS";
from1 += ".app";
// Tweaks apply to the binary inside the bundle.
to1 += ".app/Contents/MacOS/";
to1 += ".app/";
if(!mf->PlatformIsAppleIos())
{
to1 += "Contents/MacOS/";
}
to1 += targetName;
}
else
@ -525,6 +531,7 @@ void cmInstallTargetGenerator::PostReplacementTweaks(std::ostream& os,
{
this->AddInstallNamePatchRule(os, indent, config, file);
this->AddChrpathPatchRule(os, indent, config, file);
this->AddUniversalInstallRule(os, indent, file);
this->AddRanlibRule(os, indent, file);
this->AddStripRule(os, indent, file);
}
@ -861,3 +868,46 @@ cmInstallTargetGenerator::AddRanlibRule(std::ostream& os,
os << indent << "execute_process(COMMAND \""
<< ranlib << "\" \"" << toDestDirPath << "\")\n";
}
//----------------------------------------------------------------------------
void
cmInstallTargetGenerator
::AddUniversalInstallRule(std::ostream& os,
Indent const& indent,
const std::string& toDestDirPath)
{
cmMakefile const* mf = this->Target->Target->GetMakefile();
if(!mf->PlatformIsAppleIos() || !mf->IsOn("XCODE"))
{
return;
}
const char* xcodeVersion = mf->GetDefinition("XCODE_VERSION");
if(!xcodeVersion || cmSystemTools::VersionCompareGreater("6", xcodeVersion))
{
return;
}
switch(this->Target->GetType())
{
case cmState::EXECUTABLE:
case cmState::STATIC_LIBRARY:
case cmState::SHARED_LIBRARY:
case cmState::MODULE_LIBRARY:
break;
default:
return;
}
if(!this->Target->Target->GetPropertyAsBool("IOS_INSTALL_COMBINED"))
{
return;
}
os << indent << "include(CMakeIOSInstallCombined)\n";
os << indent << "ios_install_combined("
<< "\"" << this->Target->Target->GetName() << "\" "
<< "\"" << toDestDirPath << "\")\n";
}

View File

@ -101,6 +101,8 @@ protected:
const std::string& toDestDirPath);
void AddRanlibRule(std::ostream& os, Indent const& indent,
const std::string& toDestDirPath);
void AddUniversalInstallRule(std::ostream& os, Indent const& indent,
const std::string& toDestDirPath);
std::string TargetName;
cmGeneratorTarget* Target;

View File

@ -132,6 +132,7 @@ void cmTarget::SetMakefile(cmMakefile* mf)
this->SetPropertyDefault("Fortran_MODULE_DIRECTORY", 0);
this->SetPropertyDefault("GNUtoMS", 0);
this->SetPropertyDefault("OSX_ARCHITECTURES", 0);
this->SetPropertyDefault("IOS_INSTALL_COMBINED", 0);
this->SetPropertyDefault("AUTOMOC", 0);
this->SetPropertyDefault("AUTOUIC", 0);
this->SetPropertyDefault("AUTORCC", 0);

View File

@ -94,3 +94,39 @@ if(NOT XCODE_VERSION VERSION_LESS 7)
run_cmake(XcodeTbdStub)
unset(RunCMake_TEST_OPTIONS)
endif()
if(NOT XCODE_VERSION VERSION_LESS 6)
set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/XcodeIOSInstallCombined-build)
set(RunCMake_TEST_NO_CLEAN 1)
set(RunCMake_TEST_OPTIONS
"-DCMAKE_INSTALL_PREFIX:PATH=${RunCMake_TEST_BINARY_DIR}/_install"
"-DCMAKE_IOS_INSTALL_COMBINED=YES")
file(REMOVE_RECURSE "${RunCMake_TEST_BINARY_DIR}")
file(MAKE_DIRECTORY "${RunCMake_TEST_BINARY_DIR}")
run_cmake(XcodeIOSInstallCombined)
run_cmake_command(XcodeIOSInstallCombined-build ${CMAKE_COMMAND} --build .)
run_cmake_command(XcodeIOSInstallCombined-install ${CMAKE_COMMAND} --build . --target install)
unset(RunCMake_TEST_BINARY_DIR)
unset(RunCMake_TEST_NO_CLEAN)
unset(RunCMake_TEST_OPTIONS)
set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/XcodeIOSInstallCombinedPrune-build)
set(RunCMake_TEST_NO_CLEAN 1)
set(RunCMake_TEST_OPTIONS
"-DCMAKE_INSTALL_PREFIX:PATH=${RunCMake_TEST_BINARY_DIR}/_install"
"-DCMAKE_IOS_INSTALL_COMBINED=YES")
file(REMOVE_RECURSE "${RunCMake_TEST_BINARY_DIR}")
file(MAKE_DIRECTORY "${RunCMake_TEST_BINARY_DIR}")
run_cmake(XcodeIOSInstallCombinedPrune)
run_cmake_command(XcodeIOSInstallCombinedPrune-build ${CMAKE_COMMAND} --build .)
run_cmake_command(XcodeIOSInstallCombinedPrune-install ${CMAKE_COMMAND} --build . --target install)
unset(RunCMake_TEST_BINARY_DIR)
unset(RunCMake_TEST_NO_CLEAN)
unset(RunCMake_TEST_OPTIONS)
endif()

View File

@ -0,0 +1,30 @@
function(verify_architectures file)
execute_process(
COMMAND xcrun otool -vf ${RunCMake_TEST_BINARY_DIR}/_install/${file}
OUTPUT_VARIABLE otool_out
ERROR_VARIABLE otool_err
RESULT_VARIABLE otool_result)
if(NOT otool_result EQUAL "0")
message(SEND_ERROR "Could not retrieve fat headers: ${otool_err}")
return()
endif()
string(REGEX MATCHALL "architecture [^ \n\t]+" architectures ${otool_out})
string(REPLACE "architecture " "" actual "${architectures}")
list(SORT actual)
set(expected arm64 armv7 i386 x86_64)
if(NOT actual STREQUAL expected)
message(SEND_ERROR
"The actual library contains the architectures:\n ${actual} \n"
"which do not match expected ones:\n ${expected} \n"
"otool output:\n${otool_out}")
endif()
endfunction()
verify_architectures(bin/foo_app.app/foo_app)
verify_architectures(lib/libfoo_static.a)
verify_architectures(lib/libfoo_shared.dylib)
verify_architectures(lib/foo_bundle.bundle/foo_bundle)
verify_architectures(lib/foo_framework.framework/foo_framework)

View File

@ -0,0 +1,27 @@
cmake_minimum_required(VERSION 3.3)
project(IOSInstallCombined CXX)
set(CMAKE_OSX_SYSROOT iphoneos)
set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED "NO")
set(CMAKE_XCODE_ATTRIBUTE_DEBUG_INFORMATION_FORMAT "dwarf")
set(CMAKE_XCODE_ATTRIBUTE_ENABLE_BITCODE "NO")
set(CMAKE_OSX_ARCHITECTURES "armv7;arm64;i386;x86_64")
add_executable(foo_app MACOSX_BUNDLE main.cpp)
install(TARGETS foo_app BUNDLE DESTINATION bin)
add_library(foo_static STATIC foo.cpp)
install(TARGETS foo_static ARCHIVE DESTINATION lib)
add_library(foo_shared SHARED foo.cpp)
install(TARGETS foo_shared LIBRARY DESTINATION lib)
add_library(foo_bundle MODULE foo.cpp)
set_target_properties(foo_bundle PROPERTIES BUNDLE TRUE)
install(TARGETS foo_bundle LIBRARY DESTINATION lib)
add_library(foo_framework SHARED foo.cpp)
set_target_properties(foo_framework PROPERTIES FRAMEWORK TRUE)
install(TARGETS foo_framework FRAMEWORK DESTINATION lib)

View File

@ -0,0 +1,26 @@
function(verify_architectures file)
execute_process(
COMMAND xcrun otool -vf ${RunCMake_TEST_BINARY_DIR}/_install/${file}
OUTPUT_VARIABLE otool_out
ERROR_VARIABLE otool_err
RESULT_VARIABLE otool_result)
if(NOT otool_result EQUAL "0")
message(SEND_ERROR "Could not retrieve fat headers: ${otool_err}")
return()
endif()
string(REGEX MATCHALL "architecture [^ \n\t]+" architectures ${otool_out})
string(REPLACE "architecture " "" actual "${architectures}")
list(SORT actual)
set(expected armv7 x86_64)
if(NOT actual STREQUAL expected)
message(SEND_ERROR
"The actual library contains the architectures:\n ${actual} \n"
"which do not match expected ones:\n ${expected} \n"
"otool output:\n${otool_out}")
endif()
endfunction()
verify_architectures(lib/libfoo.dylib)

View File

@ -0,0 +1,2 @@
.*Unexpected architecture `i386` detected.*
.*Unexpected architecture `arm64` detected.*

View File

@ -0,0 +1,36 @@
cmake_minimum_required(VERSION 3.3)
project(XcodeIOSInstallCombinedPrune CXX)
set(CMAKE_OSX_SYSROOT iphoneos)
set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED "NO")
set(CMAKE_XCODE_ATTRIBUTE_DEBUG_INFORMATION_FORMAT "dwarf")
add_library(foo SHARED foo.cpp)
install(TARGETS foo DESTINATION lib)
add_library(baz SHARED foo.cpp)
set_target_properties(
foo baz
PROPERTIES
XCODE_ATTRIBUTE_ARCHS[sdk=iphoneos*] armv7
XCODE_ATTRIBUTE_VALID_ARCHS[sdk=iphoneos*] armv7
XCODE_ATTRIBUTE_ARCHS[sdk=iphonesimulator*] x86_64
XCODE_ATTRIBUTE_VALID_ARCHS[sdk=iphonesimulator*] x86_64
)
add_library(boo SHARED foo.cpp)
set_target_properties(
boo
PROPERTIES
XCODE_ATTRIBUTE_ARCHS[sdk=iphoneos*] arm64
XCODE_ATTRIBUTE_VALID_ARCHS[sdk=iphoneos*] arm64
XCODE_ATTRIBUTE_ARCHS[sdk=iphonesimulator*] i386
XCODE_ATTRIBUTE_VALID_ARCHS[sdk=iphonesimulator*] i386
)
add_custom_command(
TARGET foo
POST_BUILD
COMMAND lipo -create $<TARGET_FILE:baz> $<TARGET_FILE:boo> -output $<TARGET_FILE:foo>
)

View File

@ -0,0 +1,3 @@
int main(int argc, const char * argv[]) {
return 0;
}