diff options
| author | crueter <crueter@eden-emu.dev> | 2026-07-06 23:11:39 +0200 |
|---|---|---|
| committer | crueter <crueter@eden-emu.dev> | 2026-07-06 23:11:39 +0200 |
| commit | 66b073ab8d427d8fc06ffd715151f372cdde818c (patch) | |
| tree | 667e34c22f542d809a069738bd762a23786814c7 /tools/cpm/cmake/ScriptUtils.cmake | |
| parent | 4c65780f11511b20c2e0288a287faace16516d38 (diff) | |
[cmake] CPMUtil rewrite of the day number 852 (#4130)
Composite PR for several changes to CPMUtil, most notably:
- https://git.crueter.xyz/CMake/CPMUtil/pulls/19
- https://git.crueter.xyz/CMake/CPMUtil/pulls/22
- https://git.crueter.xyz/CMake/CPMUtil/pulls/24
- https://git.crueter.xyz/CMake/CPMUtil/pulls/25
These contain a lot of changes that generally simplify control flow and improve ease-of-use. Read those descriptions and patchsets for more info.
Signed-off-by: crueter <crueter@eden-emu.dev>
Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4130
Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
Reviewed-by: MaranBr <maranbr@eden-emu.dev>
Diffstat (limited to 'tools/cpm/cmake/ScriptUtils.cmake')
| -rwxr-xr-x | tools/cpm/cmake/ScriptUtils.cmake | 341 |
1 files changed, 341 insertions, 0 deletions
diff --git a/tools/cpm/cmake/ScriptUtils.cmake b/tools/cpm/cmake/ScriptUtils.cmake new file mode 100755 index 0000000000..dbd908a1ea --- /dev/null +++ b/tools/cpm/cmake/ScriptUtils.cmake @@ -0,0 +1,341 @@ +#!/usr/bin/env -S cmake -P + +# SPDX-FileCopyrightText: Copyright 2026 crueter +# SPDX-License-Identifier: LGPL-3.0-or-later + +cmake_minimum_required(VERSION 3.31) + +# TODO: Account for CPMConfig.cmake +list(APPEND CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}) +list(APPEND CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/CMakeModules) +set(CPMUTIL_ROOT ${CMAKE_SOURCE_DIR}) + +include(CPMUtil) + +# Parse the JSON object for a given key. +macro(parse_key key) + get_json_object(${key}) + set(JSON_NAME ${key}) + parse_object(${object}) +endmacro() + +# Get a package's effective URL, for an already parsed object +function(get_package_url_object out) + if (${url}) + set(${out} "${url}") + else() + get_package_url(URL_OUT "${out}" + GIT_HOST "${git_host}" + REPO "${repo}" + VERSION "${version}" + ARTIFACT "${artifact}" + PACKAGE "${package}") + endif() + + return(PROPAGATE ${out}) +endfunction() + +# Fetch a package from an already-parsed object. +function(fetch_package_object) + set(optionArgs FORCE) + cmake_parse_arguments(ARG "${optionArgs}" "" "" ${ARGN}) + + if (${url}) + set(pkg_url "${url}") + else() + get_package_url(URL_OUT pkg_url + GIT_HOST "${git_host}" + REPO "${repo}" + VERSION "${version}" + ARTIFACT "${artifact}" + PACKAGE "${package}") + endif() + + if (DEFINED CACHE_PATH_OVERRIDE) + set(cache_path ${CACHE_PATH_OVERRIDE}) + else() + get_cache_path(${package} ${version} cache_path) + endif() + + set(fetch_args + URL "${pkg_url}" + HASH "${hash}" + PATH "${cache_path}" + PATCHES ${patches}) + + if(ARG_FORCE) + list(APPEND fetch_args FORCE) + endif() + + fetch_package(${fetch_args}) +endfunction() + +# Format the cpmfile. Requires one of: jq, python, perl +# If you don't have any of those, sorry not sorry. +# Maybe I should make a shell-based alternative. +function(format_cpmfile) + # jq is the preferred formatter since it's the fastest + cpm_find_program(JQ_EXECUTABLE jq) + if (JQ_EXECUTABLE) + set(command ${JQ_EXECUTABLE} --indent 4 -S .) + else() + # Python is simple and works + find_package(Python 3.5 COMPONENTS Interpreter QUIET) + if (Python_FOUND) + set(command ${Python_EXECUTABLE} -m json.tool + --indent 4 --sort-keys) + else() + # json_pp (part of perl) also works well + cpm_find_program(JSONPP_EXECUTABLE json_pp) + if (JSONPP_EXECUTABLE) + set(json_opts "indent" "indent_length=4" "canonical" + "space_after=1" "space_before=0") + string(JOIN "," json_opts_str ${json_opts}) + + set(command ${JSONPP_EXECUTABLE} -f json -t json -json_opt + "${json_opts_str}") + else() + fatal("Fatal: could not find one of jq, Python, or perl" + "(json_pp). Install one of these packages to use" + "CPMUtil's tooling. If they ARE installed, your" + "CMake installation is broken.") + endif() + endif() + endif() + + get_cpmfile_path(file) + mktempdir(TMP) + set(tmp_file ${TMP}/cpmfile.json) + + execute_process(COMMAND ${command} + INPUT_FILE ${file} + OUTPUT_FILE ${tmp_file}) + + # TODO: error handling, mv, cp? + file(COPY_FILE ${tmp_file} ${file}) + + file(REMOVE_RECURSE ${TMP}) +endfunction() + +# Computes expected SHA512 hash of a package +function(get_package_hash url out) + mktempdir(TMP) + + get_filename_component(filename ${url} NAME) + set(file ${TMP}/${filename}) + + cpm_download("${url}" "${file}") + file(SHA512 ${file} ${out}) + + file(REMOVE_RECURSE ${TMP}) + return(PROPAGATE ${out}) +endfunction() + +# Download and put the content into a variable. +function(cpm_download_var url out) + mktempdir(TMP) + + set(file ${TMP}/tmp) + cpm_download("${url}" "${file}") + file(READ ${file} ${out}) + + file(REMOVE_RECURSE ${TMP}) + return(PROPAGATE ${out}) +endfunction() + +# Check if a URL request succeeds without actually saving anything +function(cpm_url_exists url out) + foreach(i RANGE 5) + file(DOWNLOAD "${url}" STATUS ret LOG log TIMEOUT 10) + list(GET ret 0 code) + + if (code EQUAL 0) + set(${out} TRUE) + break() + else() + if (log MATCHES "HTTP/[0-9.]+ (429|403)") + sleep(5) + continue() + endif() + + set(${out} FALSE) + break() + endif() + endforeach() + + return(PROPAGATE ${out}) +endfunction() + +# Get latest tag for a package. +# Requires an already-parsed object +function(get_latest_tag out) + # TODO: Ci packages + if (NOT repo OR ci) + set(${out} null) + return(PROPAGATE ${out}) + endif() + + # first determine if this is a tag or not + if (git_host STREQUAL github.com) + set(api "https://api.github.com/repos/${repo}") + set(check_endpoint "/git/refs/tags") + else() + set(api "https://${git_host}/api/v1/repos/${repo}") + set(check_endpoint "/tags") + endif() + + # artifacts must only check releases + if ("${artifact}" STREQUAL "") + set(json_key name) + set(endpoint "/tags") + else() + set(json_key tag_name) + set(endpoint "/releases") + set(check_endpoint "/releases/tags") + endif() + + cpm_url_exists("${api}${check_endpoint}/${version}" is_tag) + + if (NOT is_tag) + set(${out} null) + return(PROPAGATE ${out}) + endif() + + # strip out prefixes e.g. boost-, openssl-, v + # NOTE: subrels e.g. -1 at the end may cause issues. + string(REGEX REPLACE "[^0-9.-]" "" t_numeric_version "${version}") + string(REGEX REPLACE "-$|^-" "" t_numeric_version "${numeric_version}") + + # now api req + cpm_download_var("${api}${endpoint}" tags) + + string(JSON len LENGTH ${tags}) + math(EXPR last_index "${len} - 1") + + set(greatest_version_numeric ${t_numeric_version}) + set(greatest_version ${version}) + + foreach(i RANGE ${last_index}) + string(JSON tag_obj GET "${tags}" ${i}) + string(JSON tag_name GET "${tag_obj}" ${json_key}) + + # same as above + string(REGEX REPLACE "[^0-9.-]" "" numeric_tag "${tag_name}") + string(REGEX REPLACE "-$|^-" "" numeric_tag "${numeric_tag}") + + if (numeric_tag VERSION_GREATER_EQUAL greatest_version_numeric) + set(greatest_version_numeric ${numeric_tag}) + set(greatest_version ${tag_name}) + endif() + endforeach() + + # numeric version replacement + if (numeric_version) + set(${out} ${greatest_version_numeric}) + else() + set(${out} ${greatest_version}) + endif() + + return(PROPAGATE ${out}) +endfunction() + +# TODO(crueter): Combine these + +# Update hash and version of a package +# Outputs the updated object +function(modify_package object version hash out) + string(JSON new_object SET "${object}" hash "\"${hash}\"") + string(JSON new_object SET "${new_object}" version "\"${version}\"") + + set(${out} "${new_object}") + return(PROPAGATE ${out}) +endfunction() + +# Update hash and numeric_version of a package +# Outputs the updated object +function(modify_package_numeric object version hash out) + string(JSON new_object SET "${object}" hash "\"${hash}\"") + string(JSON new_object SET "${new_object}" numeric_version "\"${version}\"") + + set(${out} "${new_object}") + return(PROPAGATE ${out}) +endfunction() + + +function(get_cpmfile_keys out) + get_cpmfile_content(object) + + string(JSON len LENGTH ${object}) + + math(EXPR last_index "${len} - 1") + foreach(i RANGE ${last_index}) + string(JSON key MEMBER ${object} ${i}) + list(APPEND ${out} ${key}) + endforeach() + + return(PROPAGATE ${out}) +endfunction() + +function(parse_script_args out) + if(PRINT_USAGE) + usage() + cmake_language(EXIT 0) + endif() + + if(NOT CMAKE_SCRIPT_MODE_FILE OR NOT CMAKE_ARGC) + set(${out} "" PARENT_SCOPE) + return() + endif() + + set(found_script FALSE) + set(idx 0) + + get_filename_component(script_name "${CMAKE_SCRIPT_MODE_FILE}" NAME) + + while(idx LESS CMAKE_ARGC) + if(found_script) + set(arg "${CMAKE_ARGV${idx}}") + if(NOT arg STREQUAL "--") + list(APPEND positional_args ${arg}) + endif() + elseif(CMAKE_ARGV${idx} STREQUAL CMAKE_SCRIPT_MODE_FILE) + set(found_script TRUE) + else() + get_filename_component(arg_name "${CMAKE_ARGV${idx}}" NAME) + if(arg_name STREQUAL script_name) + set(found_script TRUE) + endif() + endif() + math(EXPR idx "${idx} + 1") + endwhile() + + if(ALL_PACKAGES AND NOT NO_ALL) + get_cpmfile_keys(all_keys) + if(NO_CI) + set(filtered_keys) + foreach(key ${all_keys}) + parse_key(${key}) + if(NOT ci) + list(APPEND filtered_keys ${key}) + endif() + endforeach() + set(positional_args ${filtered_keys}) + else() + set(positional_args ${all_keys}) + endif() + endif() + + set(${out} ${positional_args} PARENT_SCOPE) +endfunction() + +# Convert a CMake list to a JSON array +function(list_to_array list out) + set(${out} "[]") + set(idx 0) + foreach(elem ${list}) + string(JSON ${out} SET ${${out}} "${idx}" "\"${elem}\"") + math(EXPR idx "${idx} + 1") + endforeach() + + return(PROPAGATE ${out}) +endfunction()
\ No newline at end of file |
