mirror of
https://github.com/yhirose/cpp-httplib.git
synced 2026-04-11 19:28:30 +00:00
Add C++ modules support (#2291)
* Add C++ modules support * Add module examples * Missing semicolon * Update GitHub Actions script and create a modules updating script * Name the unused param * Use the guarded/direct export of header approach * Update CMakeLists.txt Co-authored-by: Andrea Pappacoda <andrea@pappacoda.it> * Update CMakeLists.txt Co-authored-by: Andrea Pappacoda <andrea@pappacoda.it> * Split scripts into split.py and generate_module.py --------- Co-authored-by: Andrea Pappacoda <andrea@pappacoda.it>
This commit is contained in:
@@ -5,6 +5,7 @@
|
|||||||
* HTTPLIB_USE_ZLIB_IF_AVAILABLE (default on)
|
* HTTPLIB_USE_ZLIB_IF_AVAILABLE (default on)
|
||||||
* HTTPLIB_USE_BROTLI_IF_AVAILABLE (default on)
|
* HTTPLIB_USE_BROTLI_IF_AVAILABLE (default on)
|
||||||
* HTTPLIB_USE_ZSTD_IF_AVAILABLE (default on)
|
* HTTPLIB_USE_ZSTD_IF_AVAILABLE (default on)
|
||||||
|
* HTTPLIB_BUILD_MODULES (default off)
|
||||||
* HTTPLIB_REQUIRE_OPENSSL (default off)
|
* HTTPLIB_REQUIRE_OPENSSL (default off)
|
||||||
* HTTPLIB_REQUIRE_ZLIB (default off)
|
* HTTPLIB_REQUIRE_ZLIB (default off)
|
||||||
* HTTPLIB_REQUIRE_BROTLI (default off)
|
* HTTPLIB_REQUIRE_BROTLI (default off)
|
||||||
@@ -110,6 +111,15 @@ option(HTTPLIB_USE_CERTS_FROM_MACOSX_KEYCHAIN "Enable feature to load system cer
|
|||||||
option(HTTPLIB_USE_NON_BLOCKING_GETADDRINFO "Enables the non-blocking alternatives for getaddrinfo." ON)
|
option(HTTPLIB_USE_NON_BLOCKING_GETADDRINFO "Enables the non-blocking alternatives for getaddrinfo." ON)
|
||||||
option(HTTPLIB_REQUIRE_ZSTD "Requires ZSTD to be found & linked, or fails build." OFF)
|
option(HTTPLIB_REQUIRE_ZSTD "Requires ZSTD to be found & linked, or fails build." OFF)
|
||||||
option(HTTPLIB_USE_ZSTD_IF_AVAILABLE "Uses ZSTD (if available) to enable zstd support." ON)
|
option(HTTPLIB_USE_ZSTD_IF_AVAILABLE "Uses ZSTD (if available) to enable zstd support." ON)
|
||||||
|
# C++20 modules support requires CMake 3.28 or later
|
||||||
|
if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.28")
|
||||||
|
option(HTTPLIB_BUILD_MODULES "Build httplib modules (requires HTTPLIB_COMPILE to be ON)." OFF)
|
||||||
|
else()
|
||||||
|
set(HTTPLIB_BUILD_MODULES OFF CACHE INTERNAL "Build httplib modules disabled (requires CMake 3.28+)" FORCE)
|
||||||
|
if(DEFINED CACHE{HTTPLIB_BUILD_MODULES} AND HTTPLIB_BUILD_MODULES)
|
||||||
|
message(WARNING "HTTPLIB_BUILD_MODULES requires CMake 3.28 or later. Current version is ${CMAKE_VERSION}. Modules support has been disabled.")
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
# Defaults to static library but respects standard BUILD_SHARED_LIBS if set
|
# Defaults to static library but respects standard BUILD_SHARED_LIBS if set
|
||||||
include(CMakeDependentOption)
|
include(CMakeDependentOption)
|
||||||
cmake_dependent_option(HTTPLIB_SHARED "Build the library as a shared library instead of static. Has no effect if using header-only."
|
cmake_dependent_option(HTTPLIB_SHARED "Build the library as a shared library instead of static. Has no effect if using header-only."
|
||||||
@@ -240,6 +250,22 @@ if(HTTPLIB_COMPILE)
|
|||||||
message(FATAL_ERROR "Failed when trying to split cpp-httplib with the Python script.\n${_httplib_split_error}")
|
message(FATAL_ERROR "Failed when trying to split cpp-httplib with the Python script.\n${_httplib_split_error}")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
# If building modules, also generate the module file
|
||||||
|
if(HTTPLIB_BUILD_MODULES)
|
||||||
|
# Put the generate_module script into the build dir
|
||||||
|
configure_file(generate_module.py "${CMAKE_CURRENT_BINARY_DIR}/generate_module.py"
|
||||||
|
COPYONLY
|
||||||
|
)
|
||||||
|
# Generate the module file
|
||||||
|
execute_process(COMMAND ${Python3_EXECUTABLE} "${CMAKE_CURRENT_BINARY_DIR}/generate_module.py"
|
||||||
|
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
|
||||||
|
ERROR_VARIABLE _httplib_module_error
|
||||||
|
)
|
||||||
|
if(_httplib_module_error)
|
||||||
|
message(FATAL_ERROR "Failed when trying to generate cpp-httplib module with the Python script.\n${_httplib_module_error}")
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
# split.py puts output in "out"
|
# split.py puts output in "out"
|
||||||
set(_httplib_build_includedir "${CMAKE_CURRENT_BINARY_DIR}/out")
|
set(_httplib_build_includedir "${CMAKE_CURRENT_BINARY_DIR}/out")
|
||||||
add_library(${PROJECT_NAME} ${HTTPLIB_LIB_TYPE} "${_httplib_build_includedir}/httplib.cc")
|
add_library(${PROJECT_NAME} ${HTTPLIB_LIB_TYPE} "${_httplib_build_includedir}/httplib.cc")
|
||||||
@@ -248,6 +274,13 @@ if(HTTPLIB_COMPILE)
|
|||||||
$<BUILD_INTERFACE:${_httplib_build_includedir}/httplib.h>
|
$<BUILD_INTERFACE:${_httplib_build_includedir}/httplib.h>
|
||||||
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/httplib.h>
|
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/httplib.h>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Add C++20 module support if requested
|
||||||
|
# Include from separate file to prevent parse errors on older CMake versions
|
||||||
|
if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.28")
|
||||||
|
include(cmake/modules.cmake)
|
||||||
|
endif()
|
||||||
|
|
||||||
set_target_properties(${PROJECT_NAME}
|
set_target_properties(${PROJECT_NAME}
|
||||||
PROPERTIES
|
PROPERTIES
|
||||||
VERSION ${${PROJECT_NAME}_VERSION}
|
VERSION ${${PROJECT_NAME}_VERSION}
|
||||||
@@ -264,8 +297,12 @@ endif()
|
|||||||
# Only useful if building in-tree, versus using it from an installation.
|
# Only useful if building in-tree, versus using it from an installation.
|
||||||
add_library(${PROJECT_NAME}::${PROJECT_NAME} ALIAS ${PROJECT_NAME})
|
add_library(${PROJECT_NAME}::${PROJECT_NAME} ALIAS ${PROJECT_NAME})
|
||||||
|
|
||||||
# Require C++11
|
# Require C++11, or C++20 if modules are enabled
|
||||||
|
if(HTTPLIB_BUILD_MODULES)
|
||||||
|
target_compile_features(${PROJECT_NAME} ${_INTERFACE_OR_PUBLIC} cxx_std_20)
|
||||||
|
else()
|
||||||
target_compile_features(${PROJECT_NAME} ${_INTERFACE_OR_PUBLIC} cxx_std_11)
|
target_compile_features(${PROJECT_NAME} ${_INTERFACE_OR_PUBLIC} cxx_std_11)
|
||||||
|
endif()
|
||||||
|
|
||||||
target_include_directories(${PROJECT_NAME} SYSTEM ${_INTERFACE_OR_PUBLIC}
|
target_include_directories(${PROJECT_NAME} SYSTEM ${_INTERFACE_OR_PUBLIC}
|
||||||
$<BUILD_INTERFACE:${_httplib_build_includedir}>
|
$<BUILD_INTERFACE:${_httplib_build_includedir}>
|
||||||
@@ -337,7 +374,11 @@ if(HTTPLIB_INSTALL)
|
|||||||
# Creates the export httplibTargets.cmake
|
# Creates the export httplibTargets.cmake
|
||||||
# This is strictly what holds compilation requirements
|
# This is strictly what holds compilation requirements
|
||||||
# and linkage information (doesn't find deps though).
|
# and linkage information (doesn't find deps though).
|
||||||
|
if(HTTPLIB_BUILD_MODULES)
|
||||||
|
install(TARGETS ${PROJECT_NAME} EXPORT httplibTargets FILE_SET CXX_MODULES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/httplib/modules CXX_MODULES_BMI DESTINATION ${CMAKE_INSTALL_LIBDIR}/httplib/modules)
|
||||||
|
else()
|
||||||
install(TARGETS ${PROJECT_NAME} EXPORT httplibTargets)
|
install(TARGETS ${PROJECT_NAME} EXPORT httplibTargets)
|
||||||
|
endif()
|
||||||
|
|
||||||
install(FILES "${_httplib_build_includedir}/httplib.h" TYPE INCLUDE)
|
install(FILES "${_httplib_build_includedir}/httplib.h" TYPE INCLUDE)
|
||||||
|
|
||||||
@@ -366,6 +407,10 @@ if(HTTPLIB_INSTALL)
|
|||||||
include(CPack)
|
include(CPack)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
if(HTTPLIB_BUILD_MODULES AND NOT HTTPLIB_COMPILE)
|
||||||
|
message(FATAL_ERROR "HTTPLIB_BUILD_MODULES requires HTTPLIB_COMPILE to be ON.")
|
||||||
|
endif()
|
||||||
|
|
||||||
if(HTTPLIB_TEST)
|
if(HTTPLIB_TEST)
|
||||||
include(CTest)
|
include(CTest)
|
||||||
add_subdirectory(test)
|
add_subdirectory(test)
|
||||||
|
|||||||
16
cmake/modules.cmake
Normal file
16
cmake/modules.cmake
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# This file contains C++20 module support requiring CMake 3.28+
|
||||||
|
# Included conditionally to prevent parse errors on older CMake versions
|
||||||
|
|
||||||
|
if(HTTPLIB_BUILD_MODULES)
|
||||||
|
if(POLICY CMP0155)
|
||||||
|
cmake_policy(SET CMP0155 NEW)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
set(CMAKE_CXX_SCAN_FOR_MODULES ON)
|
||||||
|
|
||||||
|
target_sources(${PROJECT_NAME}
|
||||||
|
PUBLIC
|
||||||
|
FILE_SET CXX_MODULES FILES
|
||||||
|
"${_httplib_build_includedir}/httplib.cppm"
|
||||||
|
)
|
||||||
|
endif()
|
||||||
77
generate_module.py
Normal file
77
generate_module.py
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
"""This script generates httplib.cppm module file from httplib.h."""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
from argparse import ArgumentParser, Namespace
|
||||||
|
from typing import List
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
"""Main entry point for the script."""
|
||||||
|
|
||||||
|
args_parser: ArgumentParser = ArgumentParser(description=__doc__)
|
||||||
|
args_parser.add_argument(
|
||||||
|
"-o", "--out", help="where to write the files (default: out)", default="out"
|
||||||
|
)
|
||||||
|
args: Namespace = args_parser.parse_args()
|
||||||
|
|
||||||
|
cur_dir: str = os.path.dirname(sys.argv[0])
|
||||||
|
if not cur_dir:
|
||||||
|
cur_dir = '.'
|
||||||
|
lib_name: str = "httplib"
|
||||||
|
header_name: str = f"/{lib_name}.h"
|
||||||
|
# get the input file
|
||||||
|
in_file: str = f"{cur_dir}{header_name}"
|
||||||
|
# get the output file
|
||||||
|
cppm_out: str = f"{args.out}/{lib_name}.cppm"
|
||||||
|
|
||||||
|
# if the modification time of the out file is after the in file,
|
||||||
|
# don't generate (as it is already finished)
|
||||||
|
do_generate: bool = True
|
||||||
|
|
||||||
|
if os.path.exists(cppm_out):
|
||||||
|
in_time: float = os.path.getmtime(in_file)
|
||||||
|
out_time: float = os.path.getmtime(cppm_out)
|
||||||
|
do_generate: bool = in_time > out_time
|
||||||
|
|
||||||
|
if do_generate:
|
||||||
|
with open(in_file) as f:
|
||||||
|
lines: List[str] = f.readlines()
|
||||||
|
|
||||||
|
os.makedirs(args.out, exist_ok=True)
|
||||||
|
|
||||||
|
# Find the Headers and Declaration comment markers
|
||||||
|
headers_start: int = -1
|
||||||
|
declaration_start: int = -1
|
||||||
|
for i, line in enumerate(lines):
|
||||||
|
if ' * Headers' in line:
|
||||||
|
headers_start = i - 1 # Include the /* line
|
||||||
|
elif ' * Declaration' in line:
|
||||||
|
declaration_start = i - 1 # Stop before the /* line
|
||||||
|
break
|
||||||
|
|
||||||
|
with open(cppm_out, 'w') as fm:
|
||||||
|
# Write module file
|
||||||
|
fm.write("module;\n\n")
|
||||||
|
|
||||||
|
# Write global module fragment (from Headers to Declaration comment)
|
||||||
|
# Filter out 'using' declarations to avoid conflicts
|
||||||
|
if headers_start >= 0 and declaration_start >= 0:
|
||||||
|
for i in range(headers_start, declaration_start):
|
||||||
|
line: str = lines[i]
|
||||||
|
if 'using' not in line:
|
||||||
|
fm.write(line)
|
||||||
|
|
||||||
|
fm.write("\nexport module httplib;\n\n")
|
||||||
|
fm.write("export extern \"C++\" {\n")
|
||||||
|
fm.write(f"{' ' * 4}#include \"httplib.h\"\n")
|
||||||
|
fm.write("}\n")
|
||||||
|
|
||||||
|
print(f"Wrote {cppm_out}")
|
||||||
|
else:
|
||||||
|
print(f"{cppm_out} is up to date")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
26
split.py
26
split.py
@@ -7,15 +7,14 @@ import sys
|
|||||||
from argparse import ArgumentParser, Namespace
|
from argparse import ArgumentParser, Namespace
|
||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
|
BORDER: str = '// ----------------------------------------------------------------------------'
|
||||||
|
|
||||||
def main() -> None:
|
def main() -> None:
|
||||||
"""Main entry point for the script."""
|
"""Main entry point for the script."""
|
||||||
BORDER: str = '// ----------------------------------------------------------------------------'
|
|
||||||
|
|
||||||
args_parser: ArgumentParser = ArgumentParser(description=__doc__)
|
args_parser: ArgumentParser = ArgumentParser(description=__doc__)
|
||||||
args_parser.add_argument(
|
args_parser.add_argument(
|
||||||
"-e", "--extension", help="extension of the implementation file (default: cc)",
|
"-e", "--extension", help="extension of the implementation file (default: cc)", default="cc"
|
||||||
default="cc"
|
|
||||||
)
|
)
|
||||||
args_parser.add_argument(
|
args_parser.add_argument(
|
||||||
"-o", "--out", help="where to write the files (default: out)", default="out"
|
"-o", "--out", help="where to write the files (default: out)", default="out"
|
||||||
@@ -25,14 +24,14 @@ def main() -> None:
|
|||||||
cur_dir: str = os.path.dirname(sys.argv[0])
|
cur_dir: str = os.path.dirname(sys.argv[0])
|
||||||
if not cur_dir:
|
if not cur_dir:
|
||||||
cur_dir = '.'
|
cur_dir = '.'
|
||||||
lib_name: str = 'httplib'
|
lib_name: str = "httplib"
|
||||||
header_name: str = f"/{lib_name}.h"
|
header_name: str = f"/{lib_name}.h"
|
||||||
source_name: str = f"/{lib_name}.{args.extension}"
|
source_name: str = f"/{lib_name}.{args.extension}"
|
||||||
# get the input file
|
# get the input file
|
||||||
in_file: str = cur_dir + header_name
|
in_file: str = f"{cur_dir}{header_name}"
|
||||||
# get the output file
|
# get the output file
|
||||||
h_out: str = args.out + header_name
|
h_out: str = f"{args.out}{header_name}"
|
||||||
cc_out: str = args.out + source_name
|
cc_out: str = f"{args.out}{source_name}"
|
||||||
|
|
||||||
# if the modification time of the out file is after the in file,
|
# if the modification time of the out file is after the in file,
|
||||||
# don't split (as it is already finished)
|
# don't split (as it is already finished)
|
||||||
@@ -51,18 +50,23 @@ def main() -> None:
|
|||||||
|
|
||||||
in_implementation: bool = False
|
in_implementation: bool = False
|
||||||
cc_out: str = args.out + source_name
|
cc_out: str = args.out + source_name
|
||||||
|
|
||||||
with open(h_out, 'w') as fh, open(cc_out, 'w') as fc:
|
with open(h_out, 'w') as fh, open(cc_out, 'w') as fc:
|
||||||
fc.write('#include "httplib.h"\n')
|
# Write source file
|
||||||
fc.write('namespace httplib {\n')
|
fc.write("#include \"httplib.h\"\n")
|
||||||
|
fc.write("namespace httplib {\n")
|
||||||
|
|
||||||
|
# Process lines for header and source split
|
||||||
for line in lines:
|
for line in lines:
|
||||||
is_border_line: bool = BORDER in line
|
is_border_line: bool = BORDER in line
|
||||||
if is_border_line:
|
if is_border_line:
|
||||||
in_implementation: bool = not in_implementation
|
in_implementation: bool = not in_implementation
|
||||||
elif in_implementation:
|
elif in_implementation:
|
||||||
fc.write(line.replace('inline ', ''))
|
fc.write(line.replace("inline ", ""))
|
||||||
else:
|
else:
|
||||||
fh.write(line)
|
fh.write(line)
|
||||||
fc.write('} // namespace httplib\n')
|
|
||||||
|
fc.write("} // namespace httplib\n")
|
||||||
|
|
||||||
print(f"Wrote {h_out} and {cc_out}")
|
print(f"Wrote {h_out} and {cc_out}")
|
||||||
else:
|
else:
|
||||||
|
|||||||
Reference in New Issue
Block a user