aboutsummaryrefslogtreecommitdiff
path: root/tools/cpm/cmake/ScriptUtils.cmake
diff options
context:
space:
mode:
authorcrueter <crueter@eden-emu.dev>2026-07-06 23:11:39 +0200
committercrueter <crueter@eden-emu.dev>2026-07-06 23:11:39 +0200
commit66b073ab8d427d8fc06ffd715151f372cdde818c (patch)
tree667e34c22f542d809a069738bd762a23786814c7 /tools/cpm/cmake/ScriptUtils.cmake
parent4c65780f11511b20c2e0288a287faace16516d38 (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-xtools/cpm/cmake/ScriptUtils.cmake341
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