1
0
mirror of synced 2024-11-28 09:30:51 +01:00
ImHex/cmake/ide_helpers.cmake
David Mentler bdaf1e4151
build: Xcode accomodating CMake setup (#1688)
### Problem description
This PR implements some rudimentary Xcode support for building and
editing ImHex.

### Implementation description

#### Problem 1: Xcode is a multi-configuration buildsystem
The project is already rather CMake generator independent, thus it did
not need to change much to support Xcode's multi-configuration paradigm:

By default, CMake generates a `.xcodeproj` in which targets build their
artifacts into the specified `<>_OUTPUT_DIRECTORY`, postfixed by the
currently active configuration. To better fit the existing paradigm, I
instead opted ot introduce `IMHEX_MAIN_OUTPUT_DIRECTORY`. This variable
is equal to the previously used `RUNTIME_OUTPUT_DIRECTORY` when using
other generators, and is changed to include a configuration specific
_prefix_ when used with Xcode.

The result is different output directories when using Xcode, and no
changes when using any other generator.

#### Problem 2: ImHex does not support AppleClang
To allow building the codebase with Xcode, I have introduced
`IMHEX_IDE_HELPERS_OVERRIDE_XCODE_COMPILER`. Specifying this option to
`ON` will force CMake to honor the user specified compiler settings,
even when using the Xcode generator.

In practice this can be used together with the new "xcode" CMakePreset
to build the project with mainline clang using `xcodebuild`, or Xcode
itself by generating a buildsystem like so:
```
cmake --preset xcode -DCMAKE_PREFIX_PATH=/opt/homebrew/opt/llvm@17
```

This solution is of course not without flaws. The inner workings are a
particularly ugly hack, and mainline clang does not implement the
necessary extensions to allow Xcode to index the code. Regardless this
option is useful to enable future work in terms of bundling/signing
macOS applications in the "intended" way using Xcode without additional
source modifications.

#### Problem 3: Vanilla CMake + Xcode = Bad developer UX 
By default, the CMake generated `.xcodeproj` is a mess. Tons of targets
are scattered about, and source files are not organized beyond grouping
them into a "Source Files" and "Header Files" group.

Even "Header Files" is missing, because the ImHex build system does not
regard private header files of libraries as sources of a target, and
Xcode does not try to guess this information.

The solution is twofold:
* Additional code has been added which organizes the targets into a neat
folder structure
* Additional code was added behind a configuration flag
`IMHEX_IDE_HELPERS_INTRUSIVE_IDE_TWEAKS` which automatically creates
source file trees in Xcode targets, and discovers the non-declared
header files via the folder convention.

### Screenshots
N/A

### Additional things

As a bonus: `IMHEX_OFFLINE_BUILD` assumes that ImHex-Patterns is cloned
into the source tree. I have added an additional fallback that tries to
locate it as a sibling folder of `${CMAKE_SOURCE_DIR}`, as this meshes
better with my filesystem setup.

The setup was tested with `CMake 3.29.2`, `Xcode 15.2`, and `llvm@17`
from homebrew.
2024-05-20 10:12:57 +00:00

150 lines
5.1 KiB
CMake

option(IMHEX_IDE_HELPERS_OVERRIDE_XCODE_COMPILER "Enable choice of compiler for Xcode builds, despite CMake's best efforts" OFF)
option(IMHEX_IDE_HELPERS_INTRUSIVE_IDE_TWEAKS "Enable intrusive CMake tweaks to better support IDEs with folder support" OFF)
# The CMake infrastructure silently ignores the CMAKE_<>_COMPILER settings when
# using the `Xcode` generator.
#
# A particularly nasty (and potentially only) way of getting around this is to
# temporarily lie about the generator being used, while CMake determines and
# locks in the compiler to use.
#
# Needless to say, this is hacky and fragile. Use at your own risk!
if (IMHEX_IDE_HELPERS_OVERRIDE_XCODE_COMPILER AND CMAKE_GENERATOR STREQUAL "Xcode")
set(CMAKE_GENERATOR "Unknown")
enable_language(C CXX)
set(CMAKE_GENERATOR "Xcode")
set(CMAKE_XCODE_ATTRIBUTE_CC "${CMAKE_C_COMPILER}")
set(CMAKE_XCODE_ATTRIBUTE_CXX "${CMAKE_CXX_COMPILER}")
if (CLANG)
set(CMAKE_XCODE_ATTRIBUTE_LD "${CMAKE_C_COMPILER}")
set(CMAKE_XCODE_ATTRIBUTE_LDPLUSPLUS "${CMAKE_CXX_COMPILER}")
endif()
# By default Xcode passes a `-index-store-path=<...>` parameter to the compiler
# during builds to build code completion indexes. This is not supported by
# anything other than AppleClang
set(CMAKE_XCODE_ATTRIBUTE_COMPILER_INDEX_STORE_ENABLE "NO")
endif()
# Generate a launch/build scheme for all targets
set(CMAKE_XCODE_GENERATE_SCHEME YES)
# Utility function that helps avoid messing with non-standard targets
macro(returnIfTargetIsNonTweakable target)
get_target_property(targetIsAliased ${target} ALIASED_TARGET)
get_target_property(targetIsImported ${target} IMPORTED)
if (targetIsAliased OR targetIsImported)
return()
endif()
get_target_property(targetType ${target} TYPE)
if (targetType MATCHES "INTERFACE_LIBRARY|UNKNOWN_LIBRARY")
return()
endif()
endmacro()
# Targets usually don't specify their private headers, nor group their source files
# which results in very spotty coverage by IDEs with folders support
#
# Unfortunately, CMake does not have a `target_source_group` like construct yet, therefore
# we have to play by the limitations of `source_group`.
#
# A particularly problematic part is that the function must be called within the directoryies
# scope for the grouping to take effect.
#
# See: https://discourse.cmake.org/t/topic/7388
function(tweakTargetForIDESupport target)
returnIfTargetIsNonTweakable(${target})
# Don't assume directory structure of third parties
get_target_property(targetSourceDir ${target} SOURCE_DIR)
if (targetSourceDir MATCHES "third_party")
return()
endif()
# Add headers to target
get_target_property(targetSourceDir ${target} SOURCE_DIR)
if (targetSourceDir)
file(GLOB_RECURSE targetPrivateHeaders CONFIGURE_DEPENDS "${targetSourceDir}/include/*.hpp")
target_sources(${target} PRIVATE "${targetPrivateHeaders}")
endif()
# Organize target sources into directory tree
get_target_property(sources ${target} SOURCES)
foreach(file IN LISTS sources)
get_filename_component(path "${file}" ABSOLUTE)
if (NOT path MATCHES "^${targetSourceDir}")
continue()
endif()
source_group(TREE "${targetSourceDir}" PREFIX "Source Tree" FILES "${file}")
endforeach()
endfunction()
if (IMHEX_IDE_HELPERS_INTRUSIVE_IDE_TWEAKS)
# See tweakTargetForIDESupport for rationale
function(add_library target)
_add_library(${target} ${ARGN})
tweakTargetForIDESupport(${target})
endfunction()
function(add_executable target)
_add_executable(${target} ${ARGN})
tweakTargetForIDESupport(${target})
endfunction()
endif()
# Adjust target's FOLDER property, which is an IDE only preference
function(_tweakTarget target path)
get_target_property(targetType ${target} TYPE)
if (TARGET generator-${target})
set_target_properties(generator-${target} PROPERTIES FOLDER "romfs/${target}")
endif()
if (TARGET romfs_file_packer-${target})
set_target_properties(romfs_file_packer-${target} PROPERTIES FOLDER "romfs/${target}")
endif()
if (TARGET libromfs-${target})
set_target_properties(libromfs-${target} PROPERTIES FOLDER "romfs/${target}")
endif()
if (${targetType} MATCHES "EXECUTABLE|LIBRARY")
set_target_properties(${target} PROPERTIES FOLDER "${path}")
endif()
endfunction()
macro(_tweakTargetsRecursive dir)
get_property(subdirectories DIRECTORY ${dir} PROPERTY SUBDIRECTORIES)
foreach(subdir IN LISTS subdirectories)
_tweakTargetsRecursive("${subdir}")
endforeach()
if(${dir} STREQUAL ${CMAKE_SOURCE_DIR})
return()
endif()
get_property(targets DIRECTORY "${dir}" PROPERTY BUILDSYSTEM_TARGETS)
file(RELATIVE_PATH rdir ${CMAKE_SOURCE_DIR} "${dir}/..")
foreach(target ${targets})
_tweakTarget(${target} "${rdir}")
endforeach()
endmacro()
# Tweak all targets this CMake build is aware about
function(tweakTargetsForIDESupport)
set_property(GLOBAL PROPERTY USE_FOLDERS ON)
_tweakTargetsRecursive("${CMAKE_SOURCE_DIR}")
endfunction()