mirror of
https://github.com/Kitware/CMake.git
synced 2026-08-03 14:20:27 +00:00
cmake_language: Add PRINT_TARGETS operation
Print CMake targets in human-readable form for debugging. Issue: #27513
This commit is contained in:
@@ -15,6 +15,7 @@ Synopsis
|
||||
cmake_language(`DEFER`_ <options>... CALL <command> [<arg>...])
|
||||
cmake_language(`SET_DEPENDENCY_PROVIDER`_ <command> SUPPORTED_METHODS <methods>...)
|
||||
cmake_language(`GET_MESSAGE_LOG_LEVEL`_ <out-var>)
|
||||
cmake_language(`PRINT_TARGETS`_ <filter>...)
|
||||
cmake_language(`EXIT`_ <exit-code>)
|
||||
cmake_language(`TRACE`_ <boolean> ...)
|
||||
|
||||
@@ -511,6 +512,58 @@ Getting current message log level
|
||||
option takes precedence. If neither are set, the default logging level
|
||||
is returned.
|
||||
|
||||
Printing Targets
|
||||
^^^^^^^^^^^^^^^^
|
||||
|
||||
.. versionadded:: 4.5
|
||||
|
||||
.. signature::
|
||||
cmake_language(PRINT_TARGETS <filter>...)
|
||||
|
||||
Prints the names of all targets that currently exist, one per line,
|
||||
annotated with their types (e.g. ``EXECUTABLE``, ``STATIC_LIBRARY``,
|
||||
``INTERFACE_LIBRARY``, ``UTILITY``). Imported targets are additionally
|
||||
annotated with ``IMPORTED``. :ref:`Alias Targets` are not listed; their
|
||||
aliased target is.
|
||||
|
||||
The output is sorted by target name and printed as a single status message.
|
||||
Each ``<filter>`` may be one of:
|
||||
|
||||
``REGEX <regex>``
|
||||
Only list targets whose name matches the given
|
||||
:ref:`regular expression <Regex Specification>`. If no target matches,
|
||||
a warning is issued.
|
||||
|
||||
``IGNORE_CASE``
|
||||
Match the ``REGEX`` case-insensitively. Only valid with ``REGEX``.
|
||||
|
||||
``IMPORTED_ONLY``
|
||||
Only list :ref:`Imported Targets`.
|
||||
|
||||
``NO_IMPORTED``
|
||||
Exclude imported targets; only list targets built by this project.
|
||||
|
||||
``IMPORTED_ONLY`` and ``NO_IMPORTED`` are mutually exclusive. When neither
|
||||
is given, both imported and non-imported targets are listed.
|
||||
|
||||
Printing Targets Examples
|
||||
"""""""""""""""""""""""""
|
||||
|
||||
.. code-block:: cmake
|
||||
|
||||
add_executable(app main.c)
|
||||
add_library(util STATIC util.c)
|
||||
find_package(Threads REQUIRED) # provides imported Threads::Threads
|
||||
|
||||
cmake_language(PRINT_TARGETS REGEX "^(app|util)$" NO_IMPORTED)
|
||||
|
||||
Gives::
|
||||
|
||||
-- Printing targets...
|
||||
Non-imported targets matching REGEX '^(app|util)$' (case sensitive):
|
||||
app (EXECUTABLE)
|
||||
util (STATIC_LIBRARY)
|
||||
|
||||
Terminating Scripts
|
||||
^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
|
||||
6
Help/release/dev/print_targets.rst
Normal file
6
Help/release/dev/print_targets.rst
Normal file
@@ -0,0 +1,6 @@
|
||||
print_targets
|
||||
-------------
|
||||
|
||||
* The :command:`cmake_language(PRINT_TARGETS)` command was added
|
||||
to print the targets that currently exist in human-readable form
|
||||
for debugging.
|
||||
@@ -5,13 +5,19 @@
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include <cm/optional>
|
||||
#include <cm/string_view>
|
||||
#include <cmext/string_view>
|
||||
|
||||
#include "cmsys/RegularExpression.hxx"
|
||||
|
||||
#include "cmArgumentParser.h"
|
||||
#include "cmArgumentParserTypes.h"
|
||||
#include "cmDependencyProvider.h"
|
||||
@@ -25,9 +31,14 @@
|
||||
#include "cmState.h"
|
||||
#include "cmStringAlgorithms.h"
|
||||
#include "cmSystemTools.h"
|
||||
#include "cmTarget.h"
|
||||
#include "cmValue.h"
|
||||
#include "cmake.h"
|
||||
|
||||
namespace cm {
|
||||
enum class TargetType;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
bool FatalError(cmExecutionStatus& status, std::string const& error)
|
||||
@@ -360,6 +371,157 @@ bool cmCMakeLanguageCommandGET_EXPERIMENTAL_FEATURE_ENABLED(
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
struct PrintTargetsArgs : public ArgumentParser::ParseResult
|
||||
{
|
||||
cm::optional<std::string> Regex;
|
||||
bool ImportedOnly = false;
|
||||
bool NoImported = false;
|
||||
bool IgnoreCase = false;
|
||||
cm::optional<std::string> MessagePrefix;
|
||||
};
|
||||
|
||||
// Lists every target that currently exists, optionally filtered by a
|
||||
// name REGEX and by imported state. "Currently exists" means anything
|
||||
// CMake has defined up to this call: targets in the current directory,
|
||||
// its ancestors, and any already-processed subdirectories. Walking the
|
||||
// global generator's makefiles captures exactly that set; a name-keyed
|
||||
// map sorts the output and dedupes imported targets, which are inherited
|
||||
// into child makefiles and would otherwise be seen many times.
|
||||
bool cmCMakeLanguageCommandPRINT_TARGETS(
|
||||
std::vector<cmListFileArgument> const& args, cmExecutionStatus& status)
|
||||
{
|
||||
cmMakefile& makefile = status.GetMakefile();
|
||||
std::vector<std::string> expandedArgs;
|
||||
makefile.ExpandArguments(args, expandedArgs);
|
||||
|
||||
// Drop the leading "PRINT_TARGETS" subcommand keyword.
|
||||
std::vector<std::string> body(expandedArgs.begin() + 1, expandedArgs.end());
|
||||
|
||||
auto const ArgsParser =
|
||||
cmArgumentParser<PrintTargetsArgs>()
|
||||
.Bind("REGEX"_s, &PrintTargetsArgs::Regex)
|
||||
.Bind("IMPORTED_ONLY"_s, &PrintTargetsArgs::ImportedOnly)
|
||||
.Bind("NO_IMPORTED"_s, &PrintTargetsArgs::NoImported)
|
||||
.Bind("IGNORE_CASE"_s, &PrintTargetsArgs::IgnoreCase)
|
||||
.Bind("__MESSAGE_PREFIX"_s, &PrintTargetsArgs::MessagePrefix);
|
||||
|
||||
std::vector<std::string> unparsed;
|
||||
auto parsedArgs = ArgsParser.Parse(body, &unparsed);
|
||||
|
||||
if (!unparsed.empty()) {
|
||||
return FatalError(
|
||||
status,
|
||||
cmStrCat(
|
||||
"Unknown argument(s) given to cmake_language(PRINT_TARGETS) call: \"",
|
||||
cmJoin(unparsed, "\" \""), "\"."));
|
||||
}
|
||||
if (parsedArgs.MaybeReportError(makefile)) {
|
||||
cmSystemTools::SetFatalErrorOccurred();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (parsedArgs.ImportedOnly && parsedArgs.NoImported) {
|
||||
return FatalError(status,
|
||||
"IMPORTED_ONLY and NO_IMPORTED keywords are mutually "
|
||||
"exclusive in cmake_language(PRINT_TARGETS) call.");
|
||||
}
|
||||
|
||||
if (parsedArgs.IgnoreCase && !parsedArgs.Regex) {
|
||||
return FatalError(status,
|
||||
"IGNORE_CASE keyword in cmake_language(PRINT_TARGETS) "
|
||||
"call is only valid with REGEX.");
|
||||
}
|
||||
|
||||
// Compile the optional REGEX up front so a bad pattern fails fast. With
|
||||
// IGNORE_CASE the pattern and the candidate names are both lower-cased.
|
||||
cm::optional<cmsys::RegularExpression> regex;
|
||||
if (parsedArgs.Regex) {
|
||||
cmsys::RegularExpression re;
|
||||
std::string const pat = parsedArgs.IgnoreCase
|
||||
? cmSystemTools::LowerCase(*parsedArgs.Regex)
|
||||
: *parsedArgs.Regex;
|
||||
if (!re.compile(pat)) {
|
||||
return FatalError(status,
|
||||
cmStrCat("REGEX regular expression \"",
|
||||
*parsedArgs.Regex, "\" cannot compile."));
|
||||
}
|
||||
regex = std::move(re);
|
||||
}
|
||||
|
||||
bool const includeNormal = !parsedArgs.ImportedOnly;
|
||||
bool const includeImported = !parsedArgs.NoImported;
|
||||
|
||||
struct TargetInfo
|
||||
{
|
||||
cm::TargetType Type;
|
||||
bool Imported;
|
||||
};
|
||||
std::map<std::string, TargetInfo> targets;
|
||||
for (auto const& mf : makefile.GetGlobalGenerator()->GetMakefiles()) {
|
||||
if (includeNormal) {
|
||||
for (auto const& ti : mf->GetTargets()) {
|
||||
cmTarget const& t = ti.second;
|
||||
targets.insert({ t.GetName(), { t.GetType(), false } });
|
||||
}
|
||||
}
|
||||
if (includeImported) {
|
||||
for (cmTarget const* t : mf->GetImportedTargets()) {
|
||||
targets.insert({ t->GetName(), { t->GetType(), true } });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Build the body first so the header is suppressed when a REGEX filters
|
||||
// everything out (matches cmake_language(PRINT_VARIABLES) behavior).
|
||||
std::string lines;
|
||||
bool anyMatched = false;
|
||||
for (auto const& t : targets) {
|
||||
if (regex) {
|
||||
std::string const subj =
|
||||
parsedArgs.IgnoreCase ? cmSystemTools::LowerCase(t.first) : t.first;
|
||||
if (!regex->find(subj)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
lines +=
|
||||
cmStrCat(" ", t.first, " (", cmState::GetTargetTypeName(t.second.Type),
|
||||
t.second.Imported ? ", IMPORTED" : "", ")\n");
|
||||
anyMatched = true;
|
||||
}
|
||||
|
||||
if (anyMatched) {
|
||||
// The message opens with a banner line. The internal __MESSAGE_PREFIX
|
||||
// keyword overrides it (used by wrappers to reproduce legacy output); it
|
||||
// is not part of the public interface.
|
||||
std::string const messagePrefix =
|
||||
parsedArgs.MessagePrefix.value_or("Printing targets...\n");
|
||||
// The header reflects the imported-filter mode and any REGEX in effect.
|
||||
char const* label = "All targets";
|
||||
if (parsedArgs.ImportedOnly) {
|
||||
label = "Imported targets";
|
||||
} else if (parsedArgs.NoImported) {
|
||||
label = "Non-imported targets";
|
||||
}
|
||||
std::string out = cmStrCat(messagePrefix, " ", label);
|
||||
if (parsedArgs.Regex) {
|
||||
out += cmStrCat(
|
||||
" matching REGEX '", *parsedArgs.Regex, "' (",
|
||||
parsedArgs.IgnoreCase ? "case insensitive" : "case sensitive", ")");
|
||||
}
|
||||
out += cmStrCat(":\n", lines);
|
||||
makefile.DisplayStatus(out, -1);
|
||||
}
|
||||
|
||||
if (!anyMatched && parsedArgs.Regex) {
|
||||
makefile.IssueMessage(
|
||||
MessageType::WARNING,
|
||||
cmStrCat("No targets matching REGEX '", *parsedArgs.Regex, "' (",
|
||||
parsedArgs.IgnoreCase ? "case insensitive" : "case sensitive",
|
||||
") in cmake_language(PRINT_TARGETS ...)."));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
bool cmCMakeLanguageCommand(std::vector<cmListFileArgument> const& args,
|
||||
@@ -539,6 +701,10 @@ bool cmCMakeLanguageCommand(std::vector<cmListFileArgument> const& args,
|
||||
status);
|
||||
}
|
||||
|
||||
if (expArgs[expArg] == "PRINT_TARGETS") {
|
||||
return cmCMakeLanguageCommandPRINT_TARGETS(args, status);
|
||||
}
|
||||
|
||||
if (expArgs[expArg] == "TRACE") {
|
||||
++expArg; // Consume "TRACE".
|
||||
|
||||
|
||||
14
Tests/RunCMake/cmake_language/PrintTargets-stdout.txt
Normal file
14
Tests/RunCMake/cmake_language/PrintTargets-stdout.txt
Normal file
@@ -0,0 +1,14 @@
|
||||
.*--.*
|
||||
+All targets matching REGEX '\^zz_print_targets_' \(case sensitive\):
|
||||
+zz_print_targets_iface \(INTERFACE_LIBRARY\)
|
||||
+zz_print_targets_imp \(STATIC_LIBRARY, IMPORTED\)
|
||||
+zz_print_targets_lib \(STATIC_LIBRARY\)
|
||||
+zz_print_targets_util \(UTILITY\)
|
||||
.*--.*
|
||||
+Non-imported targets matching REGEX '\^zz_print_targets_' \(case sensitive\):
|
||||
+zz_print_targets_iface \(INTERFACE_LIBRARY\)
|
||||
+zz_print_targets_lib \(STATIC_LIBRARY\)
|
||||
+zz_print_targets_util \(UTILITY\)
|
||||
.*--.*
|
||||
+Imported targets matching REGEX '\^zz_print_targets_' \(case sensitive\):
|
||||
+zz_print_targets_imp \(STATIC_LIBRARY, IMPORTED\)
|
||||
16
Tests/RunCMake/cmake_language/PrintTargets.cmake
Normal file
16
Tests/RunCMake/cmake_language/PrintTargets.cmake
Normal file
@@ -0,0 +1,16 @@
|
||||
enable_language(C)
|
||||
file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/zz_stub.c" "int zz_stub(void) { return 0; }\n")
|
||||
|
||||
add_library(zz_print_targets_iface INTERFACE)
|
||||
add_library(zz_print_targets_imp STATIC IMPORTED)
|
||||
add_library(zz_print_targets_lib STATIC "${CMAKE_CURRENT_BINARY_DIR}/zz_stub.c")
|
||||
add_custom_target(zz_print_targets_util)
|
||||
|
||||
# Default: both imported and non-imported targets.
|
||||
cmake_language(PRINT_TARGETS REGEX "^zz_print_targets_")
|
||||
|
||||
# NO_IMPORTED drops the imported target.
|
||||
cmake_language(PRINT_TARGETS REGEX "^zz_print_targets_" NO_IMPORTED)
|
||||
|
||||
# IMPORTED_ONLY keeps only the imported target.
|
||||
cmake_language(PRINT_TARGETS REGEX "^zz_print_targets_" IMPORTED_ONLY)
|
||||
@@ -0,0 +1,3 @@
|
||||
.*--.*
|
||||
+All targets matching REGEX '\^zz_' \(case sensitive\):
|
||||
+zz_real \(INTERFACE_LIBRARY\)
|
||||
4
Tests/RunCMake/cmake_language/PrintTargetsAlias.cmake
Normal file
4
Tests/RunCMake/cmake_language/PrintTargetsAlias.cmake
Normal file
@@ -0,0 +1,4 @@
|
||||
# Aliases are not listed; the aliased (real) target is.
|
||||
add_library(zz_real INTERFACE)
|
||||
add_library(zz_alias ALIAS zz_real)
|
||||
cmake_language(PRINT_TARGETS REGEX "^zz_")
|
||||
@@ -0,0 +1,3 @@
|
||||
CMake Warning at PrintTargetsEmptyMatch\.cmake:[0-9]+ \(cmake_language\):
|
||||
No targets matching REGEX '\^zz_no_match_' \(case sensitive\) in
|
||||
cmake_language\(PRINT_TARGETS \.\.\.\)\.
|
||||
@@ -0,0 +1,4 @@
|
||||
add_library(zz_present INTERFACE)
|
||||
|
||||
# A REGEX matching no target prints no header, just a warning.
|
||||
cmake_language(PRINT_TARGETS REGEX "^zz_no_match_")
|
||||
@@ -0,0 +1,3 @@
|
||||
CMake Warning at PrintTargetsIgnoreCase\.cmake:[0-9]+ \(cmake_language\):
|
||||
No targets matching REGEX 'ZZ_IC_LOWER' \(case sensitive\) in
|
||||
cmake_language\(PRINT_TARGETS \.\.\.\)\.
|
||||
@@ -0,0 +1,3 @@
|
||||
.*--.*
|
||||
+All targets matching REGEX 'ZZ_IC_LOWER' \(case insensitive\):
|
||||
+zz_ic_lower \(INTERFACE_LIBRARY\)
|
||||
@@ -0,0 +1,7 @@
|
||||
add_library(zz_ic_lower INTERFACE)
|
||||
|
||||
# Case-sensitive: an uppercase pattern does not match the lowercase target.
|
||||
cmake_language(PRINT_TARGETS REGEX "ZZ_IC_LOWER")
|
||||
|
||||
# IGNORE_CASE: the same pattern now matches.
|
||||
cmake_language(PRINT_TARGETS REGEX "ZZ_IC_LOWER" IGNORE_CASE)
|
||||
@@ -0,0 +1 @@
|
||||
1
|
||||
@@ -0,0 +1,3 @@
|
||||
CMake Error at PrintTargetsIgnoreCaseRequiresRegex\.cmake:[0-9]+ \(cmake_language\):
|
||||
cmake_language IGNORE_CASE keyword in cmake_language\(PRINT_TARGETS\) call is
|
||||
only valid with REGEX\.
|
||||
@@ -0,0 +1,2 @@
|
||||
# IGNORE_CASE is only meaningful with REGEX.
|
||||
cmake_language(PRINT_TARGETS IGNORE_CASE)
|
||||
@@ -0,0 +1,7 @@
|
||||
-- Printing targets\.\.\.
|
||||
All targets matching REGEX '\^zz_mp_' \(case sensitive\):
|
||||
zz_mp_lib \(INTERFACE_LIBRARY\)
|
||||
|
||||
-- CUSTOM_BANNER
|
||||
All targets matching REGEX '\^zz_mp_' \(case sensitive\):
|
||||
zz_mp_lib \(INTERFACE_LIBRARY\)
|
||||
@@ -0,0 +1,7 @@
|
||||
add_library(zz_mp_lib INTERFACE)
|
||||
|
||||
# Default banner opens the message.
|
||||
cmake_language(PRINT_TARGETS REGEX "^zz_mp_")
|
||||
|
||||
# The internal __MESSAGE_PREFIX keyword overrides the default banner.
|
||||
cmake_language(PRINT_TARGETS REGEX "^zz_mp_" __MESSAGE_PREFIX "CUSTOM_BANNER\n")
|
||||
@@ -0,0 +1 @@
|
||||
1
|
||||
@@ -0,0 +1,3 @@
|
||||
CMake Error at PrintTargetsMutuallyExclusive\.cmake:[0-9]+ \(cmake_language\):
|
||||
+cmake_language IMPORTED_ONLY and NO_IMPORTED keywords are mutually
|
||||
+exclusive in cmake_language\(PRINT_TARGETS\) call\.
|
||||
@@ -0,0 +1 @@
|
||||
cmake_language(PRINT_TARGETS IMPORTED_ONLY NO_IMPORTED)
|
||||
@@ -0,0 +1,3 @@
|
||||
.*--.*
|
||||
+All targets:
|
||||
.* +mytarget \(INTERFACE_LIBRARY\)
|
||||
3
Tests/RunCMake/cmake_language/PrintTargetsNoRegex.cmake
Normal file
3
Tests/RunCMake/cmake_language/PrintTargetsNoRegex.cmake
Normal file
@@ -0,0 +1,3 @@
|
||||
add_library(mytarget INTERFACE)
|
||||
|
||||
cmake_language(PRINT_TARGETS)
|
||||
@@ -0,0 +1 @@
|
||||
1
|
||||
@@ -0,0 +1,2 @@
|
||||
CMake Error at PrintTargetsRegexError\.cmake:[0-9]+ \(cmake_language\):
|
||||
cmake_language REGEX regular expression "\[" cannot compile\.
|
||||
@@ -0,0 +1 @@
|
||||
cmake_language(PRINT_TARGETS REGEX "[")
|
||||
13
Tests/RunCMake/cmake_language/PrintTargetsScope-stdout.txt
Normal file
13
Tests/RunCMake/cmake_language/PrintTargetsScope-stdout.txt
Normal file
@@ -0,0 +1,13 @@
|
||||
.*--.*
|
||||
+All targets matching REGEX '\^zz_' \(case sensitive\):
|
||||
+zz_parent \(INTERFACE_LIBRARY\)
|
||||
|
||||
.*--.*
|
||||
+All targets matching REGEX '\^zz_' \(case sensitive\):
|
||||
+zz_child \(INTERFACE_LIBRARY\)
|
||||
+zz_parent \(INTERFACE_LIBRARY\)
|
||||
|
||||
.*--.*
|
||||
+All targets matching REGEX '\^zz_' \(case sensitive\):
|
||||
+zz_child \(INTERFACE_LIBRARY\)
|
||||
+zz_parent \(INTERFACE_LIBRARY\)
|
||||
9
Tests/RunCMake/cmake_language/PrintTargetsScope.cmake
Normal file
9
Tests/RunCMake/cmake_language/PrintTargetsScope.cmake
Normal file
@@ -0,0 +1,9 @@
|
||||
add_library(zz_parent INTERFACE)
|
||||
|
||||
# Before the subdirectory: only the parent target exists.
|
||||
cmake_language(PRINT_TARGETS REGEX "^zz_")
|
||||
|
||||
add_subdirectory(PrintTargetsScope)
|
||||
|
||||
# After: the child's target is visible too.
|
||||
cmake_language(PRINT_TARGETS REGEX "^zz_")
|
||||
@@ -0,0 +1,4 @@
|
||||
add_library(zz_child INTERFACE)
|
||||
|
||||
# The child sees its own target and the ancestor's.
|
||||
cmake_language(PRINT_TARGETS REGEX "^zz_")
|
||||
@@ -182,3 +182,18 @@ block()
|
||||
set(RunCMake_TEST_OPTIONS --trace-expand --trace-source=trace_expand_cli_wins.cmake)
|
||||
run_cmake(trace_expand_cli_wins)
|
||||
endblock()
|
||||
|
||||
# cmake_language(PRINT_TARGETS)
|
||||
run_cmake(PrintTargets)
|
||||
run_cmake(PrintTargetsMessagePrefix)
|
||||
run_cmake(PrintTargetsMutuallyExclusive)
|
||||
block()
|
||||
set(RunCMake_TEST_NOT_EXPECT_stdout "zz_alias")
|
||||
run_cmake(PrintTargetsAlias)
|
||||
endblock()
|
||||
run_cmake(PrintTargetsScope)
|
||||
run_cmake(PrintTargetsEmptyMatch)
|
||||
run_cmake(PrintTargetsRegexError)
|
||||
run_cmake(PrintTargetsNoRegex)
|
||||
run_cmake(PrintTargetsIgnoreCase)
|
||||
run_cmake(PrintTargetsIgnoreCaseRequiresRegex)
|
||||
|
||||
Reference in New Issue
Block a user