From cb55f653284464226c1560bfe0788aed45964064 Mon Sep 17 00:00:00 2001 From: Andrew Kaster Date: Mon, 5 Aug 2024 20:45:00 -0600 Subject: [PATCH] Meta: Add a script and CMake function to generate a clang module map In theory the clang module map should not have absolute paths for the headers. Other Swift projects seem to use the -ivfsoverlay feature of clang to work around this, but it seems difficult to get to work. --- .github/actions/setup/action.yml | 2 +- Meta/CMake/Swift/GenerateSwiftHeader.cmake | 3 +- Meta/CMake/code_generators.cmake | 25 ++++++++ Meta/generate_clang_module_map.py | 69 ++++++++++++++++++++++ 4 files changed, 97 insertions(+), 2 deletions(-) create mode 100644 Meta/generate_clang_module_map.py diff --git a/.github/actions/setup/action.yml b/.github/actions/setup/action.yml index 4fffea44445..a51729f3899 100644 --- a/.github/actions/setup/action.yml +++ b/.github/actions/setup/action.yml @@ -53,7 +53,7 @@ runs: set -e sudo xcode-select --switch /Applications/Xcode_16.0.app brew update - brew install autoconf autoconf-archive automake bash ccache coreutils ffmpeg llvm@18 nasm ninja qt unzip wabt + brew install autoconf autoconf-archive automake bash ccache coreutils ffmpeg llvm@18 nasm ninja qt unzip wabt pyyaml - name: 'Install vcpkg' shell: bash diff --git a/Meta/CMake/Swift/GenerateSwiftHeader.cmake b/Meta/CMake/Swift/GenerateSwiftHeader.cmake index 3ed3fa60b1a..0182fa52a0e 100644 --- a/Meta/CMake/Swift/GenerateSwiftHeader.cmake +++ b/Meta/CMake/Swift/GenerateSwiftHeader.cmake @@ -23,7 +23,7 @@ function(_swift_generate_cxx_header target header) return() endif() - cmake_parse_arguments(PARSE_ARGV 2 "ARG" "" "MODULE_NAME;CXX_STD_VERSION" "SEARCH_PATHS") + cmake_parse_arguments(PARSE_ARGV 2 "ARG" "" "MODULE_NAME;CXX_STD_VERSION" "SEARCH_PATHS;COMPILE_OPTIONS") if(NOT ARG_MODULE_NAME) set(target_module_name $) @@ -70,6 +70,7 @@ function(_swift_generate_cxx_header target header) ${_SwiftSources} ${SDK_FLAGS} ${CXX_STD_FLAGS} + ${ARG_COMPILE_OPTIONS} -Xcc -Wno-unqualified-std-cast-call -Xcc -Wno-user-defined-literals -Xcc -Wno-unknown-warning-option diff --git a/Meta/CMake/code_generators.cmake b/Meta/CMake/code_generators.cmake index ed54591ea61..07cccf4dec7 100644 --- a/Meta/CMake/code_generators.cmake +++ b/Meta/CMake/code_generators.cmake @@ -23,6 +23,31 @@ function(embed_as_string_view name source_file output source_variable_name) add_dependencies(all_generated "generate_${name}") endfunction() +function(generate_clang_module_map target_name) + cmake_parse_arguments(PARSE_ARGV 1 MODULE_MAP "" "DIRECTORY" "GENERATED_FILES") + if (NOT MODULE_MAP_DIRECTORY) + set(MODULE_MAP_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}") + endif() + + set(module_map_file "${CMAKE_CURRENT_BINARY_DIR}/module/module.modulemap") + set(vfs_overlay_file "${CMAKE_CURRENT_BINARY_DIR}/vfs_overlay.yaml") + + find_package(Python3 REQUIRED COMPONENTS Interpreter) + # FIXME: Make this depend on the public headers of the target + add_custom_command( + OUTPUT "${module_map_file}" + COMMAND "${Python3_EXECUTABLE}" "${SerenityOS_SOURCE_DIR}/Meta/generate_clang_module_map.py" "${MODULE_MAP_DIRECTORY}" --module-map "${module_map_file}" --vfs-map ${vfs_overlay_file} ${MODULE_MAP_GENERATED_FILES} + VERBATIM + DEPENDS "${SerenityOS_SOURCE_DIR}/Meta/generate_clang_module_map.py" + ) + + add_custom_target("generate_${target_name}_module_map" DEPENDS "${module_map_file}") + add_dependencies(all_generated "generate_${target_name}_module_map") + add_dependencies("${target_name}" "generate_${target_name}_module_map") + + target_compile_options(${target_name} PUBLIC "SHELL:$<$:-Xcc -ivfsoverlay${vfs_overlay_file}>") +endfunction() + function(compile_ipc source output) if (NOT IS_ABSOLUTE ${source}) set(source ${CMAKE_CURRENT_SOURCE_DIR}/${source}) diff --git a/Meta/generate_clang_module_map.py b/Meta/generate_clang_module_map.py new file mode 100644 index 00000000000..6da2e80315d --- /dev/null +++ b/Meta/generate_clang_module_map.py @@ -0,0 +1,69 @@ +#!/usr/bin/env python3 +""" + Generates a clang module map for a given directory +""" + +import argparse +import pathlib +import yaml +import sys + + +def write_file_if_not_same(file_path, content): + try: + with open(file_path, 'r') as f: + if f.read() == content: + return + except FileNotFoundError: + pass + + with open(file_path, 'w') as f: + f.write(content) + + +def main(): + parser = argparse.ArgumentParser( + epilog=__doc__, + formatter_class=argparse.RawDescriptionHelpFormatter) + parser.add_argument('directory', help='source directory to generate module map for') + parser.add_argument('generated_files', nargs='+', help='extra files to include in the module map') + parser.add_argument('-m', '--module-map', required=True, help='output module map file') + parser.add_argument('-v', '--vfs-map', required=True, help='output VFS map file') + args = parser.parse_args() + + root = pathlib.Path(args.directory) + if not root.is_dir(): + print(f"Error: {args.directory} is not a directory", file=sys.stderr) + return 1 + pathlib.Path(args.module_map).parent.mkdir(parents=True, exist_ok=True) + + header_files = [f for f in root.rglob('**/*.h') if f.is_file()] + module_name = root.name + + module_map = f"module {module_name} {{\n" + for header_file in header_files: + module_map += f" header \"{header_file.relative_to(root)}\"\n" + for generated_file in args.generated_files: + module_map += f" header \"{generated_file}\"\n" + module_map += " requires cplusplus\n" + module_map += " export *\n" + module_map += "}\n" + + vfs_map = { + "version": 0, + "use-external-names": False, + "roots": [ + { + "name": f"{root}/module.modulemap", + "type": "file", + "external-contents": f"{args.module_map}" + } + ] + } + + write_file_if_not_same(args.module_map, module_map) + write_file_if_not_same(args.vfs_map, yaml.dump(vfs_map)) + + +if __name__ == '__main__': + sys.exit(main())