cmd: implement json version output

This commit is contained in:
AJIOB
2026-01-24 18:24:56 +03:00
committed by Aliaksandr Averchanka
parent 89fd18b48e
commit 39a3915022
20 changed files with 667 additions and 5 deletions

View File

@@ -0,0 +1,109 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"dependencies": {
"type": "array",
"items": {
"type": "object",
"description": "Information about a single dependency.",
"properties": {
"name": {
"type": "string",
"description": "The name of the dependency."
},
"type": {
"type": "string",
"description": "The type of the dependency.",
"enum": [
"system",
"bundled"
]
},
"version": {
"type": "string",
"description": "The version of the dependency if available."
},
"via": {
"type": "string",
"description": "The source from which the dependency is came from. Not presented for direct CMake dependencies."
}
},
"required": [
"name",
"type"
],
"additionalProperties": false
},
"additionalItems": false
},
"program": {
"type": "object",
"description": "Information about the CMake tool.",
"properties": {
"name": {
"type": "string",
"description": "The name of the CMake tool."
},
"version": {
"type": "object",
"description": "Information about the version of the CMake tool.",
"properties": {
"major": {
"type": "integer",
"description": "The major version of the CMake tool."
},
"minor": {
"type": "integer",
"description": "The minor version of the CMake tool."
},
"patch": {
"type": "integer",
"description": "The patch version of the CMake tool."
},
"string": {
"type": "string",
"description": "The full version string of the CMake tool."
}
},
"required": [
"major",
"minor",
"patch",
"string"
],
"additionalProperties": false
}
},
"required": [
"name",
"version"
],
"additionalProperties": false
},
"version": {
"type": "object",
"properties": {
"major": {
"type": "integer",
"description": "The major version of the JSON output format."
},
"minor": {
"type": "integer",
"description": "The minor version of the JSON output format."
}
},
"required": [
"major",
"minor"
],
"additionalProperties": false
}
},
"required": [
"dependencies",
"program",
"version"
],
"additionalProperties": false
}

View File

@@ -1,10 +1,16 @@
.. |file| replace:: The output is printed to a named ``<file>`` if given.
.. option:: -version [<file>], --version [<file>], /V [<file>]
.. option:: -version[=json-v1] [<file>], --version[=json-v1] [<file>], /V[=json-v1] [<file>], /version[=json-v1] [<file>]
Show program name/version banner and exit.
Show program name/version banner and exit. If ``json-v1`` is
specified, print extended version information in JSON format.
The JSON output contains the versions for the CMake and its
dependencies.
|file|
The JSON output format is described in machine-readable form by
:download:`this JSON schema </manual/cmake/version-schema.json>`.
.. option:: -h, -H, --help, -help, -usage, /?
Print usage information and exit.

View File

@@ -0,0 +1,5 @@
cmd-version-json
----------------
* The :option:`cmake --version` option and variants now support a ``=json-v1``
value to print detailed version information in a JSON format.

View File

@@ -522,6 +522,7 @@ add_library(
cmUVStreambuf.h
cmVariableWatch.cxx
cmVariableWatch.h
cmVersion_Dependencies.cxx
cmVersion.cxx
cmVersion.h
cmWindowsRegistry.cxx

View File

@@ -11,6 +11,13 @@
#include "cmsys/Glob.hxx"
#include "cmsys/RegularExpression.hxx"
#if !defined(CMAKE_BOOTSTRAP)
# include <memory>
# include <cm3p/json/value.h>
# include <cm3p/json/writer.h>
#endif
#include "cmDocumentationEntry.h"
#include "cmDocumentationSection.h"
#include "cmRST.h"
@@ -21,7 +28,9 @@
namespace {
cmDocumentationEntry const cmDocumentationStandardOptions[21] = {
{ "-h,-H,--help,-help,-usage,/?", "Print usage information and exit." },
{ "--version,-version,/V [<file>]", "Print version number and exit." },
{ "--version[=json-v1],-version[=json-v1],/V[=json-v1],/version[=json-v1] "
"[<file>]",
"Print version number and exit." },
{ "--help <keyword> [<file>]", "Print help for one keyword and exit." },
{ "--help-full [<file>]", "Print all help manuals and exit." },
{ "--help-manual <man> [<file>]", "Print one help manual and exit." },
@@ -86,6 +95,60 @@ bool cmDocumentation::PrintVersion(std::ostream& os)
return true;
}
bool cmDocumentation::PrintVersionJson(std::ostream& os)
{
#if !defined(CMAKE_BOOTSTRAP)
Json::Value root = Json::objectValue;
// Output version information
root["version"] = Json::objectValue;
root["version"]["major"] = 1;
root["version"]["minor"] = 0;
// CMake tool name
root["program"] = Json::objectValue;
root["program"]["name"] = this->GetNameString();
// CMake version information
root["program"]["version"] = Json::objectValue;
root["program"]["version"]["string"] = cmVersion::GetCMakeVersion();
root["program"]["version"]["major"] = cmVersion::GetMajorVersion();
root["program"]["version"]["minor"] = cmVersion::GetMinorVersion();
root["program"]["version"]["patch"] = cmVersion::GetPatchVersion();
// Dependencies
root["dependencies"] = Json::arrayValue;
std::vector<cmVersion::DependencyInfo> const& deps =
cmVersion::CollectDependencyInfo();
for (cmVersion::DependencyInfo const& dep : deps) {
Json::Value depJson = Json::objectValue;
depJson["name"] = dep.name;
if (!dep.version.empty()) {
depJson["version"] = dep.version;
}
depJson["type"] =
dep.type == cmVersion::DependencyType::System ? "system" : "bundled";
if (!dep.cameFrom.empty()) {
depJson["via"] = dep.cameFrom;
}
root["dependencies"].append(std::move(depJson));
}
// Output JSON
Json::StreamWriterBuilder builder;
builder["indentation"] = " ";
std::unique_ptr<Json::StreamWriter> writer(builder.newStreamWriter());
writer->write(root, &os);
os << std::endl;
return true;
#else
os << "{\"error\":\"JSON version output not available in bootstrap build\"}"
<< std::endl;
return false;
#endif
}
bool cmDocumentation::PrintDocumentation(Type ht, std::ostream& os)
{
switch (ht) {
@@ -125,6 +188,8 @@ bool cmDocumentation::PrintDocumentation(Type ht, std::ostream& os)
return this->PrintHelpListGenerators(os);
case cmDocumentation::Version:
return this->PrintVersion(os);
case cmDocumentation::VersionJson:
return this->PrintVersionJson(os);
case cmDocumentation::OldCustomModules:
return this->PrintOldCustomModules(os);
default:
@@ -376,9 +441,16 @@ bool cmDocumentation::CheckOptions(int argc, char const* const* argv,
return true;
} else if ((strcmp(argv[i], "--version") == 0) ||
(strcmp(argv[i], "-version") == 0) ||
(strcmp(argv[i], "/V") == 0)) {
(strcmp(argv[i], "/V") == 0) ||
(strcmp(argv[i], "/version") == 0)) {
help.HelpType = cmDocumentation::Version;
i += int(get_opt_argument(i + 1, help.Filename));
} else if (strcmp(argv[i], "--version=json-v1") == 0 ||
strcmp(argv[i], "-version=json-v1") == 0 ||
strcmp(argv[i], "/V=json-v1") == 0 ||
strcmp(argv[i], "/version=json-v1") == 0) {
help.HelpType = cmDocumentation::VersionJson;
i += int(get_opt_argument(i + 1, help.Filename));
}
if (help.HelpType != None) {
// This is a help option. See if there is a file name given.

View File

@@ -24,6 +24,7 @@ public:
{
None,
Version,
VersionJson,
Usage,
Help,
Full,
@@ -116,6 +117,7 @@ private:
bool PrintFiles(std::ostream& os, std::string const& pattern);
bool PrintVersion(std::ostream& os);
bool PrintVersionJson(std::ostream& os);
bool PrintUsage(std::ostream& os);
bool PrintHelp(std::ostream& os);
bool PrintHelpFull(std::ostream& os);

View File

@@ -2,6 +2,9 @@
file LICENSE.rst or https://cmake.org/licensing for details. */
#pragma once
#include <string>
#include <vector>
/** \class cmVersion
* \brief Helper class for providing CMake and CTest version information.
*
@@ -10,6 +13,37 @@
class cmVersion
{
public:
enum class DependencyType
{
System,
Bundled,
};
struct DependencyInfo
{
/**
* The name of the dependency.
* e.g. "curl", "libarchive", "zlib", etc.
*/
std::string name;
/**
* The version of the dependency if available.
* e.g. "7.66.0", "3.8.0", "1.2.12", etc.
*/
std::string version;
/**
* The type of the dependency.
*/
DependencyType type;
/**
* The source of the dependency.
* e.g. "curl", "libarchive", etc.
* Empty if the dependency is directly passed from CMake.
*/
std::string cameFrom;
};
/**
* Return major and minor version numbers for cmake.
*/
@@ -18,4 +52,6 @@ public:
static unsigned int GetPatchVersion();
static unsigned int GetTweakVersion();
static char const* GetCMakeVersion();
static std::vector<DependencyInfo> const& CollectDependencyInfo();
};

View File

@@ -0,0 +1,363 @@
/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
file LICENSE.rst or https://cmake.org/licensing for details. */
#include "cmVersion.h"
#if !defined(CMAKE_BOOTSTRAP)
# include <cstddef>
# include <string>
# include <utility>
# include <vector>
# include <cm3p/archive.h>
# include <cm3p/curl/curl.h>
# include <cm3p/expat.h>
# include <cm3p/json/version.h>
# include <cm3p/kwiml/version.h>
# include "cmThirdParty.h" // IWYU pragma: keep
# ifdef CMAKE_USE_SYSTEM_LIBRHASH
# include <cstdint>
# include <cm3p/rhash.h>
# endif
# include <cm3p/uv.h>
# include <cm3p/zlib.h>
# include "cmStringAlgorithms.h"
std::vector<cmVersion::DependencyInfo> const&
cmVersion::CollectDependencyInfo()
{
static std::vector<DependencyInfo> deps;
if (!deps.empty()) {
return deps;
}
// BZIP2 is not directly used in CMake, so it is not included here
// BZIP2 (libarchive)
{
char const* bzip2Version = archive_bzlib_version();
if (bzip2Version) {
DependencyInfo info;
info.name = "bzip2";
info.version = bzip2Version;
info.cameFrom = "libarchive";
size_t pos = info.version.find(',');
if (pos != std::string::npos) {
// Convert `1.0.8, 30-Mar-2009` to `1.0.8`
info.version.erase(pos);
}
# if defined(CMAKE_USE_SYSTEM_BZIP2) || defined(CMAKE_USE_SYSTEM_LIBARCHIVE)
// System BZIP2 can be used by system or bundled libarchive
// System libarchive always uses system BZIP2
info.type = DependencyType::System;
# else
info.type = DependencyType::Bundled;
# endif
deps.emplace_back(std::move(info));
}
}
// CPPDAP
{
DependencyInfo info;
info.name = "cppdap";
# ifdef CMAKE_USE_SYSTEM_CPPDAP
info.type = DependencyType::System;
// Cannot get runtime version from cppdap library
# else
info.type = DependencyType::Bundled;
// Hardcoded in protocol.h header file comments
info.version = "1.65.0";
# endif
deps.emplace_back(std::move(info));
}
// CURL
{
curl_version_info_data* curlVersion = curl_version_info(CURLVERSION_NOW);
if (curlVersion) {
// CURL itself
{
DependencyInfo info;
info.name = "curl";
if (curlVersion->version) {
info.version = curlVersion->version;
}
# ifdef CMAKE_USE_SYSTEM_CURL
info.type = DependencyType::System;
# else
info.type = DependencyType::Bundled;
# endif
deps.emplace_back(std::move(info));
}
// Cannot use CURL_AT_LEAST_VERSION and CURL_VERSION_BITS macros,
// because they needs at least curl 7.43.0,
// but we support curl 7.29.0 from CentOS 7
# if LIBCURL_VERSION_NUM >= 0x074200
// NGHTTP2 (curl)
// Added in curl 7.66.0 (0x074200), CURLVERSION_SIXTH
if (curlVersion->age >= CURLVERSION_SIXTH &&
curlVersion->nghttp2_version) {
DependencyInfo info;
info.name = "nghttp2";
info.cameFrom = "curl";
info.version = curlVersion->nghttp2_version;
# if defined(CMAKE_USE_SYSTEM_NGHTTP2) || defined(CMAKE_USE_SYSTEM_CURL)
// System CURL always uses system NGHTTP2
// System NGHTTP2 can be used by system or bundled CURL
info.type = DependencyType::System;
# else
info.type = DependencyType::Bundled;
# endif
deps.emplace_back(std::move(info));
}
# endif
// OPENSSL (curl)
if (curlVersion->ssl_version) {
DependencyInfo info;
info.name = "ssl";
info.cameFrom = "curl";
info.version = curlVersion->ssl_version;
// With Multi-SSL, the version string is `OpenSSL/3.3.5,
// BoringSSL/3.3.5`, etc.
if (cmHasLiteralPrefix(info.version, "OpenSSL/") &&
info.version.find('/', 8) == std::string::npos) {
info.name = "openssl";
info.version.erase(0, 8);
}
// Bundled version of OpenSSL is not presented
// Multi-SSL can be used by system CURL only,
// so the SSL library is always system
info.type = DependencyType::System;
deps.emplace_back(std::move(info));
}
// ZLIB (curl)
if (curlVersion->libz_version) {
DependencyInfo info;
info.name = "zlib";
info.cameFrom = "curl";
info.version = curlVersion->libz_version;
# if defined(CMAKE_USE_SYSTEM_ZLIB) || defined(CMAKE_USE_SYSTEM_CURL)
// System CURL always uses system ZLIB
// System ZLIB can be used by system or bundled CURL
info.type = DependencyType::System;
# else
info.type = DependencyType::Bundled;
# endif
deps.emplace_back(std::move(info));
}
}
}
// EXPAT
{
DependencyInfo info;
info.name = "expat";
XML_Expat_Version version = XML_ExpatVersionInfo();
info.version =
cmStrCat(version.major, '.', version.minor, '.', version.micro);
# ifdef CMAKE_USE_SYSTEM_EXPAT
info.type = DependencyType::System;
# else
info.type = DependencyType::Bundled;
# endif
deps.emplace_back(std::move(info));
}
// FORM
{
DependencyInfo info;
info.name = "form";
// Cannot get any version from form library
# ifdef CMAKE_USE_SYSTEM_FORM
info.type = DependencyType::System;
# else
info.type = DependencyType::Bundled;
# endif
deps.emplace_back(std::move(info));
}
// JSONCPP
{
DependencyInfo info;
info.name = "jsoncpp";
info.version = JSONCPP_VERSION_STRING;
# ifdef CMAKE_USE_SYSTEM_JSONCPP
info.type = DependencyType::System;
# else
info.type = DependencyType::Bundled;
# endif
deps.emplace_back(std::move(info));
}
// KWIML
{
DependencyInfo info;
info.name = "kwiml";
// Library is header-only, so we can safely use the defined version
info.version = KWIML_VERSION_STRING;
# ifdef CMAKE_USE_SYSTEM_KWIML
info.type = DependencyType::System;
# else
info.type = DependencyType::Bundled;
# endif
deps.emplace_back(std::move(info));
}
// LIBARCHIVE
{
DependencyInfo info;
info.name = "libarchive";
info.version = archive_version_string();
if (cmHasLiteralPrefix(info.version, "libarchive ")) {
info.version.erase(0, 11);
}
# ifdef CMAKE_USE_SYSTEM_LIBARCHIVE
info.type = DependencyType::System;
# else
info.type = DependencyType::Bundled;
# endif
deps.emplace_back(std::move(info));
}
// LIBLZMA is not directly used in CMake, so it is not included here
// LIBLZMA (libarchive)
{
char const* liblzmaVersion = archive_liblzma_version();
if (liblzmaVersion) {
DependencyInfo info;
info.name = "liblzma";
info.cameFrom = "libarchive";
info.version = liblzmaVersion;
# if defined(CMAKE_USE_SYSTEM_LIBLZMA) || defined(CMAKE_USE_SYSTEM_LIBARCHIVE)
// System LIBLZMA can be used by system or bundled libarchive
// System libarchive always uses system LIBLZMA
info.type = DependencyType::System;
# else
info.type = DependencyType::Bundled;
# endif
deps.emplace_back(std::move(info));
}
}
// LIBRHASH
{
DependencyInfo info;
info.name = "librhash";
# ifdef CMAKE_USE_SYSTEM_LIBRHASH
info.type = DependencyType::System;
std::uint64_t version = rhash_get_version();
std::uint8_t major = (version >> 24) & 0xFFu;
std::uint8_t minor = (version >> 16) & 0xFFu;
std::uint8_t patch = (version >> 8) & 0xFFu;
info.version = cmStrCat(major, '.', minor, '.', patch);
# else
info.type = DependencyType::Bundled;
// Hardcoded in `update-librhash.bash` script
info.version = "1.4.4";
# endif
deps.emplace_back(std::move(info));
}
// LIBUV
{
DependencyInfo info;
info.name = "libuv";
info.version = uv_version_string();
# ifdef CMAKE_USE_SYSTEM_LIBUV
info.type = DependencyType::System;
# else
info.type = DependencyType::Bundled;
# endif
deps.emplace_back(std::move(info));
}
// OPENSSL is not directly used in CMake, so it is not included here
# if ARCHIVE_VERSION_NUMBER >= 3008000
// OPENSSL (libarchive)
// This function is available in libarchive 3.8.0 (3008000) and newer
{
char const* opensslVersion = archive_openssl_version();
if (opensslVersion) {
DependencyInfo info;
info.name = "openssl";
info.cameFrom = "libarchive";
info.version = opensslVersion;
// Bundled version of OpenSSL is not presented
info.type = DependencyType::System;
deps.emplace_back(std::move(info));
}
}
# endif
// ZLIB
{
DependencyInfo info;
info.name = "zlib";
info.version = zlibVersion();
# ifdef CMAKE_USE_SYSTEM_ZLIB
info.type = DependencyType::System;
# else
info.type = DependencyType::Bundled;
# endif
deps.emplace_back(std::move(info));
}
// ZLIB (libarchive)
{
char const* zlibVersion = archive_zlib_version();
if (zlibVersion) {
DependencyInfo info;
info.name = "zlib";
info.cameFrom = "libarchive";
info.version = zlibVersion;
# if defined(CMAKE_USE_SYSTEM_ZLIB) || defined(CMAKE_USE_SYSTEM_LIBARCHIVE)
// System ZLIB can be used by system or bundled libarchive
// System libarchive always uses system ZLIB
info.type = DependencyType::System;
# else
info.type = DependencyType::Bundled;
# endif
deps.emplace_back(std::move(info));
}
}
// ZSTD is not directly used in CMake, so it is not included here
// ZSTD (libarchive)
{
char const* zstdVersion = archive_libzstd_version();
if (zstdVersion) {
DependencyInfo info;
info.name = "zstd";
info.cameFrom = "libarchive";
info.version = zstdVersion;
# if defined(CMAKE_USE_SYSTEM_ZSTD) || defined(CMAKE_USE_SYSTEM_LIBARCHIVE)
// System ZSTD can be used by system or bundled libarchive
// System libarchive always uses system ZSTD
info.type = DependencyType::System;
# else
info.type = DependencyType::Bundled;
# endif
deps.emplace_back(std::move(info));
}
}
return deps;
}
#endif

View File

@@ -1080,6 +1080,7 @@ endif()
add_executable(pseudo_llvm-rc pseudo_llvm-rc.c)
add_RunCMake_test(CommandLine -DLLVM_RC=$<TARGET_FILE:pseudo_llvm-rc> -DCMAKE_SYSTEM_NAME=${CMAKE_SYSTEM_NAME}
-DCYGWIN=${CYGWIN} -DMSYS=${MSYS} -DPython_EXECUTABLE=${Python_EXECUTABLE}
-DCMake_TEST_JSON_SCHEMA=${CMake_TEST_JSON_SCHEMA}
-DEXIT_CODE_EXE=$<TARGET_FILE:exit_code>
-DPRINT_STDIN_EXE=$<TARGET_FILE:print_stdin>)
if(CMake_TEST_LibArchive_VERSION)

View File

@@ -2,6 +2,49 @@ cmake_minimum_required(VERSION 3.10)
include(RunCMake)
cmake_policy(SET CMP0140 NEW)
function(version_json_check_python v is_json_ready)
if(RunCMake_TEST_FAILED OR NOT Python_EXECUTABLE OR NOT CMake_TEST_JSON_SCHEMA)
return()
endif()
set(json_file "${RunCMake_TEST_BINARY_DIR}/version-v${v}.json")
if (NOT is_json_ready)
file(WRITE "${json_file}" "${actual_stdout}")
set(actual_stdout "" PARENT_SCOPE)
endif()
execute_process(
COMMAND ${Python_EXECUTABLE} "${RunCMake_SOURCE_DIR}/version_json_validate_schema.py" "${json_file}"
RESULT_VARIABLE result
OUTPUT_VARIABLE output
ERROR_VARIABLE output
)
if(NOT result STREQUAL 0)
string(REPLACE "\n" "\n " output "${output}")
string(APPEND RunCMake_TEST_FAILED "Failed to validate version ${v} JSON schema for file: ${json_file}\nOutput:\n${output}\n")
endif()
return(PROPAGATE RunCMake_TEST_FAILED)
endfunction()
run_cmake_command(versionSingleDash ${CMAKE_COMMAND} -version version.txt)
run_cmake_command(versionSingleDashJson ${CMAKE_COMMAND} -version=json-v1 version-v1.json)
run_cmake_command(versionDoubleDash ${CMAKE_COMMAND} --version version.txt)
run_cmake_command(versionDoubleDashJson ${CMAKE_COMMAND} --version=json-v1 version-v1.json)
run_cmake_command(versionSlash ${CMAKE_COMMAND} /version version.txt)
run_cmake_command(versionSlashJson ${CMAKE_COMMAND} /version=json-v1 version-v1.json)
run_cmake_command(versionV ${CMAKE_COMMAND} /V version.txt)
run_cmake_command(versionVJson ${CMAKE_COMMAND} /V=json-v1 version-v1.json)
run_cmake_command(versionSingleDashNoArg ${CMAKE_COMMAND} -version)
run_cmake_command(versionSingleDashJsonNoArg ${CMAKE_COMMAND} -version=json-v1)
run_cmake_command(versionDoubleDashNoArg ${CMAKE_COMMAND} --version)
run_cmake_command(versionDoubleDashJsonNoArg ${CMAKE_COMMAND} --version=json-v1)
run_cmake_command(versionSlashNoArg ${CMAKE_COMMAND} /version)
run_cmake_command(versionSlashJsonNoArg ${CMAKE_COMMAND} /version=json-v1)
run_cmake_command(versionVNoArg ${CMAKE_COMMAND} /V)
run_cmake_command(versionVJsonNoArg ${CMAKE_COMMAND} /V=json-v1)
run_cmake_command(NoArgs ${CMAKE_COMMAND})
run_cmake_command(InvalidArg1 ${CMAKE_COMMAND} -invalid)
run_cmake_command(InvalidArg2 ${CMAKE_COMMAND} --invalid)

View File

@@ -0,0 +1 @@
version_json_check_python(1 TRUE)

View File

@@ -0,0 +1 @@
version_json_check_python(1 FALSE)

View File

@@ -0,0 +1 @@
version_json_check_python(1 TRUE)

View File

@@ -0,0 +1 @@
version_json_check_python(1 FALSE)

View File

@@ -0,0 +1 @@
version_json_check_python(1 TRUE)

View File

@@ -0,0 +1 @@
version_json_check_python(1 FALSE)

View File

@@ -0,0 +1 @@
version_json_check_python(1 TRUE)

View File

@@ -0,0 +1 @@
version_json_check_python(1 FALSE)

View File

@@ -0,0 +1,16 @@
import json
import jsonschema
import os.path
import sys
with open(sys.argv[1], "r", encoding="utf-8-sig") as f:
contents = json.load(f)
schema_file = os.path.join(
os.path.dirname(__file__),
"..", "..", "..", "Help", "manual", "cmake", "version-schema.json")
with open(schema_file, "r", encoding="utf-8") as f:
schema = json.load(f)
jsonschema.validate(contents, schema)

View File

@@ -8,7 +8,7 @@ readonly name="librhash"
readonly ownership="librhash upstream <kwrobot@kitware.com>"
readonly subtree="Utilities/cmlibrhash"
readonly repo="https://github.com/rhash/rhash.git"
readonly tag="v1.4.4"
readonly tag="v1.4.4" # When updating, sync version in cmVersion_Dependencies.cxx!
readonly shortlog=false
readonly exact_tree_match=false
readonly paths="