mirror of
https://github.com/SphereServer/Source-X
synced 2026-08-13 10:23:05 -04:00
CMake: added generic FetchGithubDep.cmake, to download and cache remote external dependencies.
This commit is contained in:
parent
42a329abd6
commit
792c0d8f0b
4 changed files with 213 additions and 30 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -8,7 +8,9 @@
|
|||
/.ctagsd
|
||||
/.codelite
|
||||
|
||||
/_deps
|
||||
/CMakeFiles
|
||||
/external
|
||||
/spheresvr.dir
|
||||
|
||||
/bin
|
||||
|
|
|
|||
|
|
@ -286,10 +286,6 @@ foreach(tgt ${TARGETS})
|
|||
target_precompile_headers(${tgt} ${pch_list})
|
||||
endforeach()
|
||||
|
||||
if(UNIT_TESTING)
|
||||
include("tests/CMakeLists.txt") # quick and dirty
|
||||
setup_tests()
|
||||
endif()
|
||||
|
||||
# --------------------
|
||||
|
||||
|
|
@ -306,6 +302,11 @@ add_subdirectory(lib)
|
|||
|
||||
toolchain_exe_stuff() # stuff to be executed after ADD_EXECUTABLE
|
||||
|
||||
if(UNIT_TESTING)
|
||||
include("tests/CMakeLists.txt") # quick and dirty
|
||||
setup_tests()
|
||||
endif()
|
||||
|
||||
# Get the Git revision number
|
||||
message(STATUS)
|
||||
message(STATUS "Retrieving git data...")
|
||||
|
|
|
|||
195
cmake/FetchGithubDep.cmake
Normal file
195
cmake/FetchGithubDep.cmake
Normal file
|
|
@ -0,0 +1,195 @@
|
|||
include(FetchContent)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# fetch_github_library():
|
||||
# - Derives the GitHub API URL for “latest release”
|
||||
# - Caches the JSON metadata locally
|
||||
# - Extracts `tag_name` from JSON (cached or freshly downloaded)
|
||||
# - Invokes FetchContent to clone/update into a local cache dir
|
||||
# - On offline/download failure, automatically reuses the existing cache
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
function(fetch_github_library project_name repo_url local_cache_dir)
|
||||
# project_name e.g. "doctest"
|
||||
# repo_url e.g. "https://github.com/doctest/doctest.git"
|
||||
# local_cache_dir e.g. "${CMAKE_SOURCE_DIR}/external"
|
||||
|
||||
# --------------------------------------------------------------
|
||||
# 1) Parse <owner> and <repo> from the provided Git URL
|
||||
# --------------------------------------------------------------
|
||||
string(REGEX MATCH
|
||||
"github\\.com[/:]([^/]+)/([^/]+)\\.git$"
|
||||
_ "${repo_url}"
|
||||
)
|
||||
#[[
|
||||
Parsing the GitHub owner/repo from a `.git` URL
|
||||
- `github\.com`
|
||||
- `\.` → a literal dot. (In regex, `.` normally means “any character,” so we escape it to match an actual `.`.)
|
||||
- `[/:]`
|
||||
- A character class matching exactly one of the characters inside: either a slash `/` or a colon `:`.
|
||||
- This covers both URL styles:
|
||||
- HTTPS: `https://github.com/owner/repo.git`
|
||||
- SSH: `git@github.com:owner/repo.git`
|
||||
- `([^/]+)`
|
||||
- `(` `)` → defines a capturing group, so whatever matches inside will be saved as CMAKE_MATCH_1.
|
||||
- `[^/]` → another character class, but with a `^` at the start: “anything but a slash.”
|
||||
- `+` → “one or more” of the preceding item.
|
||||
- Together, `([^/]+)` means “capture one or more characters that are not `/`.” That gives us the owner.
|
||||
- `/`
|
||||
- Literal, not a special character. Separates owner from repo name.
|
||||
- `([^/]+)`
|
||||
- Same as the previous group, capturing the repo name.
|
||||
- `\.git`
|
||||
- Literal. Matches the suffix `.git`.
|
||||
- `$`
|
||||
- The “end-of-string” anchor. Ensures there’s nothing after `.git`.
|
||||
]]
|
||||
|
||||
if(NOT CMAKE_MATCH_COUNT EQUAL 2)
|
||||
message(FATAL_ERROR
|
||||
"Could not parse owner/repo from '${repo_url}'")
|
||||
endif()
|
||||
set(owner "${CMAKE_MATCH_1}")
|
||||
set(repo "${CMAKE_MATCH_2}")
|
||||
|
||||
# --------------------------------------------------------------
|
||||
# 2) Build the GitHub API URL for “latest release”
|
||||
# --------------------------------------------------------------
|
||||
set(api_url
|
||||
"https://api.github.com/repos/${owner}/${repo}/releases/latest"
|
||||
)
|
||||
|
||||
# --------------------------------------------------------------
|
||||
# 3) Prepare cache paths (JSON + source tree)
|
||||
# --------------------------------------------------------------
|
||||
file(MAKE_DIRECTORY "${local_cache_dir}")
|
||||
if(NOT EXISTS "${local_cache_dir}")
|
||||
message(FATAL_ERROR
|
||||
"Failed to create cache directory: ${local_cache_dir}")
|
||||
endif()
|
||||
|
||||
set(json_file "${local_cache_dir}/${project_name}_latest.json")
|
||||
set(source_cache_dir "${local_cache_dir}/${project_name}")
|
||||
|
||||
# --------------------------------------------------------------
|
||||
# 4) Try to read a cached JSON → tag_name
|
||||
# --------------------------------------------------------------
|
||||
set(lib_tag "")
|
||||
set(offline FALSE)
|
||||
|
||||
if(EXISTS "${json_file}")
|
||||
message(STATUS "Found cached JSON for ${project_name}, validating…")
|
||||
file(READ "${json_file}" _data)
|
||||
string(REGEX MATCH "\"tag_name\"[ \t]*:[ \t]*\"([^\"]+)\"" _ "${_data}")
|
||||
#[[
|
||||
Extracting the `"tag_name"` value from the JSON
|
||||
- `"tag_name"`
|
||||
- A literal double-quoted string, matching exactly `"tag_name"`.
|
||||
- `[ \t]*`
|
||||
- A character class containing a space and `\t` (tab).
|
||||
- `*` means “zero or more” of that class.
|
||||
- So this matches any amount of whitespace (spaces or tabs) after `"tag_name"`.
|
||||
- `:`
|
||||
- A literal colon, since in JSON the key/value separator is `:`.
|
||||
- `[ \t]*`
|
||||
- Again, “any amount of spaces/tabs” after the colon.
|
||||
- `"`
|
||||
- A literal opening quote for the tag value.
|
||||
- `([^"]+)`
|
||||
- Capturing group #1.
|
||||
- `[^"]` is “any character except a quote.”
|
||||
- `+` is “one or more.”
|
||||
- So this grabs all characters inside the quotes (e.g. `v2.4.1`) until it hits the next `"`.
|
||||
- `"`
|
||||
- The closing literal quote.
|
||||
]]
|
||||
if(CMAKE_MATCH_COUNT EQUAL 1)
|
||||
set(lib_tag "${CMAKE_MATCH_1}")
|
||||
message(STATUS "✔ Using cached tag: ${lib_tag}")
|
||||
else()
|
||||
message(WARNING "Cached JSON is invalid. Will attempt fresh download.")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# --------------------------------------------------------------
|
||||
# 5) If no valid cached tag, download the latest JSON
|
||||
# --------------------------------------------------------------
|
||||
if(NOT lib_tag)
|
||||
message(STATUS "Downloading latest release info: ${api_url}")
|
||||
file(DOWNLOAD
|
||||
"${api_url}" # ← remote
|
||||
"${json_file}" # ← local destination
|
||||
STATUS dl_status
|
||||
TIMEOUT 5
|
||||
SHOW_PROGRESS
|
||||
INACTIVITY_TIMEOUT 5
|
||||
)
|
||||
list(GET dl_status 0 code)
|
||||
if(code EQUAL 0 AND EXISTS "${json_file}")
|
||||
file(READ "${json_file}" _data)
|
||||
string(REGEX MATCH "\"tag_name\"[ \t]*:[ \t]*\"([^\"]+)\"" _ "${_data}")
|
||||
if(CMAKE_MATCH_COUNT EQUAL 1)
|
||||
set(lib_tag "${CMAKE_MATCH_1}")
|
||||
message(STATUS "✔ Fetched tag: ${lib_tag}")
|
||||
else()
|
||||
message(WARNING "Failed to parse tag_name from downloaded JSON.")
|
||||
set(offline TRUE)
|
||||
endif()
|
||||
else()
|
||||
message(WARNING "Download failed (code=${code}). Will fall back to cache if any.")
|
||||
set(offline TRUE)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# --------------------------------------------------------------
|
||||
# 6) Final sanity check: ensure we have a lib_tag
|
||||
# --------------------------------------------------------------
|
||||
if(NOT lib_tag)
|
||||
message(FATAL_ERROR
|
||||
"Could not determine a valid tag for ${project_name}. Aborting.")
|
||||
endif()
|
||||
|
||||
# --------------------------------------------------------------
|
||||
# 7) Declare FetchContent variables *before* we call it
|
||||
#
|
||||
# NEW: In offline mode we must prevent both “git pull” and the
|
||||
# very first “git clone”. The trick is to:
|
||||
#
|
||||
# a) Point CMake at the already-downloaded directory via
|
||||
# FETCHCONTENT_SOURCE_DIR_<NAME>
|
||||
# b) Disable updates via FETCHCONTENT_UPDATES_DISCONNECTED_<NAME>
|
||||
#
|
||||
# These must be set *before* FetchContent_MakeAvailable().
|
||||
# --------------------------------------------------------------
|
||||
string(TOUPPER "${project_name}" UC_NAME) # doctest → DOCTEST
|
||||
|
||||
if(offline AND EXISTS "${source_cache_dir}/CMakeLists.txt")
|
||||
# ----------------------------------------------------------
|
||||
# We *have* source on disk and cannot reach the network →
|
||||
# pretend the project is “already populated”.
|
||||
# ----------------------------------------------------------
|
||||
set(FETCHCONTENT_SOURCE_DIR_${UC_NAME} "${source_cache_dir}")
|
||||
set(FETCHCONTENT_UPDATES_DISCONNECTED_${UC_NAME} ON)
|
||||
message(STATUS "Offline mode → reusing existing source at ${source_cache_dir}")
|
||||
else()
|
||||
message(STATUS "Online mode → CMake may clone or update ${project_name}")
|
||||
endif()
|
||||
|
||||
# --------------------------------------------------------------
|
||||
# 8) Formal declaration (still points at the cache dir so both
|
||||
# online and offline modes end up in the same place).
|
||||
# --------------------------------------------------------------
|
||||
FetchContent_Declare(
|
||||
${project_name}
|
||||
GIT_REPOSITORY ${repo_url}
|
||||
GIT_TAG ${lib_tag}
|
||||
SOURCE_DIR "${source_cache_dir}"
|
||||
)
|
||||
|
||||
# --------------------------------------------------------------
|
||||
# 9) Bring the dependency into the build
|
||||
# --------------------------------------------------------------
|
||||
FetchContent_MakeAvailable(${project_name})
|
||||
|
||||
endfunction()
|
||||
|
||||
|
|
@ -1,37 +1,15 @@
|
|||
#set(FETCHCONTENT_BASE_DIR
|
||||
# "${CMAKE_SOURCE_DIR}/third_party"
|
||||
#)
|
||||
include(FetchContent)
|
||||
|
||||
message(STATUS "")
|
||||
message(STATUS "Unit Tests requested")
|
||||
message(STATUS "Fetching DocTest")
|
||||
|
||||
# Download the JSON for the latest release
|
||||
set(DOCTEST_RELEASE_API
|
||||
"https://api.github.com/repos/doctest/doctest/releases/latest"
|
||||
)
|
||||
include("cmake/FetchGithubDep.cmake")
|
||||
set(EXTERNAL_CACHE_DIR "${CMAKE_SOURCE_DIR}/external")
|
||||
|
||||
file(DOWNLOAD
|
||||
${DOCTEST_RELEASE_API}
|
||||
${CMAKE_BINARY_DIR}/doctest_latest.json
|
||||
SHOW_PROGRESS
|
||||
)
|
||||
|
||||
# Extract "tag_name" via regex
|
||||
file(READ "${CMAKE_BINARY_DIR}/doctest_latest.json" _json)
|
||||
string(REGEX MATCH "\"tag_name\"[ \t]*:[ \t]*\"([^\"]+)\"" _
|
||||
"${_json}")
|
||||
set(DOCTEST_TAG "${CMAKE_MATCH_1}")
|
||||
|
||||
message(STATUS "Using doctest tag: ${DOCTEST_TAG}")
|
||||
|
||||
FetchContent_Declare(
|
||||
doctest
|
||||
GIT_REPOSITORY https://github.com/doctest/doctest.git
|
||||
GIT_TAG ${DOCTEST_TAG}
|
||||
)
|
||||
FetchContent_MakeAvailable(doctest)
|
||||
# Fetch Doctest
|
||||
fetch_github_library(doctest "https://github.com/doctest/doctest.git" "${EXTERNAL_CACHE_DIR}")
|
||||
|
||||
# Enable testing
|
||||
enable_testing()
|
||||
|
|
@ -40,6 +18,12 @@ function(setup_tests)
|
|||
foreach(tgt ${TARGETS})
|
||||
target_link_libraries(${tgt} PRIVATE doctest::doctest)
|
||||
target_compile_definitions(${tgt} PRIVATE UNIT_TESTING)
|
||||
# Doctest for some reason uses NaN, which is disabled by -ffast-math
|
||||
if("${CXX_COMPILER_ID}" STREQUAL "MSVC")
|
||||
target_compile_options(${tgt} PRIVATE /fp:precise)
|
||||
else()
|
||||
target_compile_options(${tgt} PRIVATE -fno-finite-math-only)
|
||||
endif()
|
||||
|
||||
#[[
|
||||
add_custom_target(run_tests
|
||||
|
|
@ -54,3 +38,4 @@ function(setup_tests)
|
|||
COMMAND ${tgt})
|
||||
endforeach()
|
||||
endfunction()
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue