Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make directory-level enabling recursive #122

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 12 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -117,24 +118,27 @@ FetchContent-only:
### Functions

```cmake
cppfront_generate_cpp(<OUTVAR> <cpp2 files>...)
cppfront_generate_files(<OUTVAR> <cpp2 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 <targets>...
)
cppfront_enable_targets(<targets>...)
```

Scans the `SOURCES` properties for each target in `<targets>` 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(<directories>...)
```

Recursively scans all targets inside `<directories>` and calls `cppfront_enable_targets` for them.

### Developers

The CMake project `regression-tests/CMakeLists.txt` runs the test suite of cppfront.
Expand Down
239 changes: 160 additions & 79 deletions cmake/CppfrontHelpers.cmake
Original file line number Diff line number Diff line change
@@ -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(<OUTVAR> <cpp2 files>...)
#
# These function enables `cpp2`-to-`cpp` translation for targets or targets in directories:
#
# cppfront_enable_targets(<targets>...)
# cppfront_enable_directories(<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(<OUTVAR> <cpp2 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 `<target>` 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(<targets>...)
#
# Scans the `SOURCES` properties for `<targets>` 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 `<directory>` 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(<directories>...)
#
# Recursively scans all targets inside `<directories>` 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()
2 changes: 1 addition & 1 deletion example/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@ find_package(cppfront REQUIRED)
# This works, too:
# add_subdirectory(../cppfront cppfront)

add_executable(main main.cpp2)
add_subdirectory(app)
1 change: 1 addition & 0 deletions example/app/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
add_executable(main main.cpp2)
File renamed without changes.
Loading