Skip to content

Commit

Permalink
CMake: make it possible to use cross-compiled corrade-rc via an emula…
Browse files Browse the repository at this point in the history
…tor.

If CMAKE_CROSSCOMPILING_EMULATOR is set and a native corrade-rc isn't
found, the library can build its own corrade-rc and run it through an
emulator, being nicely self-contained without requiring a native build to
be done first.

The CMAKE_CROSSCOMPILING_EMULATOR is set by the builtin Emscripten
toolchain now, but this behavior can be disabled either by passing
-DCMAKE_DISABLE_FIND_PACKAGE_NodeJs=ON to CMake or by explicitly
supplying a corrade-rc location in CORRADE_RC_EXECUTABLE. If that's
specified or the executable is found by other means, the native version
is always preferred, as it's likely always faster.

Then, the corrade-rc executable is now built also when
cross-compiling, to account for cases where CMAKE_CROSSCOMPILING_EMULATOR
may not be set when building Corrade itself, but may be available for
downstream projects, and those may thus want to make use of the emulated
corrade-rc. Conversely, building of the executable can be explicitly
disabled by setting CORRADE_WITH_RC to OFF. For some reason this option
was never used, now it is.

Finally, on the user side, not when compiling Corrade itself, we have two
scenarios. Either corrade-rc is found as a native executable, in which
case nothing changes. Or it's not found and as a fallback we try to look
for the cross-compiled version. That one then needs to have an emulator
used.
  • Loading branch information
