Skip to content
Merged
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
51 changes: 48 additions & 3 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
* HTTPLIB_USE_ZLIB_IF_AVAILABLE (default on)
* HTTPLIB_USE_BROTLI_IF_AVAILABLE (default on)
* HTTPLIB_USE_ZSTD_IF_AVAILABLE (default on)
* HTTPLIB_BUILD_MODULES (default off)
* HTTPLIB_REQUIRE_OPENSSL (default off)
* HTTPLIB_REQUIRE_ZLIB (default off)
* HTTPLIB_REQUIRE_BROTLI (default off)
Expand Down Expand Up @@ -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_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)
# 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
include(CMakeDependentOption)
cmake_dependent_option(HTTPLIB_SHARED "Build the library as a shared library instead of static. Has no effect if using header-only."
Expand Down Expand Up @@ -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}")
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"
set(_httplib_build_includedir "${CMAKE_CURRENT_BINARY_DIR}/out")
add_library(${PROJECT_NAME} ${HTTPLIB_LIB_TYPE} "${_httplib_build_includedir}/httplib.cc")
Expand All @@ -248,6 +274,13 @@ if(HTTPLIB_COMPILE)
$<BUILD_INTERFACE:${_httplib_build_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}
PROPERTIES
VERSION ${${PROJECT_NAME}_VERSION}
Expand All @@ -264,8 +297,12 @@ endif()
# Only useful if building in-tree, versus using it from an installation.
add_library(${PROJECT_NAME}::${PROJECT_NAME} ALIAS ${PROJECT_NAME})

# Require C++11
target_compile_features(${PROJECT_NAME} ${_INTERFACE_OR_PUBLIC} cxx_std_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)
endif()

target_include_directories(${PROJECT_NAME} SYSTEM ${_INTERFACE_OR_PUBLIC}
$<BUILD_INTERFACE:${_httplib_build_includedir}>
Expand Down Expand Up @@ -337,7 +374,11 @@ if(HTTPLIB_INSTALL)
# Creates the export httplibTargets.cmake
# This is strictly what holds compilation requirements
# and linkage information (doesn't find deps though).
install(TARGETS ${PROJECT_NAME} EXPORT httplibTargets)
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)
endif()

install(FILES "${_httplib_build_includedir}/httplib.h" TYPE INCLUDE)

Expand Down Expand Up @@ -366,6 +407,10 @@ if(HTTPLIB_INSTALL)
include(CPack)
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)
include(CTest)
add_subdirectory(test)
Expand Down
16 changes: 16 additions & 0 deletions cmake/modules.cmake
Original file line number Diff line number Diff line change
@@ -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 changes: 77 additions & 0 deletions generate_module.py
Original file line number Diff line number Diff line change
@@ -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 changes: 15 additions & 11 deletions split.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,14 @@
from argparse import ArgumentParser, Namespace
from typing import List

BORDER: str = '// ----------------------------------------------------------------------------'

def main() -> None:
"""Main entry point for the script."""
BORDER: str = '// ----------------------------------------------------------------------------'

args_parser: ArgumentParser = ArgumentParser(description=__doc__)
args_parser.add_argument(
"-e", "--extension", help="extension of the implementation file (default: cc)",
default="cc"
"-e", "--extension", help="extension of the implementation file (default: cc)", default="cc"
)
args_parser.add_argument(
"-o", "--out", help="where to write the files (default: out)", default="out"
Expand All @@ -25,14 +24,14 @@ def main() -> None:
cur_dir: str = os.path.dirname(sys.argv[0])
if not cur_dir:
cur_dir = '.'
lib_name: str = 'httplib'
lib_name: str = "httplib"
header_name: str = f"/{lib_name}.h"
source_name: str = f"/{lib_name}.{args.extension}"
# get the input file
in_file: str = cur_dir + header_name
in_file: str = f"{cur_dir}{header_name}"
# get the output file
h_out: str = args.out + header_name
cc_out: str = args.out + source_name
h_out: str = f"{args.out}{header_name}"
cc_out: str = f"{args.out}{source_name}"

# if the modification time of the out file is after the in file,
# don't split (as it is already finished)
Expand All @@ -51,18 +50,23 @@ def main() -> None:

in_implementation: bool = False
cc_out: str = args.out + source_name

with open(h_out, 'w') as fh, open(cc_out, 'w') as fc:
fc.write('#include "httplib.h"\n')
fc.write('namespace httplib {\n')
# Write source file
fc.write("#include \"httplib.h\"\n")
fc.write("namespace httplib {\n")

# Process lines for header and source split
for line in lines:
is_border_line: bool = BORDER in line
if is_border_line:
in_implementation: bool = not in_implementation
elif in_implementation:
fc.write(line.replace('inline ', ''))
fc.write(line.replace("inline ", ""))
else:
fh.write(line)
fc.write('} // namespace httplib\n')

fc.write("} // namespace httplib\n")

print(f"Wrote {h_out} and {cc_out}")
else:
Expand Down
Loading