blob: cc4ad5c773ec188e5090862846170fa87051d246 [file]
# Distributed under the OSI-approved BSD 3-Clause License. See accompanying
# file LICENSE.rst or https://cmake.org/licensing for details.
# Resolve the Maven dependencies listed in CPACK_APK_MAVEN_DEPENDENCIES the
# way Gradle resolves the runtime classpath of an Android application, and
# download them. Included by CPackAPK.cmake.
#
# Gradle module metadata (`.module`) is used when published: the runtime
# variant for Android is selected and `available-at` redirections, which
# Kotlin Multiplatform libraries use, are followed. Otherwise the POM is
# used. The highest version requested wins, in the order of Gradle, only
# the modules at their selected versions requesting versions, and
# dependency constraints, including those of platforms, can raise versions
# or give them to dependencies that have none. Exclusions apply below a
# module when they apply on every path to it.
if(NOT DEFINED CPACK_APK_MAVEN_REPOSITORIES)
set(CPACK_APK_MAVEN_REPOSITORIES
https://maven.google.com
https://repo.maven.apache.org/maven2
)
endif()
set(_maven_repositories)
# Relative paths are relative to the directory cpack runs in, like those of
# the other variables.
file(REAL_PATH "." _cpack_directory)
foreach(_repository IN LISTS CPACK_APK_MAVEN_REPOSITORIES)
# A local directory is a repository as well. It need not exist.
if(NOT _repository MATCHES "^[A-Za-z][A-Za-z0-9+.-]*://")
cmake_path(ABSOLUTE_PATH _repository
BASE_DIRECTORY "${_cpack_directory}" NORMALIZE)
if(NOT _repository MATCHES "^/")
string(PREPEND _repository "/")
endif()
string(PREPEND _repository "file://")
endif()
string(REGEX REPLACE "/+$" "" _repository "${_repository}")
list(APPEND _maven_repositories "${_repository}")
endforeach()
if(NOT CPACK_APK_MAVEN_CACHE_DIRECTORY)
# Next to the top level directory, which is removed after packaging.
set(CPACK_APK_MAVEN_CACHE_DIRECTORY
"${CPACK_PACKAGE_DIRECTORY}/_CPack_Packages/maven")
endif()
# Download `url` to `file` unless it exists. Sets `result` to TRUE on
# success. An optional `<algorithm>=<hash>` argument is verified.
function(_cpack_apk_maven_download url file result)
if(EXISTS "${file}")
set(${result} TRUE PARENT_SCOPE)
return()
endif()
file(DOWNLOAD "${url}" "${file}.part" STATUS _status)
list(GET _status 0 _code)
if(NOT _code EQUAL 0)
file(REMOVE "${file}.part")
set(${result} FALSE PARENT_SCOPE)
return()
endif()
if(ARGV3 MATCHES "^([A-Z0-9]+)=(.+)$")
set(_expected "${CMAKE_MATCH_2}")
file(${CMAKE_MATCH_1} "${file}.part" _actual)
if(NOT _actual STREQUAL _expected)
file(REMOVE "${file}.part")
message(FATAL_ERROR
"CPack APK: The file downloaded from\n"
" ${url}\n"
"does not have the ${CMAKE_MATCH_1} hash the repository publishes.\n"
" Expected: ${_expected}\n"
" Actual: ${_actual}")
endif()
endif()
file(RENAME "${file}.part" "${file}")
# Repositories are tried in turn, so only the successful download is
# reported.
message(STATUS "CPack APK: Downloaded ${url}")
set(${result} TRUE PARENT_SCOPE)
endfunction()
# Download `name` of `group:artifact:version` from the first repository
# that has it, into the cache. Sets `result` to the local path, or to an
# empty string when no repository has it.
function(_cpack_apk_maven_fetch group artifact version name result)
string(REPLACE "." "/" _group_path "${group}")
set(_relative "${_group_path}/${artifact}/${version}/${name}")
set(_file "${CPACK_APK_MAVEN_CACHE_DIRECTORY}/${_relative}")
foreach(_repository IN LISTS _maven_repositories)
_cpack_apk_maven_download("${_repository}/${_relative}" "${_file}" _ok
${ARGN})
if(_ok)
set(${result} "${_file}" PARENT_SCOPE)
return()
endif()
endforeach()
set(${result} "" PARENT_SCOPE)
endfunction()
# Split `group:artifact:version` into the variables named by the other
# arguments.
function(_cpack_apk_maven_split coordinates group_var artifact_var version_var)
string(REPLACE ":" ";" _parts "${coordinates}")
list(LENGTH _parts _length)
if(NOT _length EQUAL 3)
message(FATAL_ERROR
"CPack APK: '${coordinates}' is not of the form "
"<group>:<artifact>:<version>.")
endif()
list(GET _parts 0 _group)
list(GET _parts 1 _artifact)
list(GET _parts 2 _version)
set(${group_var} "${_group}" PARENT_SCOPE)
set(${artifact_var} "${_artifact}" PARENT_SCOPE)
set(${version_var} "${_version}" PARENT_SCOPE)
endfunction()
# Strip the brackets of a hard version requirement, `[1.2.3]` -> `1.2.3`.
function(_cpack_apk_maven_plain_version version context result)
string(REGEX REPLACE "^\\[([^,]*)\\]$" "\\1" _version "${version}")
if(_version MATCHES "[][(),]")
message(FATAL_ERROR
"CPack APK: ${context} requires the version range '${version}'. "
"Version ranges are not supported.")
endif()
set(${result} "${_version}" PARENT_SCOPE)
endfunction()
# Split `version` into the parts Gradle compares: separated by `.`, `-`,
# `_` and `+`, and where digits and other characters meet.
function(_cpack_apk_maven_version_parts version result)
string(REGEX REPLACE "[-_+]" "." _version "${version}")
string(REGEX REPLACE "([0-9])([^0-9.])" "\\1.\\2" _version "${_version}")
string(REGEX REPLACE "([^0-9.])([0-9])" "\\1.\\2" _version "${_version}")
string(REGEX REPLACE "\\.\\.+" "." _version "${_version}")
string(REGEX REPLACE "^\\.|\\.$" "" _version "${_version}")
string(REPLACE "." ";" _version "${_version}")
set(${result} "${_version}" PARENT_SCOPE)
endfunction()
# Rank of the version part `part`: `dev` is below the other qualifiers,
# `rc`, `snapshot`, `final`, `ga`, `release` and `sp` are above them, in
# this order, and numbers are above all qualifiers.
function(_cpack_apk_maven_version_rank part result)
if(part MATCHES "^[0-9]+$")
set(${result} 8 PARENT_SCOPE)
return()
endif()
string(TOLOWER "${part}" _part)
list(FIND _cpack_apk_maven_qualifiers "${_part}" _rank)
if(_rank EQUAL -1)
set(_rank 1)
endif()
set(${result} ${_rank} PARENT_SCOPE)
endfunction()
set(_cpack_apk_maven_qualifiers dev _other_ rc snapshot final ga release sp)
# Set `result` to 1, 0 or -1 as the version `a` is higher than, equal to or
# lower than `b`, the way Gradle orders versions. `1.0-alpha` is lower
# than `1.0`, which is lower than `1.0.1`.
function(_cpack_apk_maven_version_compare a b result)
_cpack_apk_maven_version_parts("${a}" _a)
_cpack_apk_maven_version_parts("${b}" _b)
list(LENGTH _a _a_length)
list(LENGTH _b _b_length)
set(_length ${_a_length})
if(_b_length LESS _length)
set(_length ${_b_length})
endif()
set(_i 0)
while(_i LESS _length)
list(GET _a ${_i} _a_part)
list(GET _b ${_i} _b_part)
_cpack_apk_maven_version_rank("${_a_part}" _a_rank)
_cpack_apk_maven_version_rank("${_b_part}" _b_rank)
if(_a_rank GREATER _b_rank)
set(${result} 1 PARENT_SCOPE)
return()
elseif(_a_rank LESS _b_rank)
set(${result} -1 PARENT_SCOPE)
return()
elseif(_a_rank EQUAL 8)
if(_a_part GREATER _b_part)
set(${result} 1 PARENT_SCOPE)
return()
elseif(_a_part LESS _b_part)
set(${result} -1 PARENT_SCOPE)
return()
endif()
elseif(_a_rank EQUAL 1)
if(_a_part STRGREATER _b_part)
set(${result} 1 PARENT_SCOPE)
return()
elseif(_a_part STRLESS _b_part)
set(${result} -1 PARENT_SCOPE)
return()
endif()
endif()
math(EXPR _i "${_i} + 1")
endwhile()
# The longer version is higher if its next part is a number, and lower
# otherwise.
if(_a_length EQUAL _b_length)
set(${result} 0 PARENT_SCOPE)
return()
elseif(_a_length GREATER _b_length)
list(GET _a ${_length} _part)
set(_sign 1)
else()
list(GET _b ${_length} _part)
set(_sign -1)
endif()
if(NOT _part MATCHES "^[0-9]+$")
math(EXPR _sign "-${_sign}")
endif()
set(${result} ${_sign} PARENT_SCOPE)
endfunction()
# Remove from `xml` the text from each `open` to the following `close`.
function(_cpack_apk_maven_xml_remove xml open close result)
string(LENGTH "${close}" _close_length)
while(TRUE)
string(FIND "${xml}" "${open}" _begin)
if(_begin EQUAL -1)
break()
endif()
string(SUBSTRING "${xml}" ${_begin} -1 _tail)
string(FIND "${_tail}" "${close}" _end)
if(_end EQUAL -1)
break()
endif()
math(EXPR _end "${_end} + ${_close_length}")
string(SUBSTRING "${xml}" 0 ${_begin} _head)
string(SUBSTRING "${_tail}" ${_end} -1 _tail)
set(xml "${_head}${_tail}")
endwhile()
set(${result} "${xml}" PARENT_SCOPE)
endfunction()
# Set `result` to the content of the first element `tag` of `xml`, or to
# an empty string.
function(_cpack_apk_maven_xml_element xml tag result)
set(_content "")
string(FIND "${xml}" "<${tag}>" _begin)
if(_begin GREATER -1)
string(LENGTH "<${tag}>" _length)
math(EXPR _begin "${_begin} + ${_length}")
string(SUBSTRING "${xml}" ${_begin} -1 _content)
string(FIND "${_content}" "</${tag}>" _end)
if(_end GREATER -1)
string(SUBSTRING "${_content}" 0 ${_end} _content)
endif()
endif()
set(${result} "${_content}" PARENT_SCOPE)
endfunction()
# Set `result` to TRUE if `group:artifact` matches one of the `excludes`,
# `group:artifact` patterns in which either part may be `*`.
function(_cpack_apk_maven_excluded group artifact excludes result)
foreach(_exclude IN LISTS excludes)
if(NOT _exclude MATCHES "^([^:]*):(.*)$")
continue()
endif()
set(_group "${CMAKE_MATCH_1}")
set(_artifact "${CMAKE_MATCH_2}")
if((_group STREQUAL "*" OR _group STREQUAL group)
AND (_artifact STREQUAL "*" OR _artifact STREQUAL artifact))
set(${result} TRUE PARENT_SCOPE)
return()
endif()
endforeach()
set(${result} FALSE PARENT_SCOPE)
endfunction()
# Index of the variant of the module metadata `json` that Gradle picks for
# the runtime classpath of an Android application, or an empty string.
# `kind` is `library`, or `platform` for a platform, which only has
# dependency constraints.
function(_cpack_apk_maven_variant json kind result)
set(_best "")
set(_best_score 0)
string(JSON _count LENGTH "${json}" variants)
if(_count EQUAL 0)
set(${result} "" PARENT_SCOPE)
return()
endif()
math(EXPR _last "${_count} - 1")
foreach(_i RANGE ${_last})
foreach(_attribute IN ITEMS usage category platform environment build_type)
set(_${_attribute} "")
endforeach()
foreach(_pair IN ITEMS
usage=org.gradle.usage
category=org.gradle.category
platform=org.jetbrains.kotlin.platform.type
environment=org.gradle.jvm.environment
build_type=com.android.build.api.attributes.BuildTypeAttr)
string(REPLACE "=" ";" _pair "${_pair}")
list(GET _pair 0 _attribute)
list(GET _pair 1 _name)
string(JSON _value ERROR_VARIABLE _error
GET "${json}" variants ${_i} attributes ${_name})
if(NOT _error)
set(_${_attribute} "${_value}")
endif()
endforeach()
if(NOT _usage STREQUAL "java-runtime")
continue()
endif()
if(kind STREQUAL "platform")
if(NOT _category MATCHES "^(enforced-)?platform$")
continue()
endif()
elseif(_category AND NOT _category STREQUAL "library")
continue()
endif()
set(_score 2)
if(_platform STREQUAL "androidJvm")
math(EXPR _score "${_score} + 8")
elseif(_platform STREQUAL "jvm")
math(EXPR _score "${_score} + 4")
elseif(_platform)
# Kotlin/Native, JavaScript, WebAssembly or common metadata.
continue()
endif()
if(_environment STREQUAL "android")
math(EXPR _score "${_score} + 2")
endif()
# Libraries publishing all their Android build types have a debug and a
# release variant; the application is packaged for release.
if(_build_type STREQUAL "" OR _build_type STREQUAL "release")
math(EXPR _score "${_score} + 1")
endif()
if(_score GREATER _best_score)
set(_best ${_i})
set(_best_score ${_score})
endif()
endforeach()
set(${result} "${_best}" PARENT_SCOPE)
endfunction()
# Read the Gradle module metadata `json` of `coordinates`, as a `kind`.
function(_cpack_apk_maven_module json kind coordinates)
_cpack_apk_maven_variant("${json}" ${kind} _variant)
if(_variant STREQUAL "")
if(kind STREQUAL "platform")
set(_what "platform variant")
else()
set(_what "variant")
endif()
message(FATAL_ERROR
"CPack APK: The Maven module '${coordinates}' has no ${_what} for the "
"Java runtime.")
endif()
set(_deps)
set(_constraints)
set(_redirect)
set(_files)
string(JSON _available ERROR_VARIABLE _error
GET "${json}" variants ${_variant} available-at)
if(NOT _error)
string(JSON _g GET "${_available}" group)
string(JSON _m GET "${_available}" module)
string(JSON _v GET "${_available}" version)
set(_redirect "${_g}:${_m}:${_v}")
else()
foreach(_kind IN ITEMS dependencies dependencyConstraints)
string(JSON _count ERROR_VARIABLE _error
LENGTH "${json}" variants ${_variant} ${_kind})
if(_error OR _count EQUAL 0)
continue()
endif()
math(EXPR _last "${_count} - 1")
foreach(_i RANGE ${_last})
string(JSON _dep GET "${json}" variants ${_variant} ${_kind} ${_i})
string(JSON _g GET "${_dep}" group)
string(JSON _m GET "${_dep}" module)
set(_v)
foreach(_key IN ITEMS strictly requires prefers)
string(JSON _v ERROR_VARIABLE _error GET "${_dep}" version ${_key})
if(NOT _error)
break()
endif()
set(_v)
endforeach()
if(_v)
_cpack_apk_maven_plain_version("${_v}"
"The Maven module '${coordinates}'" _v)
endif()
if(_kind STREQUAL "dependencyConstraints")
# A constraint without a version, which only rejects versions,
# does not raise one.
if(_v)
list(APPEND _constraints "${_g}:${_m}:${_v}")
endif()
continue()
endif()
# A platform has no artifacts, but its constraints give versions,
# including to dependencies that have none.
set(_dep_kind library)
string(JSON _category ERROR_VARIABLE _error
GET "${_dep}" attributes org.gradle.category)
if(NOT _error AND _category MATCHES "platform")
set(_dep_kind platform)
endif()
set(_excludes)
string(JSON _excludes_count ERROR_VARIABLE _error
LENGTH "${_dep}" excludes)
if(NOT _error AND _excludes_count GREATER 0)
math(EXPR _excludes_last "${_excludes_count} - 1")
foreach(_j RANGE ${_excludes_last})
foreach(_field IN ITEMS group module)
string(JSON _${_field} ERROR_VARIABLE _error
GET "${_dep}" excludes ${_j} ${_field})
if(_error)
set(_${_field} "*")
endif()
endforeach()
list(APPEND _excludes "${_group}:${_module}")
endforeach()
endif()
list(JOIN _excludes "," _excludes)
list(APPEND _deps "${_dep_kind}|${_g}:${_m}:${_v}|${_excludes}")
endforeach()
endforeach()
string(JSON _count ERROR_VARIABLE _error
LENGTH "${json}" variants ${_variant} files)
if(NOT _error AND _count GREATER 0)
math(EXPR _last "${_count} - 1")
foreach(_i RANGE ${_last})
string(JSON _url GET "${json}" variants ${_variant} files ${_i} url)
string(JSON _sha256 GET "${json}" variants ${_variant} files ${_i} sha256)
string(TOLOWER "${_sha256}" _sha256)
list(APPEND _files "${_url}|SHA256=${_sha256}")
endforeach()
endif()
endif()
set(_maven_deps "${_deps}" PARENT_SCOPE)
set(_maven_constraints "${_constraints}" PARENT_SCOPE)
set(_maven_redirect "${_redirect}" PARENT_SCOPE)
set(_maven_files "${_files}" PARENT_SCOPE)
endfunction()
# The `<dependency>` elements of `xml`, as
# `group|artifact|version|scope|optional|excludes`, where `excludes` are
# the `group:artifact` of its exclusions, joined by `,`. The `[` and `]`
# of version ranges would keep lists from being split, so they are given
# as `<lb>` and `<rb>`, which the elements cannot hold.
function(_cpack_apk_maven_pom_dependencies xml result)
set(_space "[ \t\r\n]*")
set(_deps)
string(LENGTH "<dependency>" _open_length)
string(LENGTH "<exclusion>" _exclusion_open_length)
while(TRUE)
string(FIND "${xml}" "<dependency>" _begin)
if(_begin EQUAL -1)
break()
endif()
math(EXPR _begin "${_begin} + ${_open_length}")
string(SUBSTRING "${xml}" ${_begin} -1 xml)
string(FIND "${xml}" "</dependency>" _end)
string(SUBSTRING "${xml}" 0 ${_end} _chunk)
# The transitive dependencies to leave out.
_cpack_apk_maven_xml_element("${_chunk}" exclusions _exclusions)
_cpack_apk_maven_xml_remove("${_chunk}" "<exclusions>" "</exclusions>"
_chunk)
set(_excludes)
while(TRUE)
string(FIND "${_exclusions}" "<exclusion>" _begin)
if(_begin EQUAL -1)
break()
endif()
math(EXPR _begin "${_begin} + ${_exclusion_open_length}")
string(SUBSTRING "${_exclusions}" ${_begin} -1 _exclusions)
string(FIND "${_exclusions}" "</exclusion>" _end)
string(SUBSTRING "${_exclusions}" 0 ${_end} _exclusion)
foreach(_tag IN ITEMS groupId artifactId)
set(_${_tag} "*")
if(_exclusion MATCHES "<${_tag}>${_space}([^< \t\r\n]+)${_space}</${_tag}>")
set(_${_tag} "${CMAKE_MATCH_1}")
endif()
endforeach()
string(APPEND _excludes ",${_groupId}:${_artifactId}")
endwhile()
string(REGEX REPLACE "^," "" _excludes "${_excludes}")
foreach(_tag IN ITEMS groupId artifactId version scope optional)
set(_${_tag})
if(_chunk MATCHES "<${_tag}>${_space}([^< \t\r\n]*)${_space}</${_tag}>")
set(_${_tag} "${CMAKE_MATCH_1}")
endif()
endforeach()
set(_entry
"${_groupId}|${_artifactId}|${_version}|${_scope}|${_optional}|${_excludes}")
string(REPLACE "[" "<lb>" _entry "${_entry}")
string(REPLACE "]" "<rb>" _entry "${_entry}")
list(APPEND _deps "${_entry}")
endwhile()
set(${result} "${_deps}" PARENT_SCOPE)
endfunction()
# Set `result` to the version `value` given by
# `_cpack_apk_maven_pom_dependencies`, with its `[` and `]` restored.
function(_cpack_apk_maven_pom_dependency_version value result)
string(REPLACE "<lb>" "[" value "${value}")
string(REPLACE "<rb>" "]" value "${value}")
set(${result} "${value}" PARENT_SCOPE)
endfunction()
# Set `result` to `value`, the `what` of `dependency` in the POM of
# `group:artifact:version`, whose `<properties>` element holds `properties`,
# with the properties it refers to replaced.
function(_cpack_apk_maven_pom_property value properties group artifact version
dependency what result)
set(_space "[ \t\r\n]*")
# Properties of this POM; those of parent POMs are not known.
while(value MATCHES "\\$\\{([^}]*)\\}")
set(_property "${CMAKE_MATCH_1}")
if(_property MATCHES "^((project|pom)\\.)?groupId$")
set(_value "${group}")
elseif(_property MATCHES "^((project|pom)\\.)?artifactId$")
set(_value "${artifact}")
elseif(_property MATCHES "^((project|pom)\\.)?version$")
set(_value "${version}")
elseif(properties MATCHES "<${_property}>${_space}([^<]*[^< \t\r\n])${_space}</${_property}>")
set(_value "${CMAKE_MATCH_1}")
else()
message(FATAL_ERROR
"CPack APK: The ${what} of '${dependency}' in the POM of "
"'${group}:${artifact}:${version}' refers to the unknown property "
"'${_property}'. Parent POMs are not supported.")
endif()
string(REPLACE "\${${_property}}" "${_value}" value "${value}")
endwhile()
set(${result} "${value}" PARENT_SCOPE)
endfunction()
# Set `result` to the version `value` of `dependency` in the POM of
# `group:artifact:version`, whose `<properties>` element holds
# `properties`.
function(_cpack_apk_maven_pom_version value properties group artifact version
dependency result)
_cpack_apk_maven_pom_property("${value}" "${properties}"
${group} ${artifact} ${version} "${dependency}" version value)
if(NOT value STREQUAL "")
_cpack_apk_maven_plain_version("${value}"
"The POM of '${group}:${artifact}:${version}'" value)
endif()
set(${result} "${value}" PARENT_SCOPE)
endfunction()
# Replace the properties that the group and artifact held by the variables
# named `group_var` and `artifact_var` refer to, in the POM of
# `group:artifact:version`, whose `<properties>` element holds `properties`.
# Modules often name the group of their siblings `${project.groupId}`.
function(_cpack_apk_maven_pom_coordinates group_var artifact_var properties
group artifact version)
set(_dependency "${${group_var}}:${${artifact_var}}")
_cpack_apk_maven_pom_property("${${group_var}}" "${properties}"
${group} ${artifact} ${version} "${_dependency}" group _group)
_cpack_apk_maven_pom_property("${${artifact_var}}" "${properties}"
${group} ${artifact} ${version} "${_dependency}" artifact _artifact)
set(${group_var} "${_group}" PARENT_SCOPE)
set(${artifact_var} "${_artifact}" PARENT_SCOPE)
endfunction()
# Read the POM `pom` of `group:artifact:version`, as a `kind`. A platform
# has the versions the POM manages as dependency constraints, and the
# platforms it imports as dependencies.
function(_cpack_apk_maven_pom pom kind group artifact version)
file(READ "${pom}" _content)
string(REPLACE ";" "," _content "${_content}")
# Only the elements of the project itself count, not those in comments,
# profiles or the build, such as the dependencies of plugins.
_cpack_apk_maven_xml_remove("${_content}" "<!--" "-->" _content)
foreach(_tag IN ITEMS profiles build reporting)
_cpack_apk_maven_xml_remove("${_content}" "<${_tag}>" "</${_tag}>"
_content)
endforeach()
_cpack_apk_maven_xml_element("${_content}" properties _properties)
_cpack_apk_maven_xml_element("${_content}" dependencyManagement _managed)
_cpack_apk_maven_xml_remove("${_content}"
"<dependencyManagement>" "</dependencyManagement>" _content)
set(_deps)
set(_constraints)
_cpack_apk_maven_pom_dependencies("${_managed}" _managed)
foreach(_entry IN LISTS _managed)
string(REPLACE "|" ";" _fields "${_entry}")
list(GET _fields 0 _g)
list(GET _fields 1 _a)
list(GET _fields 2 _v)
list(GET _fields 3 _scope)
_cpack_apk_maven_pom_dependency_version("${_v}" _v)
_cpack_apk_maven_pom_coordinates(_g _a "${_properties}"
${group} ${artifact} ${version})
if(kind STREQUAL "platform")
_cpack_apk_maven_pom_version("${_v}" "${_properties}"
${group} ${artifact} ${version} "${_g}:${_a}" _v)
if(_v STREQUAL "")
continue()
endif()
if(_scope STREQUAL "import")
list(APPEND _deps "platform|${_g}:${_a}:${_v}|")
else()
list(APPEND _constraints "${_g}:${_a}:${_v}")
endif()
elseif(NOT _scope STREQUAL "import")
# The versions of the dependencies that do not give one.
string(MAKE_C_IDENTIFIER "${_g}:${_a}" _id)
set(_managed_${_id} "${_v}")
endif()
endforeach()
set(_files)
if(kind STREQUAL "library")
_cpack_apk_maven_pom_dependencies("${_content}" _dependencies)
foreach(_entry IN LISTS _dependencies)
string(REPLACE "|" ";" _fields "${_entry}")
list(GET _fields 0 _g)
list(GET _fields 1 _a)
list(GET _fields 2 _v)
list(GET _fields 3 _scope)
list(GET _fields 4 _optional)
list(GET _fields 5 _excludes)
_cpack_apk_maven_pom_dependency_version("${_v}" _v)
_cpack_apk_maven_pom_coordinates(_g _a "${_properties}"
${group} ${artifact} ${version})
string(REPLACE "," ";" _excludes "${_excludes}")
set(_resolved_excludes)
foreach(_exclude IN LISTS _excludes)
string(REPLACE ":" ";" _exclude "${_exclude}")
list(GET _exclude 0 _eg)
list(GET _exclude 1 _ea)
_cpack_apk_maven_pom_coordinates(_eg _ea "${_properties}"
${group} ${artifact} ${version})
list(APPEND _resolved_excludes "${_eg}:${_ea}")
endforeach()
list(JOIN _resolved_excludes "," _excludes)
if(_scope AND NOT _scope MATCHES "^(compile|runtime)$")
continue()
endif()
if(_optional STREQUAL "true")
continue()
endif()
if(_v STREQUAL "")
string(MAKE_C_IDENTIFIER "${_g}:${_a}" _id)
set(_v "${_managed_${_id}}")
endif()
_cpack_apk_maven_pom_version("${_v}" "${_properties}"
${group} ${artifact} ${version} "${_g}:${_a}" _v)
if(_v STREQUAL "")
message(FATAL_ERROR
"CPack APK: The POM of '${group}:${artifact}:${version}' does not "
"give the version of '${_g}:${_a}'. Parent POMs and imported "
"BOMs are not supported.")
endif()
list(APPEND _deps "library|${_g}:${_a}:${_v}|${_excludes}")
endforeach()
set(_space "[ \t\r\n]*")
set(_packaging jar)
if(_content MATCHES "<packaging>${_space}([^< \t\r\n]*)")
set(_packaging "${CMAKE_MATCH_1}")
endif()
if(_packaging STREQUAL "bundle")
set(_packaging jar)
endif()
if(NOT _packaging STREQUAL "pom")
set(_name "${artifact}-${version}.${_packaging}")
_cpack_apk_maven_fetch(${group} ${artifact} ${version} "${_name}.sha1"
_sha1_file)
if(_sha1_file)
file(STRINGS "${_sha1_file}" _sha1 LIMIT_COUNT 1 REGEX "^[0-9a-fA-F]+")
string(REGEX MATCH "^[0-9a-fA-F]+" _sha1 "${_sha1}")
string(TOLOWER "${_sha1}" _sha1)
list(APPEND _files "${_name}|SHA1=${_sha1}")
else()
list(APPEND _files "${_name}|")
endif()
endif()
endif()
set(_maven_deps "${_deps}" PARENT_SCOPE)
set(_maven_constraints "${_constraints}" PARENT_SCOPE)
set(_maven_redirect "" PARENT_SCOPE)
set(_maven_files "${_files}" PARENT_SCOPE)
endfunction()
# Read the metadata of `group:artifact:version` as a `kind`, `library` or
# `platform`, and set in the caller:
# _maven_deps its dependencies, `kind|group:artifact:version|excludes`,
# where the version may be empty and `excludes` are
# `group:artifact` patterns joined by `,`
# _maven_constraints its dependency constraints, `group:artifact:version`
# _maven_redirect the `group:artifact:version` it moved to, if any
# _maven_files its files, `name|algorithm=hash`
function(_cpack_apk_maven_metadata kind group artifact version)
set(_coordinates "${group}:${artifact}:${version}")
_cpack_apk_maven_fetch(${group} ${artifact} ${version}
"${artifact}-${version}.module" _module)
if(_module)
file(READ "${_module}" _json)
_cpack_apk_maven_module("${_json}" ${kind} "${_coordinates}")
else()
_cpack_apk_maven_fetch(${group} ${artifact} ${version}
"${artifact}-${version}.pom" _pom)
if(NOT _pom)
list(JOIN _maven_repositories ", " _repositories)
message(FATAL_ERROR
"CPack APK: The Maven artifact '${_coordinates}' was not found in "
"any of: ${_repositories}")
endif()
_cpack_apk_maven_pom("${_pom}" ${kind} ${group} ${artifact} ${version})
endif()
foreach(_field IN ITEMS deps constraints redirect files)
set(_maven_${_field} "${_maven_${_field}}" PARENT_SCOPE)
endforeach()
endfunction()
# Set `result` to TRUE if the version `a` is higher than `b`, or `b` is
# empty.
function(_cpack_apk_maven_version_higher a b result)
set(_higher TRUE)
if(NOT b STREQUAL "")
_cpack_apk_maven_version_compare("${a}" "${b}" _order)
if(NOT _order EQUAL 1)
set(_higher FALSE)
endif()
endif()
set(${result} ${_higher} PARENT_SCOPE)
endfunction()
# Resolve CPACK_APK_MAVEN_DEPENDENCIES and set `result` to the downloaded
# archives.
function(_cpack_apk_maven_resolve result)
list(JOIN CPACK_APK_MAVEN_DEPENDENCIES ", " _dependencies)
message(STATUS "CPack APK: Resolving Maven dependencies: ${_dependencies}")
# The graph is walked again whenever the version of a module, or the
# exclusions that apply below it, change after it was visited, until
# they are stable. Exclusions only shrink. Each walk starts from the
# versions the modules were requested at, and constrained to, in the
# previous one, so that, as with Gradle, the versions a module no longer
# selected requests do not count. Should those versions cycle, they are
# only raised from then on.
set(_ids)
set(_states)
set(_monotonic FALSE)
set(_walk TRUE)
while(_walk)
set(_walk FALSE)
set(_visited)
set(_visited_modules)
set(_selected_files)
set(_pending)
foreach(_id IN LISTS _ids)
set(_selected_${_id} "${_floor_${_id}}")
set(_constraint_${_id} "${_floor_constraint_${_id}}")
set(_requested_${_id})
set(_constrained_${_id})
endforeach()
# Entries are `kind|group:artifact:version|excludes`.
set(_queue)
foreach(_dependency IN LISTS CPACK_APK_MAVEN_DEPENDENCIES)
list(APPEND _queue "library|${_dependency}|")
endforeach()
while(_queue)
while(_queue)
list(POP_FRONT _queue _entry)
string(REPLACE "|" ";" _fields "${_entry}")
list(GET _fields 0 _kind)
list(GET _fields 1 _coordinates)
list(GET _fields 2 _incoming)
string(REPLACE "," ";" _incoming "${_incoming}")
_cpack_apk_maven_split("${_coordinates}" _g _a _v)
string(MAKE_C_IDENTIFIER "${_g}:${_a}" _id)
list(APPEND _ids "${_id}")
if(NOT _v STREQUAL "")
_cpack_apk_maven_version_higher("${_v}" "${_requested_${_id}}"
_higher)
if(_higher)
set(_requested_${_id} "${_v}")
endif()
endif()
foreach(_candidate IN ITEMS "${_v}" "${_constraint_${_id}}")
if(_candidate STREQUAL "")
continue()
endif()
_cpack_apk_maven_version_higher("${_candidate}"
"${_selected_${_id}}" _higher)
if(_higher)
set(_selected_${_id} "${_candidate}")
if("${_g}:${_a}" IN_LIST _visited_modules)
set(_walk TRUE)
endif()
endif()
endforeach()
# Exclusions apply below a module if they apply on every path to it.
string(MAKE_C_IDENTIFIER "${_kind}:${_g}:${_a}" _node)
if(NOT DEFINED _excludes_${_node})
set(_excludes_${_node} "${_incoming}")
else()
set(_common)
foreach(_exclude IN LISTS _excludes_${_node})
if(_exclude IN_LIST _incoming)
list(APPEND _common "${_exclude}")
endif()
endforeach()
if(NOT "${_common}" STREQUAL "${_excludes_${_node}}")
set(_excludes_${_node} "${_common}")
if("${_kind}|${_g}:${_a}" IN_LIST _visited)
set(_walk TRUE)
endif()
endif()
endif()
if("${_kind}|${_g}:${_a}" IN_LIST _visited)
continue()
endif()
if("${_selected_${_id}}" STREQUAL "")
# Required without a version, which a platform or a dependency
# constraint may give later.
list(APPEND _pending "${_entry}")
continue()
endif()
list(APPEND _visited "${_kind}|${_g}:${_a}")
list(APPEND _visited_modules "${_g}:${_a}")
set(_v "${_selected_${_id}}")
string(MAKE_C_IDENTIFIER "${_kind}:${_g}:${_a}:${_v}" _key)
if(NOT DEFINED _metadata_${_key}_files)
_cpack_apk_maven_metadata(${_kind} ${_g} ${_a} ${_v})
foreach(_field IN ITEMS deps constraints redirect files)
set(_metadata_${_key}_${_field} "${_maven_${_field}}")
endforeach()
endif()
foreach(_constraint IN LISTS _metadata_${_key}_constraints)
_cpack_apk_maven_split("${_constraint}" _cg _ca _cv)
string(MAKE_C_IDENTIFIER "${_cg}:${_ca}" _cid)
list(APPEND _ids "${_cid}")
_cpack_apk_maven_version_higher("${_cv}" "${_constrained_${_cid}}"
_higher)
if(_higher)
set(_constrained_${_cid} "${_cv}")
endif()
_cpack_apk_maven_version_higher("${_cv}" "${_constraint_${_cid}}"
_higher)
if(_higher)
set(_constraint_${_cid} "${_cv}")
if("${_cg}:${_ca}" IN_LIST _visited_modules)
_cpack_apk_maven_version_higher("${_cv}" "${_selected_${_cid}}"
_higher)
if(_higher)
set(_walk TRUE)
endif()
endif()
endif()
endforeach()
set(_excludes "${_excludes_${_node}}")
foreach(_dep IN LISTS _metadata_${_key}_deps)
string(REPLACE "|" ";" _fields "${_dep}")
list(GET _fields 0 _dep_kind)
list(GET _fields 1 _dep_coordinates)
list(GET _fields 2 _dep_excludes)
_cpack_apk_maven_split("${_dep_coordinates}" _dg _da _dv)
_cpack_apk_maven_excluded(${_dg} ${_da} "${_excludes}" _excluded)
if(_excluded)
continue()
endif()
string(REPLACE "," ";" _dep_excludes "${_dep_excludes}")
set(_below ${_excludes} ${_dep_excludes})
list(REMOVE_DUPLICATES _below)
list(JOIN _below "," _below)
list(APPEND _queue "${_dep_kind}|${_dep_coordinates}|${_below}")
endforeach()
if(_metadata_${_key}_redirect)
list(JOIN _excludes "," _below)
list(APPEND _queue
"${_kind}|${_metadata_${_key}_redirect}|${_below}")
endif()
foreach(_file IN LISTS _metadata_${_key}_files)
list(APPEND _selected_files "${_g}:${_a}:${_v}|${_file}")
endforeach()
endwhile()
# Modules required without a version may have been given one since.
set(_waiting)
foreach(_entry IN LISTS _pending)
string(REPLACE "|" ";" _fields "${_entry}")
list(GET _fields 1 _coordinates)
_cpack_apk_maven_split("${_coordinates}" _g _a _v)
string(MAKE_C_IDENTIFIER "${_g}:${_a}" _id)
if(NOT "${_selected_${_id}}${_constraint_${_id}}" STREQUAL "")
list(APPEND _queue "${_entry}")
else()
list(APPEND _waiting "${_entry}")
endif()
endforeach()
set(_pending "${_waiting}")
endwhile()
# The versions the next walk starts from.
list(REMOVE_DUPLICATES _ids)
set(_changed FALSE)
set(_state)
foreach(_id IN LISTS _ids)
foreach(_floor_kind IN ITEMS requested constrained)
if(_floor_kind STREQUAL "requested")
set(_floor_var _floor_${_id})
else()
set(_floor_var _floor_constraint_${_id})
endif()
set(_floor "${_${_floor_kind}_${_id}}")
if(_monotonic)
_cpack_apk_maven_version_higher("${${_floor_var}}" "${_floor}"
_higher)
if(_higher)
set(_floor "${${_floor_var}}")
endif()
endif()
if(NOT "${_floor}" STREQUAL "${${_floor_var}}")
set(${_floor_var} "${_floor}")
set(_changed TRUE)
endif()
list(APPEND _state "${_floor}")
endforeach()
endforeach()
if(_changed)
set(_walk TRUE)
if(NOT _monotonic)
string(MD5 _state "${_ids};${_state}")
if(_state IN_LIST _states)
set(_monotonic TRUE)
else()
list(APPEND _states "${_state}")
endif()
endif()
endif()
if(_pending AND NOT _walk)
set(_modules)
foreach(_entry IN LISTS _pending)
string(REPLACE "|" ";" _fields "${_entry}")
list(GET _fields 1 _coordinates)
_cpack_apk_maven_split("${_coordinates}" _g _a _v)
list(APPEND _modules "${_g}:${_a}")
endforeach()
list(REMOVE_DUPLICATES _modules)
list(JOIN _modules "\n " _modules)
message(FATAL_ERROR
"CPack APK: The Maven artifacts\n ${_modules}\nare required "
"without a version, and no platform or dependency constraint gives "
"one.")
endif()
endwhile()
set(_archives)
foreach(_entry IN LISTS _selected_files)
string(REPLACE "|" ";" _fields "${_entry}")
list(GET _fields 0 _coordinates)
list(GET _fields 1 _name)
list(GET _fields 2 _hash)
_cpack_apk_maven_split("${_coordinates}" _g _a _v)
_cpack_apk_maven_fetch(${_g} ${_a} ${_v} "${_name}" _path ${_hash})
if(NOT _path)
message(FATAL_ERROR
"CPack APK: '${_name}' of the Maven artifact '${_coordinates}' could "
"not be downloaded.")
endif()
# Only code archives are consumed; a module may publish other files.
if(_path MATCHES "\\.(aar|jar)$")
list(APPEND _archives "${_path}")
_cpack_apk_debug("Maven dependency: ${_path}")
endif()
endforeach()
set(${result} "${_archives}" PARENT_SCOPE)
endfunction()