mosra committed Oct 4, 2024
1 parent d2dd8c4 commit 868a502
Show file tree
Hide file tree
Showing 6 changed files with 145 additions and 41 deletions.
59 changes: 41 additions & 18 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -127,11 +127,51 @@ if(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
option(CORRADE_MSVC2015_COMPATIBILITY "Enable compatibility mode for MSVC 2015 (might disable some features)" OFF)
endif()

# Attempt to find a native CORRADE_RC_EXECUTABLE when cross-compiling. Needs to
# be done before the CORRADE_WITH_RC option is defined, as that one can be
# turned off if a native one is found.
if(CMAKE_CROSSCOMPILING)
find_program(CORRADE_RC_EXECUTABLE corrade-rc)
# src/Corrade/Utility/CMakeLists.txt sets CORRADE_RC_EXECUTABLE to
# Corrade::rc when a cross-compiled corrade-rc is used via an emulator to
# prevent the find_program() and related logic from being done again for
# each CMake run. If that's the case, we shouldn't attempt to create the
# Corrade::rc target here.
if(CORRADE_RC_EXECUTABLE AND NOT CORRADE_RC_EXECUTABLE STREQUAL Corrade::rc)
# Strictly speaking the GLOBAL isn't needed, but when crosscompiling
# with Corrade as a CMake subproject and
# find_package(Corrade REQUIRED rc) # or Utility
# isn't present for whatever reason, corrade_add_resource() called
# outside of Corrade itself would fail on the rc target not existing.
# We could require everyone to call find_package() to fix this one
# particular issue but that would be kinda mean. Don't be mean. The
# find_package() call is still recommended as it unifies the external
# and subproject workflows but it shouldn't be *required*.
add_executable(Corrade::rc IMPORTED GLOBAL)
set_property(TARGET Corrade::rc PROPERTY IMPORTED_LOCATION ${CORRADE_RC_EXECUTABLE})
# add_custom_command() uses CMAKE_CROSSCOMPILING_EMULATOR only since 3.6.
# It's unlikely that people would use ancient CMake 3.5 for crosscompiling
# so the message doesn't mention that, nevertheless check for the version
# to not make this silently fail much later at build time with 3.5.
elseif(NOT CMAKE_CROSSCOMPILING_EMULATOR OR CMAKE_VERSION VERSION_LESS 3.6)
message(FATAL_ERROR "Native corrade-rc executable, which is needed when crosscompiling without CMAKE_CROSSCOMPILING_EMULATOR set, was not found. Either build it, make it available through PATH or pass its location to CMake using the CORRADE_RC_EXECUTABLE option, or specify CMAKE_CROSSCOMPILING_EMULATOR to have Corrade run the cross-compiled executable instead.")
else()
# Target Corrade::rc gets created in src/Corrade/Utility/CMakeLists.txt
endif()
endif()

option(CORRADE_WITH_INTERCONNECT "Build Interconnect library" ON)
option(CORRADE_WITH_PLUGINMANAGER "Build PluginManager library" ON)
option(CORRADE_WITH_TESTSUITE "Build TestSuite library" ON)
cmake_dependent_option(CORRADE_WITH_UTILITY "Build Utility library" ON "NOT CORRADE_WITH_INTERCONNECT;NOT CORRADE_WITH_PLUGINMANAGER;NOT CORRADE_WITH_TESTSUITE" ON)
cmake_dependent_option(CORRADE_WITH_RC "Build the corrade-rc utility" ON "NOT CORRADE_WITH_UTILITY" ON)
# If we're crosscompiling and the native executable is found, we can skip
# building the cross-compiled version. See above for why it's additionally
# compared against Corrade::rc.
if(CMAKE_CROSSCOMPILING AND CORRADE_RC_EXECUTABLE AND NOT CORRADE_RC_EXECUTABLE STREQUAL Corrade::rc)
option(CORRADE_WITH_RC "Build the corrade-rc utility" ON)
else()
cmake_dependent_option(CORRADE_WITH_RC "Build the corrade-rc utility" ON "NOT CORRADE_WITH_UTILITY" ON)
endif()
cmake_dependent_option(CORRADE_WITH_MAIN "Build Main library" ON "NOT CORRADE_WITH_TESTSUITE;NOT CORRADE_WITH_RC" ON)

option(CORRADE_BUILD_DEPRECATED "Include deprecated API in the build" ON)
Expand Down Expand Up @@ -395,23 +435,6 @@ if(CORRADE_BUILD_TESTS)
endif()
endif()

if(CMAKE_CROSSCOMPILING)
find_program(CORRADE_RC_EXECUTABLE corrade-rc)
if(NOT CORRADE_RC_EXECUTABLE)
message(FATAL_ERROR "Native `corrade-rc` executable, which is needed when crosscompiling, was not found. Build it and either make it available through PATH or pass its location to CMake using the CORRADE_RC_EXECUTABLE option.")
endif()
# Strictly speaking the GLOBAL isn't needed, but when crosscompiling with
# Corrade as a CMake subproject and find_package(Corrade REQUIRED rc) (or
# Utility) isn't present for whatever reason, corrade_add_resource() called
# outside of Corrade itself would fail on the rc target not existing. We
# could require everyone to call find_package() to fix this one particular
# issue but that would be kinda mean. Don't be mean. The find_package()
# call is still recommended as it unifies the external and subproject
# workflows but it shouldn't be *required*.
add_executable(Corrade::rc IMPORTED GLOBAL)
set_property(TARGET Corrade::rc PROPERTY IMPORTED_LOCATION ${CORRADE_RC_EXECUTABLE})
endif()

# If we're in a CMake subproject, find_package(Corrade) will be looking for
# these, so supply their in-source location to cache
if(CORRADE_TESTSUITE_TARGET_XCTEST)
Expand Down
37 changes: 23 additions & 14 deletions doc/building-corrade.dox
Original file line number Diff line number Diff line change
Expand Up @@ -525,9 +525,12 @@ which not:
`CORRADE_WITH_TESTSUITE` is enabled. The @ref Containers library is built
along with this library.
- `CORRADE_WITH_RC` --- Build the @ref corrade-rc "corrade-rc" utility.
Enabled automatically if `CORRADE_WITH_UTILITY` or anything depending on it is
enabled. It's possible to build just this executable without anything else,
for example when crosscompiling.
Enabled automatically if `CORRADE_WITH_UTILITY` or anything depending on it
is enabled. It's also possible to build just this executable without
anything else, for example to have a native version for
@ref building-corrade-cross "cross-compiling". Conversely, if a native
`corrade-rc` is found when cross-compiling, this option can be turned off
to not build a cross-compiled executable at all.

Options controlling the build:

Expand Down Expand Up @@ -729,11 +732,16 @@ git submodule update --init
@m_class{m-block m-primary}

@par Native build of corrade-rc
You also need to have a native version of Corrade installed, because
Corrade needs to run `corrade-rc` utility on the host system as part of the
build process. If native version of `corrade-rc` is not found on the
system, cross-compilation will fail. If `corrade-rc` is not in `PATH`,
point CMake to it using `-DCORRADE_RC_EXECUTABLE=/path/to/corrade-rc`.
Unless the toolchain sets `CORRADE_CROSSCOMPILING_EMULATOR` (which is the
case for example for Emscripten but not necessarily the others), you also
need to have a native version of Corrade installed, because Corrade needs
to run `corrade-rc` utility on the host system as part of the build
process.
@par
If native version of `corrade-rc` is found, or its location is passed
explicitly to CMake with `-DCORRADE_RC_EXECUTABLE=/path/to/corrade-rc`,
it's used. Otherwise, if `CORRADE_CROSSCOMPILING_EMULATOR` is present, the
build will use a cross-compiled version through the emulator.

@subsection building-corrade-cross-winrt Cross-compiling for Windows RT

Expand Down Expand Up @@ -835,18 +843,19 @@ pass it explicitly on command-line using `-DEMSCRIPTEN_PREFIX`. Default is
porting and they are generally slower, thus `CORRADE_BUILD_STATIC` is
implicitly enabled.

Then create build directory and run `cmake` and the build command in it. You
can omit specifying `CORRADE_RC_EXECUTABLE` if
@ref building-corrade-cross-rc "natively-built corrade-rc" is accessible
through `PATH`.
Then create build directory and run `cmake` and the build command in it.
Compared to other cross-compiling cases, the build will build and use its own
`corrade-rc` through Node.js if a
@ref building-corrade-cross-rc "natively-built corrade-rc" isn't found, but you
can always force use of the native version by passing its location through
`CORRADE_RC_EXECUTABLE`.

@code{.sh}
mkdir build-emscripten && cd build-emscripten
cmake .. \
-DCMAKE_TOOLCHAIN_FILE="../toolchains/generic/Emscripten.cmake" \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_INSTALL_PREFIX=/usr/lib/emscripten/system \
-DCORRADE_RC_EXECUTABLE=/path/to/corrade-rc
-DCMAKE_INSTALL_PREFIX=/usr/lib/emscripten/system
cmake --build .
@endcode

Expand Down
5 changes: 5 additions & 0 deletions doc/corrade-changelog.dox
Original file line number Diff line number Diff line change
Expand Up @@ -575,6 +575,11 @@ namespace Corrade {
- The @cmake corrade_add_test() @ce CMake command is fixed to not call
Node.js with itself when `CMAKE_CROSSCOMPILING_EMULATOR` is set (see
[mosra/corrade#185](https://github.com/mosra/corrade/issues/185))
- Corrade can now cross-compile and use its own @ref corrade-rc "corrade-rc"
executable through an emulator if `CMAKE_CROSSCOMPILING_EMULATOR` is set,
instead of requiring a native version of it to be compiled first. See also
[mosra/corrade#81](https://github.com/mosra/corrade/issues/81) and
[mosra/corrade#185](https://github.com/mosra/corrade/issues/185).

@subsection corrade-changelog-latest-bugfixes Bug fixes

Expand Down
27 changes: 27 additions & 0 deletions modules/FindCorrade.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,8 @@
# CORRADE_*_LIBRARY_DEBUG - Debug version of given library, if found
# CORRADE_*_LIBRARY_RELEASE - Release version of given library, if found
# CORRADE_*_EXECUTABLE - Location of given executable, if found
# CORRADE_*_EXECUTABLE_EMULATOR - Emulator to run CORRADE_*_EXECUTABLE, if a
# non-native version was found when cross-compiling
# CORRADE_USE_MODULE - Path to UseCorrade.cmake module (included
# automatically)
# CORRADE_TESTSUITE_XCTEST_RUNNER - Path to XCTestRunner.mm.in file
Expand Down Expand Up @@ -463,6 +465,31 @@ foreach(_component ${Corrade_FIND_COMPONENTS})
find_program(CORRADE_${_COMPONENT}_EXECUTABLE corrade-${_component})
mark_as_advanced(CORRADE_${_COMPONENT}_EXECUTABLE)

# If the executable wasn't found, we're cross-compiling, an
# emulator is set and we're on CMake 3.6+ that actually uses
# CMAKE_CROSSCOMPILING_EMULATOR in add_custom_command((), try to
# find the cross-compiled version as a (slower) fallback. This
# assumes the toolchain sets CMAKE_FIND_ROOT_PATH_MODE_PROGRAM to
# NEVER, i.e. that the search is restricted to native executables
# by default.
if(NOT CORRADE_${_COMPONENT}_EXECUTABLE AND CMAKE_CROSSCOMPILING AND CMAKE_CROSSCOMPILING_EMULATOR AND NOT CMAKE_VERSION VERSION_LESS 3.6)
# Additionally, there are no CMAKE_FIND_PROGRAM_SUFFIXES akin
# to CMAKE_FIND_LIBRARY_SUFFIXES for libraries, so we have to
# try manually.
if(CORRADE_TARGET_EMSCRIPTEN)
set(_CORRADE_PROGRAM_EXTENSION .js)
endif()
find_program(CORRADE_${_COMPONENT}_EXECUTABLE
NAMES
corrade-${_component}
corrade-${_component}${_CORRADE_PROGRAM_EXTENSION}
ONLY_CMAKE_FIND_ROOT_PATH)
if(CORRADE_${_COMPONENT}_EXECUTABLE)
set(CORRADE_${_COMPONENT}_EXECUTABLE_EMULATOR ${CMAKE_CROSSCOMPILING_EMULATOR} CACHE PATH "Emulator for running a cross-compiled corrade-${_component} executable")
mark_as_advanced(CORRADE_${_COMPONENT}_EXECUTABLE_EMULATOR)
endif()
endif()

if(CORRADE_${_COMPONENT}_EXECUTABLE)
set_property(TARGET Corrade::${_component} PROPERTY
IMPORTED_LOCATION ${CORRADE_${_COMPONENT}_EXECUTABLE})
Expand Down
26 changes: 20 additions & 6 deletions modules/UseCorrade.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -593,7 +593,7 @@ function(corrade_add_resource name input)
# See _CORRADE_USE_NO_TARGET_CHECKS in Corrade's root CMakeLists
if(NOT _CORRADE_USE_NO_TARGET_CHECKS AND NOT TARGET Corrade::rc)
if(CMAKE_CROSSCOMPILING)
message(FATAL_ERROR "The Corrade::rc target, needed by corrade_add_resource() and corrade_add_static_plugin(), doesn't exist. Build a native version, either make it available through PATH or pass its location to CMake using the CORRADE_RC_EXECUTABLE option, and add the Utility / rc component to your find_package(Corrade).")
message(FATAL_ERROR "The Corrade::rc target, needed by corrade_add_resource() and corrade_add_static_plugin(), doesn't exist. Either build a native version, make it available through PATH or pass its location to CMake using the CORRADE_RC_EXECUTABLE option, or specify CMAKE_CROSSCOMPILING_EMULATOR to have Corrade run a cross-compiled executable instead, and add the Utility / rc component to your find_package(Corrade).")
else()
message(FATAL_ERROR "The Corrade::rc target, needed by corrade_add_resource() and corrade_add_static_plugin(), doesn't exist. Add the Utility / rc component to your find_package(Corrade) or enable CORRADE_WITH_UTILITY / CORRADE_WITH_RC if you have Corrade as a CMake subproject.")
endif()
Expand Down Expand Up @@ -635,10 +635,17 @@ function(corrade_add_resource name input)
# recognized automatically)
set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS ${input})

# Run command
# Run command. If a non-native Corrade::rc was found and needs to be run
# through an emulator, we need to pass the actual executable path to it,
# not just the target.
if(CORRADE_RC_EXECUTABLE_EMULATOR)
set(command ${CORRADE_RC_EXECUTABLE_EMULATOR} $<TARGET_FILE:Corrade::rc>)
else()
set(command Corrade::rc)
endif()
add_custom_command(
OUTPUT "${out}"
COMMAND Corrade::rc ${single_file} ${name} "${input}" "${out}"
COMMAND ${command} ${single_file} ${name} "${input}" "${out}"
DEPENDS Corrade::rc ${input} ${dependencies}
COMMENT "Compiling data resource file ${out}"
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}")
Expand Down Expand Up @@ -771,12 +778,19 @@ function(corrade_add_static_plugin plugin_name install_dirs metadata_file)
endif()

# Compile resources. If the metadata file is disabled, the resource is
# empty.
# empty. If a non-native Corrade::rc was found and needs to be run through
# an emulator, we need to pass the actual executable path to it, not just
# the target.
if(CORRADE_RC_EXECUTABLE_EMULATOR)
set(command ${CORRADE_RC_EXECUTABLE_EMULATOR} $<TARGET_FILE:Corrade::rc>)
else()
set(command Corrade::rc)
endif()
set(resource_out ${CMAKE_CURRENT_BINARY_DIR}/resource_${plugin_name}.cpp)
if(metadata_file)
add_custom_command(
OUTPUT ${resource_out}
COMMAND Corrade::rc ${plugin_name} --single ${metadata_file} ${resource_out}
COMMAND ${command} ${plugin_name} --single ${metadata_file} ${resource_out}
DEPENDS Corrade::rc ${metadata_file}
COMMENT "Compiling static plugin metadata file ${resource_out}"
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
Expand All @@ -785,7 +799,7 @@ function(corrade_add_static_plugin plugin_name install_dirs metadata_file)
add_custom_command(
OUTPUT ${resource_out}
COMMAND ${CMAKE_COMMAND} -E touch ${metadata_file}
COMMAND Corrade::rc ${plugin_name} --single ${metadata_file} ${resource_out}
COMMAND ${command} ${plugin_name} --single ${metadata_file} ${resource_out}
DEPENDS Corrade::rc
COMMENT "Compiling static plugin metadata file ${resource_out}"
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
Expand Down
32 changes: 29 additions & 3 deletions src/Corrade/Utility/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -318,7 +318,17 @@ endif()
# However, it seems like this feature never materialized, as doing this will
# result in corrade-rc that's looking for vcruntime140_app.dll in order to be
# run. Last checked: Nov 2019.
if(NOT CMAKE_CROSSCOMPILING)
if(CORRADE_WITH_RC)
# If CMAKE_CROSSCOMPILING is set, either the CORRADE_RC_EXECUTABLE has to
# be found in root CMakeLists, or a FATAL_ERROR is printed there, or this
# message gets printed.
if(CMAKE_CROSSCOMPILING AND CMAKE_CROSSCOMPILING_EMULATOR AND NOT CORRADE_RC_EXECUTABLE AND NOT CMAKE_VERSION VERSION_LESS 3.6)
message(STATUS "Native CORRADE_RC_EXECUTABLE not found, using a cross-compiled corrade-rc")
# Set the variable to not attempt to look for corrade-rc and print the
# above again on the next run
set(CORRADE_RC_EXECUTABLE Corrade::rc CACHE INTERNAL "")
endif()

# Sources for standalone corrade-rc
set(CorradeUtilityRc_SRCS
Arguments.cpp
Expand Down Expand Up @@ -382,8 +392,24 @@ if(NOT CMAKE_CROSSCOMPILING)
if(CORRADE_TARGET_UNIX)
target_link_libraries(corrade-rc PRIVATE ${CMAKE_DL_LIBS})
endif()
# On Emscripten, we'd like to access the actual real filesystem, not the
# virtual one
if(CORRADE_TARGET_EMSCRIPTEN)
# TODO switch to target_link_options() and SHELL: once we require CMake
# 3.13 unconditionally
target_link_libraries(corrade-rc PUBLIC "-s NODERAWFS=1")
endif()
install(TARGETS corrade-rc DESTINATION ${CORRADE_BINARY_INSTALL_DIR})
if(CORRADE_TARGET_EMSCRIPTEN)
install(FILES
$<TARGET_FILE_DIR:corrade-rc>/corrade-rc.js.mem
$<TARGET_FILE_DIR:corrade-rc>/corrade-rc.wasm
DESTINATION ${CORRADE_BINARY_INSTALL_DIR} OPTIONAL)
endif()

# Corrade::rc target alias for superprojects
add_executable(Corrade::rc ALIAS corrade-rc)
# Corrade::rc target alias for superprojects. Create only if it wasn't
# already created in root CMakeLists for CORRADE_RC_EXECUTABLE.
if(NOT TARGET Corrade::rc)
add_executable(Corrade::rc ALIAS corrade-rc)
endif()
endif()

0 comments on commit 868a502

Please sign in to comment.