diff --git a/README.md b/README.md index 342a665..e05d447 100644 --- a/README.md +++ b/README.md @@ -96,7 +96,8 @@ No matter how you use this CMake build, it exposes the following points of confi Universal: -* `CPPFRONT_NO_MAGIC` -- off by default. When enabled, skips the automatic `cpp2`-to-`cpp` translation. +* `CPPFRONT_NO_MAGIC` -- disabled by default. When disabled, automatically translate `cpp2`-to-`cpp` for all + targets inside the directory where `find_package` is called. * `CPPFRONT_FLAGS` -- a semicolon-separated list of additional flags to pass to `cppfront`. For now, these are assumed to be universal to a project, and it is not supported to change them after the package has loaded, whether via `find_package`, `add_subdirectory`, FetchContent, or any other mechanism. @@ -117,24 +118,27 @@ FetchContent-only: ### Functions ```cmake -cppfront_generate_cpp( ...) +cppfront_generate_files( ...) ``` Writes to the variable named by `OUTVAR` a list of absolute paths to the generated `.cpp` files associated with -each `.cpp2` file in the arguments list. A hashing scheme prevents `cppfront` from running on the same `.cpp2` file -multiple times. +each `.cpp2` file in the arguments list. ```cmake -cppfront_enable( - TARGETS ... -) +cppfront_enable_targets(...) ``` Scans the `SOURCES` properties for each target in `` for entries ending in `.cpp2`. These are passed -to `cppfront_generate_cpp` and the results are added to the target automatically. When `CPPFRONT_NO_MAGIC` is +to `cppfront_generate_files` and the results are added to the target automatically. When `CPPFRONT_NO_MAGIC` is unset (i.e. by default), this command runs on all targets in the directory that imported this package at the end of processing the directory. +```cmake +cppfront_enable_directories(...) +``` + +Recursively scans all targets inside `` and calls `cppfront_enable_targets` for them. + ### Developers The CMake project `regression-tests/CMakeLists.txt` runs the test suite of cppfront. diff --git a/cmake/CppfrontHelpers.cmake b/cmake/CppfrontHelpers.cmake index 891f20f..6b6f0d0 100644 --- a/cmake/CppfrontHelpers.cmake +++ b/cmake/CppfrontHelpers.cmake @@ -1,91 +1,172 @@ -function(_cppfront_unique_name base hash outvar) - string(LENGTH "${hash}" len) - foreach (i RANGE 0 "${len}") - string(SUBSTRING "${hash}" 0 "${i}" uniq) - if (uniq) - set(name "${base}-${uniq}") - else () - set(name "${base}") - endif () - get_property(name_used GLOBAL PROPERTY "cppfront/names/${name}" SET) - if (NOT name_used) - set("${outvar}" "${name}" PARENT_SCOPE) - set_property(GLOBAL PROPERTY "cppfront/names/${name}" 1) - return() - endif () - endforeach () - # This should be impossible, unless caching in _cppfront_generate_source - # is broken. - message(FATAL_ERROR "Could not compute a unique name using ${base} and ${hash}") +# - Helpers for cppfront usage +# This module provides helpers for cppfront usage +# +# These variables can affects the behaviour of this module: +# +# CPPFRONT_NO_MAGIC: +# +# Disabled by default. +# When disabled, automatically translate `cpp2` to `cpp` for all targets +# inside the directory where this module is included. +# +# CPPFRONT_FLAGS: +# +# a semicolon-separated list of additional flags to pass to `cppfront`. +# +# This function translates `cpp2` to `cpp`: +# +# cppfront_generate_files( ...) +# +# These function enables `cpp2`-to-`cpp` translation for targets or targets in directories: +# +# cppfront_enable_targets(...) +# cppfront_enable_directories(...) + +include_guard() + +function(_convert_path_relative_to_source_dir file out) + cmake_path(IS_RELATIVE file is_relative) + + if(is_relative) + cmake_path(ABSOLUTE_PATH file BASE_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} NORMALIZE) + endif() + + cmake_path(RELATIVE_PATH file BASE_DIRECTORY ${CMAKE_SOURCE_DIR}) + + set("${out}" "${file}" PARENT_SCOPE) endfunction() -function(_cppfront_generate_source src out) - file(REAL_PATH "${src}" src) - string(SHA256 src_hash "${src}") - - get_property(out_file GLOBAL PROPERTY "cppfront/out_file/${src_hash}") - if (out_file) - set("${out}" "${out_file}" PARENT_SCOPE) - return() - endif () - - cmake_path(GET src STEM original_stem) - _cppfront_unique_name("${original_stem}" "${src_hash}" basename) - - # assume no SHA256 collisions - file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}/_cppfront/") - if(src MATCHES [[.*\.h2]]) - set(ext ".h") - else() - set(ext ".cpp") - endif() - set(out_file "${CMAKE_BINARY_DIR}/_cppfront/${basename}${ext}") - - add_custom_command( - OUTPUT "${out_file}" - COMMAND cppfront::cppfront "${src}" -o "${out_file}" ${CPPFRONT_FLAGS} - DEPENDS cppfront::cppfront "${src}" - VERBATIM - ) - - set_property(GLOBAL PROPERTY "cppfront/out_file/${src_hash}" "${out_file}") - set("${out}" "${out_file}" PARENT_SCOPE) +# Parse the cpp2 `source` that is relative to `CMAKE_SOURCE_DIR`. +function(_parse_relative_source relative_source out_absolute_source_file out_absolute_binary_file) + cmake_path(GET relative_source PARENT_PATH parent_path) + cmake_path(GET relative_source FILENAME filename) + cmake_path(GET filename STEM LAST_ONLY filestem) + cmake_path(GET relative_source EXTENSION LAST_ONLY extension) + + if(extension MATCHES "\.h2") + set(extension ".h") + else() + set(extension ".cpp") + endif() + + set(absolute_binary "${filestem}${extension}") + cmake_path(ABSOLUTE_PATH absolute_binary BASE_DIRECTORY "${CMAKE_BINARY_DIR}/_cppfront/${parent_path}/" NORMALIZE) + set("${out_absolute_binary_file}" "${absolute_binary}" PARENT_SCOPE) + + cmake_path(ABSOLUTE_PATH relative_source BASE_DIRECTORY "${CMAKE_SOURCE_DIR}" NORMALIZE OUTPUT_VARIABLE absolute_source) + set("${out_absolute_source_file}" "${absolute_source}" PARENT_SCOPE) +endfunction() + +# Writes to the variable named by `OUTVAR` a absolute path to the generated +# `.cpp` file associated with the `.cpp2` file in the arguments list. +function(_cppfront_generate_file file out) + _convert_path_relative_to_source_dir("${file}" source_file) + + _parse_relative_source("${source_file}" absolute_source_file absolute_binary_file) + + cmake_path(GET absolute_binary_file PARENT_PATH binary_directory) + + add_custom_command( + OUTPUT ${absolute_binary_file} + COMMAND ${CMAKE_COMMAND} -E make_directory ${binary_directory} + COMMAND cppfront::cppfront "${absolute_source_file}" -o "${absolute_binary_file}" ${CPPFRONT_FLAGS} + DEPENDS cppfront::cppfront "${absolute_source_file}" + COMMENT "Generating ${absolute_binary_file}" + VERBATIM + ) + + set("${out}" "${absolute_binary_file}" PARENT_SCOPE) endfunction() -function(cppfront_generate_cpp srcs) - set(cpp2srcs "") - foreach (src IN LISTS ARGN) - _cppfront_generate_source("${src}" cpp2) - list(APPEND cpp2srcs "${cpp2}") - endforeach () - set("${srcs}" "${cpp2srcs}" PARENT_SCOPE) +# cppfront_generate_files( ...) +# +# Writes to the variable named by `OUTVAR` a list of absolute paths to the +# generated `.cpp` files associated with each `.cpp2` file in the arguments +# list. +function(cppfront_generate_files out) + set(files "") + + foreach(file IN LISTS ARGN) + _cppfront_generate_file(${file} "output_file") + list(APPEND files "${output_file}") + endforeach() + + set("${out}" "${files}" PARENT_SCOPE) endfunction() -function(cppfront_enable) - cmake_parse_arguments(PARSE_ARGV 0 ARG "" "" "TARGETS") +# Scans the `SOURCES` properties for `` for entries ending in `.cpp2`. +# These are passed to `cppfront_generate_files` and the results are added to the +# target automatically. When `CPPFRONT_NO_MAGIC` is unset (i.e. by default), +# this command runs on all targets in the directory that imported this package +# at the end of processing the directory. +function(_cppfront_enable_target target) + get_property(cpp2sources TARGET "${target}" PROPERTY SOURCES) + list(FILTER cpp2sources INCLUDE REGEX "\\.(cpp|h)2$") + + if(cpp2sources) + target_link_libraries("${target}" PRIVATE cppfront::cpp2util) + get_property(source_dir TARGET "${target}" PROPERTY SOURCE_DIR) - foreach (tgt IN LISTS ARG_TARGETS) - get_property(sources TARGET "${tgt}" PROPERTY SOURCES) - list(FILTER sources INCLUDE REGEX "\\.(cpp|h)2$") + set(cpp2_absolute_sources "") + foreach(source IN LISTS cpp2sources) + cmake_path(IS_RELATIVE source is_relative) + if(is_relative) + cmake_path(ABSOLUTE_PATH source BASE_DIRECTORY ${source_dir} NORMALIZE) + endif() + list(APPEND cpp2_absolute_sources ${source}) + endforeach() + + cppfront_generate_files("cpp1sources" ${cpp2_absolute_sources}) + + add_custom_target("${target}.parse_cpp2" DEPENDS ${cpp1sources}) + add_dependencies("${target}" "${target}.parse_cpp2") + target_sources("${target}" PRIVATE "${cpp1sources}") + endif() +endfunction() - if (sources) - target_link_libraries("${tgt}" PRIVATE cppfront::cpp2util) - cppfront_generate_cpp(cpp1sources ${sources}) - target_sources("${tgt}" PRIVATE ${cpp1sources}) - endif () - endforeach () +# cppfront_enable_targets(...) +# +# Scans the `SOURCES` properties for `` for entries ending in `.cpp2`. +# These are passed to `cppfront_generate_cpp` and the results are added to the +# target automatically. When `CPPFRONT_NO_MAGIC` is unset (i.e. by default), +# this command runs on all targets in the directory that imported this package +# at the end of processing the directory. +function(cppfront_enable_targets) + foreach(target IN LISTS ARGN) + _cppfront_enable_target("${target}") + endforeach() endfunction() -if (NOT CPPFRONT_NO_MAGIC) - function(_cppfront_enable_dir) - get_property(targets DIRECTORY . PROPERTY BUILDSYSTEM_TARGETS) - cppfront_enable(TARGETS ${targets}) - endfunction() +# Recursively scans all targets inside `` and calls `cppfront_enable_targets` for them. +function(_cppfront_enable_directory directory) + function(_cppfront_enable_current_dir directory) + get_property(targets DIRECTORY "${directory}" PROPERTY BUILDSYSTEM_TARGETS) - if (NOT _CPPFRONT_MAGIC_DIR) - set(_CPPFRONT_MAGIC_DIR "${CMAKE_CURRENT_SOURCE_DIR}") - endif () + cppfront_enable_targets(${targets}) + + get_property(subdirs DIRECTORY "${directory}" PROPERTY SUBDIRECTORIES) + + foreach(subdir IN LISTS subdirs) + _cppfront_enable_current_dir("${subdir}") + endforeach() + endfunction() + + message(STATUS "Enabling cppfront for all targets in ${directory}") + cmake_language(DEFER DIRECTORY "${directory}" CALL _cppfront_enable_current_dir "${directory}") +endfunction() + +# cppfront_enable_directories(...) +# +# Recursively scans all targets inside `` and calls +# `cppfront_enable_targets` for them. +function(cppfront_enable_directories) + foreach(directory IN LISTS ARGN) + _cppfront_enable_directory("${directory}") + endforeach() +endfunction() - message(VERBOSE "Enabling cppfront for all targets in ${_CPPFRONT_MAGIC_DIR}") - cmake_language(DEFER DIRECTORY "${_CPPFRONT_MAGIC_DIR}" CALL _cppfront_enable_dir) -endif () +# If `CPPFRONT_NO_MAGIC` not enabled, automatically translate `cpp2`-to-`cpp` +# for all targets inside the directory where this module is included. +if(NOT CPPFRONT_NO_MAGIC) + cppfront_enable_directories(${CMAKE_CURRENT_SOURCE_DIR}) +endif() diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt index f92e1a8..f478387 100644 --- a/example/CMakeLists.txt +++ b/example/CMakeLists.txt @@ -5,4 +5,4 @@ find_package(cppfront REQUIRED) # This works, too: # add_subdirectory(../cppfront cppfront) -add_executable(main main.cpp2) +add_subdirectory(app) diff --git a/example/app/CMakeLists.txt b/example/app/CMakeLists.txt new file mode 100644 index 0000000..36eac36 --- /dev/null +++ b/example/app/CMakeLists.txt @@ -0,0 +1 @@ +add_executable(main main.cpp2) \ No newline at end of file diff --git a/example/main.cpp2 b/example/app/main.cpp2 similarity index 100% rename from example/main.cpp2 rename to example/app/main.cpp2