cmake_language: Add PRINT_PROPERTIES operation

Print CMake entities' properties in human-readable form for debugging.

Issue: #27513
This commit is contained in:
Tom Osika
2026-08-10 17:41:05 -04:00
parent 4eedd0fdb9
commit 2a2012e913
91 changed files with 1824 additions and 24 deletions
+176
View File
@@ -20,6 +20,16 @@ Synopsis
cmake_language(`PRINT_TARGETS`_ <filter>...)
cmake_language(`PRINT_VARIABLES`_
[{ ALL [<filter>...] | NAMED <vars>... }])
cmake_language(PRINT_PROPERTIES
`TARGETS <PRINT_PROPERTIES-TARGETS_>`__ <targets>...
<options>...
[{ ALL [<filter>] | NAMED <properties>... }])
cmake_language(PRINT_PROPERTIES
{ `SOURCES <PRINT_PROPERTIES-SOURCES_>`__ <sources>... |
`DIRECTORIES <PRINT_PROPERTIES-DIRECTORIES_>`__ <dirs>... |
`TESTS <PRINT_PROPERTIES-TESTS_>`__ <tests>... |
`CACHE_ENTRIES <PRINT_PROPERTIES-CACHE_ENTRIES_>`__ <entries>... }
NAMED <properties>...)
Introduction
^^^^^^^^^^^^
@@ -683,3 +693,169 @@ Gives::
Variables in scope at '/path/to/CMakeLists.txt' matching name '^MY_' (case sensitive):
MY_FLAG = "on"
CACHE{MY_PATH}:FILEPATH = "/some/path"
Printing Properties
^^^^^^^^^^^^^^^^^^^
.. versionadded:: 4.5
.. signature::
cmake_language(PRINT_PROPERTIES
TARGETS <targets>... <options>...
[{ ALL [<filter>] | NAMED <properties>... }])
:target: PRINT_PROPERTIES-TARGETS
Prints the values of properties for the specified targets.
The set of properties to print may be specified by one of:
``ALL [<filter>]`` (default)
Enumerates every property set on each named target, printing one entry
per property.
The optional ``<filter>`` may be one of:
``PROPERTY_NAME_REGEX <name-regex>``
Print properties whose name matches the given regular expression.
``PROPERTY_VALUE_REGEX <value-regex>``
Print properties whose value matches the given regular expression.
If no property survives the regex filters for a given target, a
``CMake Warning`` is emitted in place of that target's block.
``NAMED <properties>...``
Prints exactly the named properties on each entity, in the order given.
A property that is not set prints as ``<NOTFOUND>``.
The ``<options>...`` are:
``DEFERRED``
Defers the print to generation time rather than running during
configure. Useful when the properties of interest are populated by
later configure-time commands. On its own it does not change which
targets are visited - only the named targets are printed.
``FOLLOW_DEPENDENCIES``
Implies ``DEFERRED``. When set, the walk visits every target reachable
from each named target through any linkage - ``PUBLIC``, ``INTERFACE``,
``PRIVATE``, and :genex:`$<LINK_ONLY:...>` - and prints the requested
properties for each.
.. note::
``FOLLOW_DEPENDENCIES`` does not turn on generator-expression
evaluation for the property *values* this command prints.
The only place generator expression evaluation happens is internal:
on each visited target's link list (the effective ``LINK_LIBRARIES`` /
``INTERFACE_LINK_LIBRARIES``), evaluated solely to identify which
dependencies are reachable. Every printed property value,
including the link-list properties themselves, is emitted exactly
as stored, with generator expressions intact and unsubstituted.
This matches the behavior of :command:`get_target_property`.
.. signature::
cmake_language(PRINT_PROPERTIES SOURCES <sources>... NAMED <properties>...)
cmake_language(PRINT_PROPERTIES DIRECTORIES <dirs>... NAMED <properties>...)
cmake_language(PRINT_PROPERTIES TESTS <tests>... NAMED <properties>...)
cmake_language(PRINT_PROPERTIES CACHE_ENTRIES <entries>... NAMED <properties>...)
:target:
PRINT_PROPERTIES-SOURCES
PRINT_PROPERTIES-DIRECTORIES
PRINT_PROPERTIES-TESTS
PRINT_PROPERTIES-CACHE_ENTRIES
Prints the values of properties for the specified source files, directories,
tests, or cache entries. Exactly one scope keyword must be specified.
The set of properties to print must be specified by:
``NAMED <properties>...``
Prints exactly the named properties on each entity, in the order given.
A property that is not set prints as ``<NOTFOUND>``.
Printing Properties Examples
""""""""""""""""""""""""""""
Printing the ``LOCATION`` and ``INTERFACE_INCLUDE_DIRECTORIES`` properties for
both targets ``foo`` and ``bar``:
.. code-block:: cmake
cmake_language(
PRINT_PROPERTIES
TARGETS foo bar
NAMED LOCATION INTERFACE_INCLUDE_DIRECTORIES
)
Gives::
-- Printing properties...
Properties for TARGET foo:
foo.LOCATION = "/usr/lib/libfoo.so"
foo.INTERFACE_INCLUDE_DIRECTORIES = "/usr/include;/usr/include/foo"
Properties for TARGET bar:
bar.LOCATION = "/usr/lib/libbar.so"
bar.INTERFACE_INCLUDE_DIRECTORIES = "/usr/include;/usr/include/bar"
Printing all properties on a target, filtered to a couple of names:
.. code-block:: cmake
add_library(my_lib src.cpp)
cmake_language(
PRINT_PROPERTIES
TARGETS my_lib
ALL
PROPERTY_NAME_REGEX "AUTOMOC"
PROPERTY_VALUE_REGEX "(ON|OFF)"
)
Gives::
-- Printing properties...
All properties for TARGET my_lib matching name 'AUTOMOC' and value '(ON|OFF)':
my_lib.AUTOMOC_COMPILER_PREDEFINES = "ON"
my_lib.AUTOMOC_PATH_PREFIX = "OFF"
Deferring the print of one target's properties to generation time, without
visiting any dependencies:
.. code-block:: cmake
add_executable(myexe main.c)
cmake_language(
PRINT_PROPERTIES
TARGETS myexe
DEFERRED
NAMED SOURCES
)
Gives::
-- Printing properties...
Properties for TARGET myexe:
myexe.SOURCES = "main.c"
Printing a property on a target and every reachable dependency:
.. code-block:: cmake
add_library(leaflib STATIC src.c)
set_target_properties(leaflib PROPERTIES MY_PROP "leaf")
add_library(mylib STATIC src.c)
target_link_libraries(mylib PRIVATE leaflib)
set_target_properties(mylib PROPERTIES MY_PROP "mylib")
cmake_language(
PRINT_PROPERTIES
TARGETS mylib
FOLLOW_DEPENDENCIES
NAMED MY_PROP
)
Gives::
-- Printing properties...
Properties for TARGET mylib (and all reachable):
mylib.MY_PROP = "mylib"
leaflib.MY_PROP = "leaf"
@@ -0,0 +1,6 @@
print_all_properties
--------------------
* The :command:`cmake_language(PRINT_PROPERTIES)` command was added
to print CMake entities' properties in human-readable form for
debugging.
+658
View File
@@ -5,8 +5,10 @@
#include <algorithm>
#include <array>
#include <cstddef>
#include <initializer_list>
#include <map>
#include <memory>
#include <set>
#include <string>
#include <unordered_map>
#include <utility>
@@ -23,10 +25,15 @@
#include "cmDependencyProvider.h"
#include "cmExecutionStatus.h"
#include "cmExperimental.h"
#include "cmGeneratorTarget.h"
#include "cmGetPropertyCommand.h"
#include "cmGlobalGenerator.h"
#include "cmLinkItem.h"
#include "cmListFileCache.h"
#include "cmLocalGenerator.h"
#include "cmMakefile.h"
#include "cmMessageType.h" // IWYU pragma: keep
#include "cmPropertyMap.h"
#include "cmRange.h"
#include "cmState.h"
#include "cmStateSnapshot.h"
@@ -34,6 +41,8 @@
#include "cmStringAlgorithms.h"
#include "cmSystemTools.h"
#include "cmTarget.h"
#include "cmTargetPropertyComputer.h"
#include "cmTargetPropertyHelper.h"
#include "cmValue.h"
#include "cmake.h"
@@ -785,8 +794,649 @@ bool cmCMakeLanguageCommandPRINT_VARIABLES(
parsedArgs.CmakePrintVariables);
return true;
}
// Walks every target reachable from `root` through any linkage - PUBLIC,
// INTERFACE, PRIVATE, and `$<LINK_ONLY:>`-wrapped deps. At each level we
// union the link implementation and link interface, each queried with both
// UseTo::Compile and UseTo::Link, so deps that only appear under LINK_ONLY
// (which evaluates to empty under UseTo::Compile) still come through.
std::vector<cmGeneratorTarget const*> CollectDependentTargets(
cmGeneratorTarget const* root, std::string const& config)
{
std::vector<cmGeneratorTarget const*> deps;
std::set<cmGeneratorTarget const*> visited;
visited.insert(root);
std::vector<cmGeneratorTarget const*> queue;
queue.push_back(root);
while (!queue.empty()) {
cmGeneratorTarget const* cur = queue.back();
queue.pop_back();
auto visit = [&](std::vector<cmLinkItem> const& libs) {
for (cmLinkItem const& item : libs) {
cmGeneratorTarget const* dep = item.Target;
if (!dep || !visited.insert(dep).second) {
continue;
}
deps.push_back(dep);
queue.push_back(dep);
}
};
for (auto useTo : { cmGeneratorTarget::UseTo::Compile,
cmGeneratorTarget::UseTo::Link }) {
if (cmLinkImplementationLibraries const* impl =
cur->GetLinkImplementationLibraries(config, useTo)) {
visit(impl->Libraries);
}
if (cmLinkInterfaceLibraries const* iface =
cur->GetLinkInterfaceLibraries(config, root, useTo)) {
visit(iface->Libraries);
}
}
}
return deps;
}
// Append one property line to `out`:
// " <entityName>.<propertyName> = \"<value>\""
// A null `value` writes "<NOTFOUND>" instead of "= \"...\"".
void WritePropertyLine(std::string& out, std::string const& entityName,
std::string const& propertyName, cmValue value)
{
out += cmStrCat(" ", entityName, ".", propertyName);
if (value) {
out += cmStrCat(" = \"", *value, "\"");
} else {
out += " = <NOTFOUND>";
}
out += "\n";
}
enum class BlockKind
{
Explicit,
All,
};
enum class HeaderSuffix
{
None,
Reachable,
};
void EmitBlockHeader(
std::string& out, std::string const& entityName,
std::string const& entityType, BlockKind kind, HeaderSuffix suffix,
cm::optional<std::string> const& nameRegexStr = cm::nullopt,
cm::optional<std::string> const& valueRegexStr = cm::nullopt)
{
out +=
cmStrCat(" ", (kind == BlockKind::All ? "All properties" : "Properties"),
" for ", entityType, " ", entityName);
if (suffix == HeaderSuffix::Reachable) {
out += " (and all reachable)";
}
if (kind == BlockKind::All && (nameRegexStr || valueRegexStr)) {
out += " matching";
if (nameRegexStr) {
out += cmStrCat(" name '", *nameRegexStr, "'");
}
if (nameRegexStr && valueRegexStr) {
out += " and";
}
if (valueRegexStr) {
out += cmStrCat(" value '", *valueRegexStr, "'");
}
}
out += ":\n";
}
// Build the "no properties matched" warning text used when the regex filter
// wipes everything out for a target's ALL block. Returned without any
// "Warning:" prefix - callers feed it to IssueMessage, which adds the
// standard "CMake Warning at <file>:<line>" framing.
std::string EmptyMatchWarningMessage(
std::string const& entityName, std::string const& entityType,
cm::optional<std::string> const& nameRegexStr,
cm::optional<std::string> const& valueRegexStr)
{
std::string msg =
cmStrCat("No properties for ", entityType, " ", entityName, " matching");
if (nameRegexStr) {
msg += cmStrCat(" name '", *nameRegexStr, "'");
}
if (nameRegexStr && valueRegexStr) {
msg += " and";
}
if (valueRegexStr) {
msg += cmStrCat(" value '", *valueRegexStr, "'");
}
msg += " in cmake_language(PRINT_PROPERTIES ...).";
return msg;
}
// Entity kinds supported by cmake_language(PRINT_PROPERTIES).
enum class EntityKind
{
Target,
Source,
Test,
Directory,
Cache
};
// Display name used in PRINT_PROPERTIES output ("TARGET", "SOURCE", etc.).
char const* EntityTypeName(EntityKind kind)
{
switch (kind) {
case EntityKind::Target:
return "TARGET";
case EntityKind::Source:
return "SOURCE";
case EntityKind::Test:
return "TEST";
case EntityKind::Directory:
return "DIRECTORY";
case EntityKind::Cache:
return "CACHE";
}
return "";
}
// Dispatch a property lookup to the appropriate per-entity helper.
bool GetPropertyHelper(cmExecutionStatus& status, EntityKind kind,
std::string const& name,
std::string const& propertyName, cmValue& out)
{
switch (kind) {
case EntityKind::Target:
return GetPropertyCommand::LookupTargetProperty(status, name,
propertyName, out);
case EntityKind::Source:
return GetPropertyCommand::LookupSourceProperty(status, name,
propertyName, out);
case EntityKind::Test:
return GetPropertyCommand::LookupTestProperty(status, name, propertyName,
out);
case EntityKind::Directory:
return GetPropertyCommand::LookupDirectoryProperty(status, name,
propertyName, out);
case EntityKind::Cache:
return GetPropertyCommand::LookupCacheProperty(status, name,
propertyName, out);
}
return false;
}
// Emit one ALL-mode block: prints all properties matching the specified
// regexes for a head target (`targets[0]`) and optionally any reachable
// dependencies (`targets[1]` and on).
bool EmitAllPropertiesBlock(std::string& out, std::string const& headerName,
std::vector<cmTarget*> const& targets,
HeaderSuffix suffix,
cm::optional<cmsys::RegularExpression>& nameRegex,
cm::optional<cmsys::RegularExpression>& valueRegex,
cm::optional<std::string> const& nameRegexStr,
cm::optional<std::string> const& valueRegexStr)
{
std::string groupBuf;
bool groupEmitted = false;
for (cmTarget* t : targets) {
std::string const& rowName =
(t == targets.front()) ? headerName : t->GetName();
for (auto const& kv : t->GetExtendedProperties().GetList()) {
if (nameRegex && !nameRegex->find(kv.first)) {
continue;
}
if (valueRegex && !valueRegex->find(kv.second)) {
continue;
}
WritePropertyLine(groupBuf, rowName, kv.first, cmValue(kv.second));
groupEmitted = true;
}
}
if (!groupEmitted) {
return false;
}
EmitBlockHeader(out, headerName, "TARGET", BlockKind::All, suffix,
nameRegexStr, valueRegexStr);
out += groupBuf;
return true;
}
// Outcome of emitting a NAMED-mode block.
enum class NamedBlockResult
{
Failed, // a property lookup hard-failed
Emitted, // at least one property line was written
Empty, // every requested property was skipped; header suppressed
};
// Emit one block of user-specified properties for a head target (`targets[0]`)
// and optionally any reachable dependencies (`targets[1]` and on).
NamedBlockResult EmitNamedPropertiesBlock(
std::string& out, std::string const& headerName,
std::vector<cmTarget*> const& targets,
std::vector<std::string> const& namedProperties, HeaderSuffix suffix,
cmMakefile& mf)
{
bool headerWritten = false;
for (cmTarget* t : targets) {
std::string const& rowName =
(t == targets.front()) ? headerName : t->GetName();
for (std::string const& propName : namedProperties) {
// A computed location property may not be read from a non-imported
// target; skip it with a warning rather than triggering the getter's
// fatal error.
if (!t->IsImported() &&
cmTargetPropertyComputer::IsComputedLocationProperty(t->GetType(),
propName)) {
mf.IssueMessage(
MessageType::WARNING,
cmStrCat("The ", propName,
" property may not be read from non-imported target \"",
rowName, "\"; skipping."));
continue;
}
cmValue v;
if (cmGetTargetProperty(rowName, t, propName, mf, v) !=
cmGetTargetPropertyResult::Success) {
return NamedBlockResult::Failed;
}
if (!headerWritten) {
EmitBlockHeader(out, headerName, "TARGET", BlockKind::Explicit,
suffix);
headerWritten = true;
}
WritePropertyLine(out, rowName, propName, v);
}
}
return headerWritten ? NamedBlockResult::Emitted : NamedBlockResult::Empty;
}
bool PrintPropertiesConfigureTime(
cmExecutionStatus& status, std::vector<std::string> const& namedProperties,
bool all, std::vector<std::string> const& entityNames, EntityKind kind,
cm::optional<std::string> const& propertyNameRegexStr,
cm::optional<std::string> const& propertyValueRegexStr,
cm::optional<cmsys::RegularExpression>& propertyNameRegex,
cm::optional<cmsys::RegularExpression>& propertyValueRegex,
std::string const& messagePrefix)
{
cmMakefile& makefile = status.GetMakefile();
bool const hasRegex = propertyNameRegexStr || propertyValueRegexStr;
std::string out = messagePrefix;
bool anyEmitted = false;
for (auto const& entityName : entityNames) {
if (kind == EntityKind::Target) {
cmTarget* target = makefile.FindTargetToUse(entityName);
if (!target) {
out += cmStrCat("\n No such TARGET \"", entityName, "\" !\n\n");
anyEmitted = true;
continue;
}
if (all) {
bool const emitted = EmitAllPropertiesBlock(
out, entityName, { target }, HeaderSuffix::None, propertyNameRegex,
propertyValueRegex, propertyNameRegexStr, propertyValueRegexStr);
if (emitted) {
anyEmitted = true;
} else if (hasRegex) {
makefile.IssueMessage(MessageType::WARNING,
EmptyMatchWarningMessage(
entityName, "TARGET", propertyNameRegexStr,
propertyValueRegexStr));
}
} else {
NamedBlockResult const result = EmitNamedPropertiesBlock(
out, entityName, { target }, namedProperties, HeaderSuffix::None,
makefile);
if (result == NamedBlockResult::Failed) {
status.SetError(cmStrCat(
"failed to retrieve properties for TARGET \"", entityName, "\""));
return false;
}
if (result == NamedBlockResult::Emitted) {
anyEmitted = true;
}
}
} else {
EmitBlockHeader(out, entityName, EntityTypeName(kind),
BlockKind::Explicit, HeaderSuffix::None);
for (auto const& propertyName : namedProperties) {
cmValue v;
if (!GetPropertyHelper(status, kind, entityName, propertyName, v)) {
return false;
}
WritePropertyLine(out, entityName, propertyName, v);
}
anyEmitted = true;
}
}
if (anyEmitted) {
makefile.DisplayStatus(out, -1);
}
return true;
}
bool PrintTargetPropertiesDeferred(
std::vector<std::string> targetNames,
std::vector<std::string> namedProperties, bool all,
cm::optional<std::string> propertyNameRegexStr,
cm::optional<std::string> propertyValueRegexStr,
cm::optional<cmsys::RegularExpression> propertyNameRegex,
cm::optional<cmsys::RegularExpression> propertyValueRegex,
bool followDependencies, std::string messagePrefix,
cmExecutionStatus& status)
{
cmListFileBacktrace const bt = status.GetMakefile().GetBacktrace();
status.GetMakefile().AddGeneratorAction(
[targetNames, namedProperties, all, propertyNameRegexStr,
propertyValueRegexStr, propertyNameRegex, propertyValueRegex,
followDependencies, messagePrefix, bt](
cmLocalGenerator& lg, cmListFileBacktrace const& /*lambdaBt*/) mutable {
std::vector<std::string> const configs =
lg.GetMakefile()->GetGeneratorConfigs(cmMakefile::IncludeEmptyConfig);
std::string const config =
configs.empty() ? std::string() : configs.front();
bool const hasRegex = propertyNameRegexStr || propertyValueRegexStr;
cmake* cmakeInst = lg.GetMakefile()->GetCMakeInstance();
std::string out = messagePrefix;
bool anyEmitted = false;
for (std::string const& name : targetNames) {
cmGeneratorTarget* root = lg.FindGeneratorTargetToUse(name);
if (!root) {
out += cmStrCat("\n No such TARGET \"", name, "\" !\n\n");
anyEmitted = true;
continue;
}
std::vector<cmTarget*> targets;
targets.push_back(root->Target);
HeaderSuffix const suffix =
followDependencies ? HeaderSuffix::Reachable : HeaderSuffix::None;
if (followDependencies) {
for (cmGeneratorTarget const* dep :
CollectDependentTargets(root, config)) {
targets.push_back(dep->Target);
}
}
if (all) {
bool const emitted = EmitAllPropertiesBlock(
out, name, targets, suffix, propertyNameRegex, propertyValueRegex,
propertyNameRegexStr, propertyValueRegexStr);
if (emitted) {
anyEmitted = true;
} else if (hasRegex) {
cmakeInst->IssueMessage(
MessageType::WARNING,
EmptyMatchWarningMessage(name, "TARGET", propertyNameRegexStr,
propertyValueRegexStr),
bt);
}
} else {
NamedBlockResult const result = EmitNamedPropertiesBlock(
out, name, targets, namedProperties, suffix, *lg.GetMakefile());
if (result == NamedBlockResult::Failed) {
cmakeInst->IssueMessage(
MessageType::FATAL_ERROR,
cmStrCat("failed to retrieve properties for TARGET \"", name,
"\""),
bt);
return;
}
if (result == NamedBlockResult::Emitted) {
anyEmitted = true;
}
}
}
if (anyEmitted) {
lg.GetMakefile()->DisplayStatus(out, -1);
}
},
cmMakefile::GeneratorActionWhen::AfterGeneratorTargets);
return true;
}
bool cmCMakeLanguageCommandPRINT_PROPERTIES(
std::vector<std::string> const& args, cmExecutionStatus& status)
{
struct PrintPropertiesArg : public ArgumentParser::ParseResult
{
bool All = false;
ArgumentParser::NonEmpty<std::vector<std::string>> Named;
cm::optional<std::string> PropertyNameRegex;
cm::optional<std::string> PropertyValueRegex;
// Internal: set by the deprecated cmake_print_properties() module wrapper
// to select the historical leading-blank-line output instead of the
// banner. Not part of the public interface.
bool CmakePrintProperties = false;
};
auto ArgsParser =
cmArgumentParser<PrintPropertiesArg>()
.Bind("ALL"_s, &PrintPropertiesArg::All)
.Bind("NAMED"_s, &PrintPropertiesArg::Named)
.Bind("PROPERTY_NAME_REGEX"_s, &PrintPropertiesArg::PropertyNameRegex)
.Bind("PROPERTY_VALUE_REGEX"_s, &PrintPropertiesArg::PropertyValueRegex)
.Bind("__CMAKE_PRINT_PROPERTIES"_s,
&PrintPropertiesArg::CmakePrintProperties);
std::vector<std::string> unparsed;
auto parsedArgs = ArgsParser.Parse(args, &unparsed);
if (unparsed.empty()) {
return FatalError(status,
cmStrCat("mode keyword missing in ",
"cmake_language(PRINT_PROPERTIES) call, ",
"there must be exactly one of TARGETS SOURCES "
"TESTS DIRECTORIES CACHE_ENTRIES"));
}
if (parsedArgs.MaybeReportError(status.GetMakefile())) {
cmSystemTools::SetFatalErrorOccurred();
return true;
}
// Second parse args to get the mode
struct PrintPropertiesModesArgs : public ArgumentParser::ParseResult
{
ArgumentParser::MaybeEmpty<std::vector<std::string>> Targets;
ArgumentParser::MaybeEmpty<std::vector<std::string>> Sources;
ArgumentParser::MaybeEmpty<std::vector<std::string>> Tests;
ArgumentParser::MaybeEmpty<std::vector<std::string>> Directories;
ArgumentParser::MaybeEmpty<std::vector<std::string>> CacheEntries;
bool Deferred = false;
bool FollowDependencies = false;
};
auto const ArgsParserMode =
cmArgumentParser<PrintPropertiesModesArgs>()
.Bind("TARGETS"_s, &PrintPropertiesModesArgs::Targets)
.Bind("SOURCES"_s, &PrintPropertiesModesArgs::Sources)
.Bind("TESTS"_s, &PrintPropertiesModesArgs::Tests)
.Bind("DIRECTORIES"_s, &PrintPropertiesModesArgs::Directories)
.Bind("CACHE_ENTRIES"_s, &PrintPropertiesModesArgs::CacheEntries)
.Bind("DEFERRED"_s, &PrintPropertiesModesArgs::Deferred)
.Bind("FOLLOW_DEPENDENCIES"_s,
&PrintPropertiesModesArgs::FollowDependencies);
std::vector<std::string> modeArgs = unparsed;
unparsed.clear();
auto parsedArgsMode = ArgsParserMode.Parse(modeArgs, &unparsed);
if (!unparsed.empty()) {
return FatalError(
status, cmStrCat("Unknown keywords: \"", cmJoin(unparsed, " "), "\""));
}
if (parsedArgsMode.MaybeReportError(status.GetMakefile())) {
cmSystemTools::SetFatalErrorOccurred();
return true;
}
std::vector<std::string> modes;
std::vector<std::string> items;
EntityKind kind = EntityKind::Target;
if (!parsedArgsMode.Targets.empty()) {
modes.push_back("TARGETS");
items = parsedArgsMode.Targets;
kind = EntityKind::Target;
}
if (!parsedArgsMode.Sources.empty()) {
modes.push_back("SOURCES");
items = parsedArgsMode.Sources;
kind = EntityKind::Source;
}
if (!parsedArgsMode.Tests.empty()) {
modes.push_back("TESTS");
items = parsedArgsMode.Tests;
kind = EntityKind::Test;
}
if (!parsedArgsMode.Directories.empty()) {
modes.push_back("DIRECTORIES");
items = parsedArgsMode.Directories;
kind = EntityKind::Directory;
}
if (!parsedArgsMode.CacheEntries.empty()) {
modes.push_back("CACHE_ENTRIES");
items = parsedArgsMode.CacheEntries;
kind = EntityKind::Cache;
}
if (modes.empty()) {
return FatalError(status,
cmStrCat("mode keyword missing in ",
"cmake_language(PRINT_PROPERTIES) call, ",
"there must be exactly one of TARGETS SOURCES "
"TESTS DIRECTORIES CACHE_ENTRIES"));
}
if (modes.size() > 1) {
return FatalError(status,
cmStrCat("multiple mode keywords used in ",
"cmake_language(PRINT_PROPERTIES) call, ",
"there must be exactly one of TARGETS SOURCES "
"TESTS DIRECTORIES CACHE_ENTRIES."));
}
std::string const mode = modes[0];
bool const isTargets = (mode == "TARGETS");
bool const hasNamed = !parsedArgs.Named.empty();
bool const hasRegex =
parsedArgs.PropertyNameRegex || parsedArgs.PropertyValueRegex;
if (!isTargets) {
if (parsedArgs.All) {
return FatalError(status,
cmStrCat("ALL keyword in ",
"cmake_language(PRINT_PROPERTIES) call ",
"is only valid with the TARGETS scope."));
}
if (hasRegex) {
return FatalError(
status,
cmStrCat("PROPERTY_NAME_REGEX and PROPERTY_VALUE_REGEX in ",
"cmake_language(PRINT_PROPERTIES) call ",
"are only valid with the TARGETS scope and ALL."));
}
if (parsedArgsMode.Deferred) {
return FatalError(status,
cmStrCat("DEFERRED keyword in ",
"cmake_language(PRINT_PROPERTIES) call ",
"is only valid with the TARGETS scope."));
}
if (parsedArgsMode.FollowDependencies) {
return FatalError(status,
cmStrCat("FOLLOW_DEPENDENCIES keyword in ",
"cmake_language(PRINT_PROPERTIES) call ",
"is only valid with the TARGETS scope."));
}
if (!hasNamed) {
return FatalError(status,
cmStrCat("NAMED keyword missing in ",
"cmake_language(PRINT_PROPERTIES) call ",
"with ", mode, " scope."));
}
} else {
// ALL and NAMED are mutually exclusive on TARGETS.
if (parsedArgs.All && hasNamed) {
return FatalError(status,
cmStrCat("ALL and NAMED keywords in ",
"cmake_language(PRINT_PROPERTIES) call ",
"are mutually exclusive."));
}
// Regex filters require ALL - explicit or implicit. Combining regex
// with NAMED is an error.
if (hasNamed && hasRegex) {
return FatalError(
status,
cmStrCat("PROPERTY_NAME_REGEX and PROPERTY_VALUE_REGEX in ",
"cmake_language(PRINT_PROPERTIES) call ",
"are only valid with ALL, not NAMED."));
}
}
// FOLLOW_DEPENDENCIES implies DEFERRED.
if (parsedArgsMode.FollowDependencies) {
parsedArgsMode.Deferred = true;
}
// Default to ALL when neither ALL nor NAMED is given (TARGETS only - the
// non-TARGETS path already rejected this combination above).
bool const all = parsedArgs.All || !hasNamed;
// __CMAKE_PRINT_PROPERTIES marks the deprecated cmake_print_properties()
// wrapper, which is always NAMED; reject it in ALL enumeration mode.
if (all && parsedArgs.CmakePrintProperties) {
return FatalError(status,
cmStrCat("__CMAKE_PRINT_PROPERTIES in ",
"cmake_language(PRINT_PROPERTIES) call ",
"is only valid with NAMED."));
}
// Compile regexes once up front so syntax errors are reported here rather
// than from inside helpers (in particular, from the deferred lambda at
// generate time).
cm::optional<cmsys::RegularExpression> propertyNameRegex;
cm::optional<cmsys::RegularExpression> propertyValueRegex;
if (parsedArgs.PropertyNameRegex) {
cmsys::RegularExpression re;
if (!re.compile(*parsedArgs.PropertyNameRegex)) {
return FatalError(status,
cmStrCat("PROPERTY_NAME_REGEX regular expression \"",
*parsedArgs.PropertyNameRegex,
"\" cannot compile."));
}
propertyNameRegex = std::move(re);
}
if (parsedArgs.PropertyValueRegex) {
cmsys::RegularExpression re;
if (!re.compile(*parsedArgs.PropertyValueRegex)) {
return FatalError(status,
cmStrCat("PROPERTY_VALUE_REGEX regular expression \"",
*parsedArgs.PropertyValueRegex,
"\" cannot compile."));
}
propertyValueRegex = std::move(re);
}
// The message opens with a leading banner line. The internal
// __CMAKE_PRINT_PROPERTIES marker (not part of the public interface) selects
// the legacy format instead; cmake_print_properties() sets it to reproduce
// its historical leading blank line.
std::string const messagePrefix = parsedArgs.CmakePrintProperties
? std::string("\n")
: std::string("Printing properties...\n");
if (parsedArgsMode.Deferred) {
return PrintTargetPropertiesDeferred(
items, parsedArgs.Named, all, parsedArgs.PropertyNameRegex,
parsedArgs.PropertyValueRegex, std::move(propertyNameRegex),
std::move(propertyValueRegex), parsedArgsMode.FollowDependencies,
messagePrefix, status);
}
return PrintPropertiesConfigureTime(
status, parsedArgs.Named, all, items, kind, parsedArgs.PropertyNameRegex,
parsedArgs.PropertyValueRegex, propertyNameRegex, propertyValueRegex,
messagePrefix);
}
}
bool cmCMakeLanguageCommand(std::vector<cmListFileArgument> const& args,
cmExecutionStatus& status)
{
@@ -1007,5 +1657,13 @@ bool cmCMakeLanguageCommand(std::vector<cmListFileArgument> const& args,
FatalError(status, "TRACE OFF request without a corresponding TRACE ON");
}
if (expArgs[expArg] == "PRINT_PROPERTIES") {
++expArg;
finishArgs();
std::vector<std::string> const printPropertyArgs(expArgs.begin() + expArg,
expArgs.end());
return cmCMakeLanguageCommandPRINT_PROPERTIES(printPropertyArgs, status);
}
return FatalError(status, "called with unknown meta-operation");
}
+1 -2
View File
@@ -4,9 +4,8 @@
#include <string>
#include "cmValue.h"
class cmMakefile;
class cmValue;
enum class cmGetDirectoryPropertyResult
{
+20 -5
View File
@@ -2,11 +2,6 @@
file LICENSE.rst or https://cmake.org/licensing for details. */
#include "cmGetPropertyCommand.h"
#include <cstddef>
#include <cm/string_view>
#include <cmext/string_view>
#include "cmDirectoryPropertyHelper.h"
#include "cmExecutionStatus.h"
#include "cmFileSet.h"
@@ -510,6 +505,17 @@ bool LookupSourceProperty(
return false;
}
bool LookupSourceProperty(cmExecutionStatus& status, std::string const& name,
std::string const& propertyName, cmValue& out)
{
std::vector<std::string> noDirectories;
std::vector<std::string> noTargetDirectories;
return LookupSourceProperty(status, name, propertyName,
/*sourceFileDirectoryOptionEnabled=*/false,
/*sourceFileTargetOptionEnabled=*/false,
noDirectories, noTargetDirectories, out);
}
bool LookupTestProperty(cmExecutionStatus& status, std::string const& name,
std::string const& propertyName,
bool testDirectoryOptionEnabled,
@@ -532,6 +538,15 @@ bool LookupTestProperty(cmExecutionStatus& status, std::string const& name,
return false;
}
bool LookupTestProperty(cmExecutionStatus& status, std::string const& name,
std::string const& propertyName, cmValue& out)
{
std::string noDirectory;
return LookupTestProperty(status, name, propertyName,
/*testDirectoryOptionEnabled=*/false, noDirectory,
out);
}
bool LookupCacheProperty(cmExecutionStatus& status, std::string const& name,
std::string const& propertyName, cmValue& out)
{
+9 -2
View File
@@ -7,9 +7,8 @@
#include <string>
#include <vector>
#include "cmValue.h"
class cmExecutionStatus;
class cmValue;
namespace GetPropertyCommand {
@@ -33,11 +32,19 @@ bool LookupSourceProperty(
std::vector<std::string>& sourceFileDirectories,
std::vector<std::string>& sourceFileTargetDirectories, cmValue& out);
// Convenience overload: no DIRECTORY / TARGET_DIRECTORY scoping.
bool LookupSourceProperty(cmExecutionStatus& status, std::string const& name,
std::string const& propertyName, cmValue& out);
bool LookupTestProperty(cmExecutionStatus& status, std::string const& name,
std::string const& propertyName,
bool testDirectoryOptionEnabled,
std::string& testDirectory, cmValue& out);
// Convenience overload: no DIRECTORY scoping.
bool LookupTestProperty(cmExecutionStatus& status, std::string const& name,
std::string const& propertyName, cmValue& out);
bool LookupCacheProperty(cmExecutionStatus& status, std::string const& name,
std::string const& propertyName, cmValue& out);
+1 -2
View File
@@ -5,9 +5,8 @@
#include <string>
#include <vector>
#include "cmValue.h"
class cmExecutionStatus;
class cmValue;
enum class cmGetSourceFilePropertyResult
{
+82 -7
View File
@@ -163,6 +163,11 @@ struct FileSetType
void AddFileSet(std::string const& name, cm::FileSetMetadata::Visibility vis,
cmListFileBacktrace bt);
// We recompute this every time since some of the property
// names depend on names of the file sets
cmPropertyMap GetProperties(cmTarget const* tgt,
cmTargetInternals const* impl) const;
};
struct UsageRequirementProperty
@@ -874,6 +879,43 @@ void FileSetType::AddFileSet(std::string const& name,
}
}
cmPropertyMap FileSetType::GetProperties(cmTarget const* tgt,
cmTargetInternals const* impl) const
{
std::set<std::string> propNames{ std::string(this->DefaultDirectoryProperty),
std::string(this->DefaultPathProperty),
std::string(this->SelfEntries.PropertyName),
std::string(
this->InterfaceEntries.PropertyName) };
for (auto const& entry : this->SelfEntries.Entries) {
std::string directoryPropertyName =
cmStrCat(this->DirectoryPrefix, entry.Value);
std::string pathPropertyName = cmStrCat(this->PathPrefix, entry.Value);
propNames.emplace(directoryPropertyName);
propNames.emplace(pathPropertyName);
}
for (auto const& entry : this->InterfaceEntries.Entries) {
std::string directoryPropertyName =
cmStrCat(this->DirectoryPrefix, entry.Value);
std::string pathPropertyName = cmStrCat(this->PathPrefix, entry.Value);
propNames.emplace(directoryPropertyName);
propNames.emplace(pathPropertyName);
}
cmPropertyMap propertyMap;
for (std::string const& prop : propNames) {
auto value = this->ReadProperties(tgt, impl, prop);
if (value.first) {
propertyMap.SetProperty(prop, value.second);
}
}
return propertyMap;
}
template <typename ValueType>
bool UsageRequirementProperty::Write(
cmTargetInternals const* impl, cm::optional<cmListFileBacktrace> const& bt,
@@ -2669,13 +2711,7 @@ void cmTarget::CheckProperty(std::string const& prop,
}
}
cmValue cmTarget::GetComputedProperty(std::string const& prop,
cmMakefile& mf) const
{
return cmTargetPropertyComputer::GetProperty(this, prop, mf);
}
cmValue cmTarget::GetProperty(std::string const& prop) const
std::unordered_set<std::string> const& cmTarget::GetSpecialPropertyNames()
{
static std::unordered_set<std::string> const specialProps{
propC_STANDARD,
@@ -2710,6 +2746,18 @@ cmValue cmTarget::GetProperty(std::string const& prop) const
propIMPORTED_CXX_MODULES_COMPILE_OPTIONS,
propIMPORTED_CXX_MODULES_LINK_LIBRARIES,
};
return specialProps;
}
cmValue cmTarget::GetComputedProperty(std::string const& prop,
cmMakefile& mf) const
{
return cmTargetPropertyComputer::GetProperty(this, prop, mf);
}
cmValue cmTarget::GetProperty(std::string const& prop) const
{
auto const& specialProps = cmTarget::GetSpecialPropertyNames();
if (specialProps.count(prop)) {
if (prop == propC_STANDARD || prop == propCXX_STANDARD ||
prop == propCUDA_STANDARD || prop == propHIP_STANDARD ||
@@ -2867,6 +2915,33 @@ cmPropertyMap const& cmTarget::GetDirectProperties() const
return this->impl->Properties;
}
cmPropertyMap cmTarget::GetExtendedProperties() const
{
// Get properties in the base property map
cmPropertyMap pm = this->impl->Properties;
// Get special properties
auto const& specialProps = cmTarget::GetSpecialPropertyNames();
for (auto const& propName : specialProps) {
cmValue propValue = this->GetProperty(propName);
if (propValue) {
pm.SetProperty(propName, propValue);
}
}
// Get fileset properties
for (auto const& fileSetType : this->impl->FileSetTypes) {
cmPropertyMap fileSetProperties =
fileSetType.second.GetProperties(this, this->impl.get());
auto fileSetPropertiesList = fileSetProperties.GetList();
for (auto const& propPair : fileSetPropertiesList) {
pm.SetProperty(propPair.first, propPair.second);
}
}
return pm;
}
bool cmTarget::IsDLLPlatform() const
{
return this->impl->IsDLLPlatform;
+7
View File
@@ -9,6 +9,7 @@
#include <memory>
#include <set>
#include <string>
#include <unordered_set>
#include <utility>
#include <vector>
@@ -232,9 +233,15 @@ public:
std::string const& GetSafeProperty(std::string const& prop) const;
bool GetPropertyAsBool(std::string const& prop) const;
void CheckProperty(std::string const& prop, cmMakefile* context) const;
static std::unordered_set<std::string> const& GetSpecialPropertyNames();
cmValue GetComputedProperty(std::string const& prop, cmMakefile& mf) const;
//! Get properties set directly on this target (no special/computed/chained)
cmPropertyMap const& GetDirectProperties() const;
/**
* Get the properties in the property map plus
* special properties, fileset properties, etc.
*/
cmPropertyMap GetExtendedProperties() const;
//! Return whether or not the target is for a DLL platform.
bool IsDLLPlatform() const;
-2
View File
@@ -2,8 +2,6 @@
file LICENSE.rst or https://cmake.org/licensing for details. */
#include "cmTargetPropertyHelper.h"
#include <utility>
#include "cmGlobalGenerator.h"
#include "cmMakefile.h"
#include "cmTarget.h"
-2
View File
@@ -4,8 +4,6 @@
#include <string>
#include "cmValue.h"
class cmMakefile;
class cmTarget;
class cmValue;
+1 -2
View File
@@ -4,9 +4,8 @@
#include <string>
#include "cmValue.h"
class cmExecutionStatus;
class cmValue;
enum class cmGetTestPropertyResult
{
@@ -0,0 +1,20 @@
.*--.*
+Properties for TARGET mylib:
+mylib\.TYPE = "STATIC_LIBRARY"
+mylib\.NAME = "mylib"
--.*
+Printing all properties for TARGET mylib matching name '\^MY_PROP_' and value '\^alpha\$':
+mylib\.MY_PROP_X = "alpha"
-- Configuring done .*
--.*
+Printing all properties for TARGET mylib \(and all reachable\) matching name '\^MY_PROP_' and value '\^alpha\$':
+mylib\.MY_PROP_X = "alpha"
+mydep\.MY_PROP_X = "alpha"
--.*
+Properties for TARGET mylib:
+mylib\.TYPE = "STATIC_LIBRARY"
-- Generating done .*
@@ -0,0 +1,49 @@
include(CMakePrintHelpers)
enable_language(C)
file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/stub.c" "void stub(void) {}\n")
add_library(mydep STATIC "${CMAKE_CURRENT_BINARY_DIR}/stub.c")
set_target_properties(mydep PROPERTIES
MY_PROP_X "alpha"
MY_PROP_Y "beta"
OTHER_PROP "alpha"
)
add_library(mylib STATIC "${CMAKE_CURRENT_BINARY_DIR}/stub.c")
target_link_libraries(mylib PRIVATE mydep)
set_target_properties(mylib PROPERTIES
MY_PROP_X "alpha"
MY_PROP_Y "beta"
OTHER_PROP "alpha"
)
# (1) configure-time, specific properties — exercises forwarding of TARGETS
# + PROPERTIES <names>.
cmake_print_properties(TARGETS mylib PROPERTIES TYPE NAME)
# (2) configure-time, ALL + both regex keywords — exercises forwarding of
# PROPERTY_NAME_REGEX and PROPERTY_VALUE_REGEX together.
cmake_print_properties(
TARGETS mylib
PROPERTIES ALL
PROPERTY_NAME_REGEX "^MY_PROP_"
PROPERTY_VALUE_REGEX "^alpha$"
)
# (3) DEFERRED + FOLLOW_DEPENDENCIES + ALL + both regexes — exercises
# forwarding of every new keyword at once, and verifies the walker
# actually reaches the PRIVATE dep at generate time (mydep must appear
# in the output, not just mylib).
cmake_print_properties(
TARGETS mylib
DEFERRED
FOLLOW_DEPENDENCIES
PROPERTIES ALL
PROPERTY_NAME_REGEX "^MY_PROP_"
PROPERTY_VALUE_REGEX "^alpha$"
)
# (4) DEFERRED alone + specific name — exercises DEFERRED forwarding by
# itself (no dependency walk; mydep must NOT appear).
cmake_print_properties(TARGETS mylib DEFERRED PROPERTIES TYPE)
@@ -0,0 +1,15 @@
-- Printing properties\.\.\.
+Properties for TARGET nothing:
+nothing\.LINKER_LANGUAGE = <NOTFOUND>
+nothing\.TYPE = \"STATIC_LIBRARY\"
+Properties for TARGET something:
+something\.LINKER_LANGUAGE = <NOTFOUND>
+something\.TYPE = \"EXECUTABLE\"
-- Printing properties\.\.\.
+Properties for SOURCE nothing\.c:
+nothing\.c\.COMPILE_DEFINITIONS = <NOTFOUND>
+nothing\.c\.LANGUAGE = \"C\"
+Properties for SOURCE something\.c:
+something\.c\.COMPILE_DEFINITIONS = \"SOMETHING=1\"
+something\.c\.LANGUAGE = \"C\"
@@ -0,0 +1,26 @@
enable_language(C)
set_property(SOURCE nothing.c PROPERTY LANGUAGE C)
set_property(SOURCE something.c PROPERTY
COMPILE_DEFINITIONS SOMETHING=1)
add_library(nothing STATIC nothing.c nothing.h)
add_executable(something something.c something.h)
target_link_libraries(something PUBLIC nothing)
cmake_language(
PRINT_PROPERTIES
TARGETS nothing something
NAMED
LINKER_LANGUAGE
TYPE
)
cmake_language(
PRINT_PROPERTIES
SOURCES nothing.c something.c
NAMED
COMPILE_DEFINITIONS
LANGUAGE
)
@@ -0,0 +1,14 @@
-- Printing properties\.\.\.
+All properties for TARGET mylib:
.* +mylib.HEADER_DIRS = \"[^\"]*/Tests/RunCMake/cmake_language\"
.* +mylib.HEADER_DIRS_HEADERS = \"[^\"]*/Tests/RunCMake/cmake_language\"
.* +mylib.HEADER_SET = \"[^\"]*/Tests/RunCMake/cmake_language/nothing.h\"
.* +mylib.HEADER_SETS = \"HEADERS\"
.* +mylib.HEADER_SET_HEADERS = \"[^\"]*/Tests/RunCMake/cmake_language/nothing.h\"
.* +mylib.IMPORTED = \"FALSE\"
.* +mylib.INCLUDE_DIRECTORIES = \"\$\<BUILD_INTERFACE:[^\"]*/Tests/RunCMake/cmake_language\>\"
.* +mylib.INTERFACE_HEADER_SETS = \"HEADERS\"
.* +mylib.INTERFACE_INCLUDE_DIRECTORIES = \"\$\<BUILD_INTERFACE:[^\"]*/Tests/RunCMake/cmake_language\>\"
.* +mylib.NAME = \"mylib\"
.* +mylib.SOURCES = \"nothing.c\"
.* +mylib.TYPE = \"STATIC_LIBRARY\"
@@ -0,0 +1,22 @@
enable_language(C)
add_library(mylib STATIC nothing.c)
target_sources(mylib
PUBLIC
FILE_SET HEADERS
FILES nothing.h
)
add_executable(something something.c something.h)
target_link_libraries(something PUBLIC nothing)
# Printing all for library. To reduce maintenance burden, we only pin a few
# properties in the expected output instead of listing every property reported
# by ALL.
cmake_language(
PRINT_PROPERTIES
TARGETS mylib
ALL
)
@@ -0,0 +1,3 @@
^CMake Error at PrintPropertiesAllAndNamed\.cmake:[0-9]+ \(cmake_language\):
cmake_language ALL and NAMED keywords in cmake_language\(PRINT_PROPERTIES\)
call are mutually exclusive\.
@@ -0,0 +1,10 @@
enable_language(C)
add_library(mylib STATIC nothing.c)
# ALL and NAMED are mutually exclusive.
cmake_language(
PRINT_PROPERTIES
TARGETS mylib
ALL
NAMED MY_PROP
)
@@ -0,0 +1,7 @@
-- Configuring done .*
-- Printing properties\.\.\.
+All properties for TARGET mylib \(and all reachable\) matching name '\^MY_' and value 'alpha':
+mylib\.MY_PROP = "alpha-mylib"
+leaflib\.MY_PROP = "alpha-leaf"
-- Generating done .*
@@ -0,0 +1,29 @@
enable_language(C)
add_library(leaflib STATIC nothing.c)
set_target_properties(leaflib PROPERTIES
MY_PROP "alpha-leaf"
MY_OTHER "beta-leaf"
OTHER_PROP "alpha-other"
)
add_library(mylib STATIC nothing.c)
target_link_libraries(mylib PUBLIC leaflib)
set_target_properties(mylib PROPERTIES
MY_PROP "alpha-mylib"
MY_OTHER "beta-mylib"
OTHER_PROP "alpha-other"
)
# Only properties whose name starts with MY_ AND whose value contains
# "alpha" should print: MY_PROP on both targets.
# MY_OTHER fails the value regex; OTHER_PROP fails the name regex.
cmake_language(
PRINT_PROPERTIES
TARGETS mylib
DEFERRED
FOLLOW_DEPENDENCIES
ALL
PROPERTY_NAME_REGEX "^MY_"
PROPERTY_VALUE_REGEX "alpha"
)
@@ -0,0 +1,13 @@
-- Configuring done .*
-- Printing properties\.\.\.
+All properties for TARGET mylib \(and all reachable\) matching name '\^MY_PROP':
+mylib\.MY_PROP = "mylib"
+mylib\.MY_PROP_2 = "mylib2"
+intermediate\.MY_PROP = "intermediate"
+intermediate\.MY_PROP_2 = "intermediate2"
+leaflib\.MY_PROP = "leaf"
+leaflib\.MY_PROP_2 = "leaf2"
+linkonlylib\.MY_PROP = "linkonly"
+linkonlylib\.MY_PROP_2 = "linkonly2"
-- Generating done .*
@@ -0,0 +1,42 @@
enable_language(C)
add_library(leaflib STATIC nothing.c)
set_target_properties(leaflib PROPERTIES
MY_PROP "leaf"
MY_PROP_2 "leaf2"
)
add_library(linkonlylib STATIC nothing.c)
set_target_properties(linkonlylib PROPERTIES
MY_PROP "linkonly"
MY_PROP_2 "linkonly2"
)
add_library(intermediate STATIC nothing.c)
target_link_libraries(intermediate
PRIVATE leaflib
INTERFACE $<LINK_ONLY:linkonlylib>
)
set_target_properties(intermediate PROPERTIES
MY_PROP "intermediate"
MY_PROP_2 "intermediate2"
)
add_library(mylib STATIC nothing.c)
target_link_libraries(mylib PUBLIC intermediate)
set_target_properties(mylib PROPERTIES
MY_PROP "mylib"
MY_PROP_2 "mylib2"
)
# ALL + FOLLOW_DEPENDENCIES with a tight name regex pins the unified
# printer's ALL branch: each reachable target's matching properties show
# up in walk order, covering PRIVATE and $<LINK_ONLY:> traversal.
cmake_language(
PRINT_PROPERTIES
TARGETS mylib
DEFERRED
FOLLOW_DEPENDENCIES
ALL
PROPERTY_NAME_REGEX "^MY_PROP"
)
@@ -0,0 +1,3 @@
^CMake Error at PrintPropertiesAllRequiresTargets\.cmake:[0-9]+ \(cmake_language\):
cmake_language ALL keyword in cmake_language\(PRINT_PROPERTIES\) call is only
valid with the TARGETS scope\.
@@ -0,0 +1,6 @@
# ALL is only valid with the TARGETS scope.
cmake_language(
PRINT_PROPERTIES
SOURCES nothing.c
ALL
)
@@ -0,0 +1,9 @@
-- Printing properties\.\.\.
Properties for CACHE MY_CACHE_A:
MY_CACHE_A\.TYPE = "STRING"
MY_CACHE_A\.HELPSTRING = "help A"
MY_CACHE_A\.NOT_SET = <NOTFOUND>
Properties for CACHE MY_CACHE_B:
MY_CACHE_B\.TYPE = "PATH"
MY_CACHE_B\.HELPSTRING = "help B"
MY_CACHE_B\.NOT_SET = <NOTFOUND>
@@ -0,0 +1,11 @@
set(MY_CACHE_A "a_val" CACHE STRING "help A")
set(MY_CACHE_B "b_val" CACHE PATH "help B")
cmake_language(
PRINT_PROPERTIES
CACHE_ENTRIES MY_CACHE_A MY_CACHE_B
NAMED
TYPE
HELPSTRING
NOT_SET
)
@@ -0,0 +1,7 @@
-- Configuring done .*
-- Printing properties\.\.\.
+Properties for TARGET mylib \(and all reachable\):
+mylib\.MY_PROP = "mylib"
+debug_dep\.MY_PROP = "debug"
-- Generating done .*
@@ -0,0 +1,25 @@
enable_language(C)
add_library(debug_dep STATIC nothing.c)
set_target_properties(debug_dep PROPERTIES MY_PROP "debug")
add_library(release_dep STATIC nothing.c)
set_target_properties(release_dep PROPERTIES MY_PROP "release")
add_library(mylib STATIC nothing.c)
target_link_libraries(mylib PRIVATE
$<$<CONFIG:Debug>:debug_dep>
$<$<CONFIG:Release>:release_dep>
)
set_target_properties(mylib PROPERTIES MY_PROP "mylib")
# Driven with -DCMAKE_BUILD_TYPE=Debug - only debug_dep should be reached
# by the walker, since the $<CONFIG:Release> arm evaluates to empty under
# Debug.
cmake_language(
PRINT_PROPERTIES
TARGETS mylib
DEFERRED
FOLLOW_DEPENDENCIES
NAMED MY_PROP
)
@@ -0,0 +1,6 @@
-- Configuring done .*
-- Printing properties\.\.\.
+Properties for TARGET mylib:
+mylib\.MY_PROP = "mylib_value"
-- Generating done .*
@@ -0,0 +1,11 @@
enable_language(C)
add_library(mylib STATIC nothing.c)
set_target_properties(mylib PROPERTIES MY_PROP "mylib_value")
cmake_language(
PRINT_PROPERTIES
TARGETS mylib
DEFERRED
NAMED MY_PROP
)
@@ -0,0 +1,3 @@
^CMake Error at PrintPropertiesDeferredRequiresTargets\.cmake:[0-9]+ \(cmake_language\):
cmake_language DEFERRED keyword in cmake_language\(PRINT_PROPERTIES\) call is
only valid with the TARGETS scope\.
@@ -0,0 +1,8 @@
# DEFERRED is only meaningful with TARGETS - pairing it with a non-TARGETS
# scope must produce a fatal error.
cmake_language(
PRINT_PROPERTIES
SOURCES nothing.c
DEFERRED
NAMED LANGUAGE
)
@@ -0,0 +1,9 @@
-- Printing properties\.\.\.
Properties for DIRECTORY \.:
\.\.MY_PROP = "top_val"
\.\.LABELS = "top_label"
\.\.NOT_SET = <NOTFOUND>
Properties for DIRECTORY PrintPropertiesDirectories-sub:
PrintPropertiesDirectories-sub\.MY_PROP = "sub_val"
PrintPropertiesDirectories-sub\.LABELS = "sub_label"
PrintPropertiesDirectories-sub\.NOT_SET = <NOTFOUND>
@@ -0,0 +1 @@
set_directory_properties(PROPERTIES MY_PROP "sub_val" LABELS "sub_label")
@@ -0,0 +1,11 @@
set_directory_properties(PROPERTIES MY_PROP "top_val" LABELS "top_label")
add_subdirectory(PrintPropertiesDirectories-sub)
cmake_language(
PRINT_PROPERTIES
DIRECTORIES . PrintPropertiesDirectories-sub
NAMED
MY_PROP
LABELS
NOT_SET
)
@@ -0,0 +1,16 @@
-- Configuring done .*
CMake Warning at PrintPropertiesEmptyMatch\.cmake:[0-9]+ \(cmake_language\):
No properties for TARGET mylib matching name 'ZZZ_NO_SUCH_PROPERTY' in
cmake_language\(PRINT_PROPERTIES \.\.\.\)\.
Call Stack \(most recent call first\):
CMakeLists\.txt:[0-9]+ \(include\)
CMake Warning at PrintPropertiesEmptyMatch\.cmake:[0-9]+ \(cmake_language\):
No properties for TARGET mylib matching value 'ZZZ_NO_SUCH_VALUE' in
cmake_language\(PRINT_PROPERTIES \.\.\.\)\.
Call Stack \(most recent call first\):
CMakeLists\.txt:[0-9]+ \(include\)
-- Generating done .*
@@ -0,0 +1,24 @@
enable_language(C)
add_library(libdep INTERFACE)
add_library(mylib STATIC nothing.c)
target_link_libraries(mylib PRIVATE libdep)
# One call per regex so each warning text is short and won't wrap.
cmake_language(
PRINT_PROPERTIES
TARGETS mylib
DEFERRED
FOLLOW_DEPENDENCIES
ALL
PROPERTY_NAME_REGEX "ZZZ_NO_SUCH_PROPERTY"
)
cmake_language(
PRINT_PROPERTIES
TARGETS mylib
DEFERRED
FOLLOW_DEPENDENCIES
ALL
PROPERTY_VALUE_REGEX "ZZZ_NO_SUCH_VALUE"
)
@@ -0,0 +1,15 @@
-- Printing properties\.\.\.
+All properties for TARGET mylib matching name 'INTERFACE':
+mylib.INTERFACE_CXX_MODULE_SETS = \"\"
+mylib.INTERFACE_HEADER_SETS = \"HEADERS\"
+mylib.INTERFACE_INCLUDE_DIRECTORIES = \"\$\<BUILD_INTERFACE:[^\"]*/Tests/RunCMake/cmake_language\>\"
+mylib.INTERFACE_SOURCE_SETS = \"\"
-- Printing properties\.\.\.
+All properties for TARGET mylib matching value 'HEADERS':
+mylib.HEADER_SETS = \"HEADERS\"
+mylib.INTERFACE_HEADER_SETS = \"HEADERS\"
-- Printing properties\.\.\.
+All properties for TARGET mylib matching name 'INTERFACE' and value 'HEADERS':
+mylib.INTERFACE_HEADER_SETS = \"HEADERS\"
@@ -0,0 +1,38 @@
enable_language(C)
add_library(mylib STATIC nothing.c)
target_sources(mylib
PUBLIC
FILE_SET HEADERS
FILES nothing.h
)
# Add a property
set_target_properties(mylib
PROPERTIES
some_property some_value
another_property another_value
)
cmake_language(
PRINT_PROPERTIES
TARGETS mylib
ALL
PROPERTY_NAME_REGEX INTERFACE
)
cmake_language(
PRINT_PROPERTIES
TARGETS mylib
ALL
PROPERTY_VALUE_REGEX HEADERS
)
cmake_language(
PRINT_PROPERTIES
TARGETS mylib
ALL
PROPERTY_NAME_REGEX INTERFACE
PROPERTY_VALUE_REGEX HEADERS
)
@@ -0,0 +1,13 @@
-- Configuring done .*
-- Printing properties\.\.\.
+Properties for TARGET mylib \(and all reachable\):
+mylib\.MY_PROP = "mylib"
+mylib\.MY_PROP_2 = "mylib2"
+intermediate\.MY_PROP = "intermediate"
+intermediate\.MY_PROP_2 = "intermediate2"
+leaflib\.MY_PROP = "leaf"
+leaflib\.MY_PROP_2 = "leaf2"
+linkonlylib\.MY_PROP = "linkonly"
+linkonlylib\.MY_PROP_2 = "linkonly2"
-- Generating done .*
@@ -0,0 +1,38 @@
enable_language(C)
add_library(leaflib STATIC nothing.c)
set_target_properties(leaflib PROPERTIES
MY_PROP "leaf"
MY_PROP_2 "leaf2"
)
add_library(linkonlylib STATIC nothing.c)
set_target_properties(linkonlylib PROPERTIES
MY_PROP "linkonly"
MY_PROP_2 "linkonly2"
)
add_library(intermediate STATIC nothing.c)
target_link_libraries(intermediate
PRIVATE leaflib
INTERFACE $<LINK_ONLY:linkonlylib>
)
set_target_properties(intermediate PROPERTIES
MY_PROP "intermediate"
MY_PROP_2 "intermediate2"
)
add_library(mylib STATIC nothing.c)
target_link_libraries(mylib PUBLIC intermediate)
set_target_properties(mylib PROPERTIES
MY_PROP "mylib"
MY_PROP_2 "mylib2"
)
cmake_language(
PRINT_PROPERTIES
TARGETS mylib
DEFERRED
FOLLOW_DEPENDENCIES
NAMED MY_PROP MY_PROP_2
)
@@ -0,0 +1,7 @@
-- Configuring done .*
-- Printing properties\.\.\.
+Properties for TARGET mylib \(and all reachable\):
+mylib\.MY_PROP = "mylib"
+leaflib\.MY_PROP = "leaf"
-- Generating done .*
@@ -0,0 +1,15 @@
enable_language(C)
add_library(leaflib STATIC nothing.c)
set_target_properties(leaflib PROPERTIES MY_PROP "leaf")
add_library(mylib STATIC nothing.c)
target_link_libraries(mylib PUBLIC leaflib)
set_target_properties(mylib PROPERTIES MY_PROP "mylib")
cmake_language(
PRINT_PROPERTIES
TARGETS mylib
FOLLOW_DEPENDENCIES
NAMED MY_PROP
)
@@ -0,0 +1,3 @@
^CMake Error at PrintPropertiesFollowRequiresTargets\.cmake:[0-9]+ \(cmake_language\):
cmake_language FOLLOW_DEPENDENCIES keyword in
cmake_language\(PRINT_PROPERTIES\) call is only valid with the TARGETS scope\.
@@ -0,0 +1,7 @@
# FOLLOW_DEPENDENCIES is only valid with the TARGETS scope.
cmake_language(
PRINT_PROPERTIES
SOURCES nothing.c
FOLLOW_DEPENDENCIES
NAMED LANGUAGE
)
@@ -0,0 +1,3 @@
-- Printing properties\.\.\.
+All properties for TARGET mylib matching name '\^MY_MARKER\$':
+mylib\.MY_MARKER = "marker_value"
@@ -0,0 +1,12 @@
enable_language(C)
add_library(mylib STATIC nothing.c)
set_target_properties(mylib PROPERTIES MY_MARKER "marker_value")
# No ALL or NAMED keyword - ALL should be implied. Narrow with a regex so
# the output is deterministic across cmake builds.
cmake_language(
PRINT_PROPERTIES
TARGETS mylib
PROPERTY_NAME_REGEX "^MY_MARKER$"
)
@@ -0,0 +1,3 @@
^CMake Error at PrintPropertiesLegacyFlagRequiresNamed\.cmake:[0-9]+ \(cmake_language\):
cmake_language __CMAKE_PRINT_PROPERTIES in cmake_language\(PRINT_PROPERTIES\)
call is only valid with NAMED\.
@@ -0,0 +1,3 @@
# __CMAKE_PRINT_PROPERTIES marks the legacy cmake_print_properties() call, which
# is always NAMED; here no NAMED is given (implicit ALL), which rejects it.
cmake_language(PRINT_PROPERTIES TARGETS some_target __CMAKE_PRINT_PROPERTIES)
@@ -0,0 +1,3 @@
-- Printing properties\.\.\.
+Properties for TARGET myimp:
+myimp\.LOCATION = "/imported/libmyimp\.so"
@@ -0,0 +1,5 @@
add_library(myimp SHARED IMPORTED)
set_target_properties(myimp PROPERTIES IMPORTED_LOCATION "/imported/libmyimp.so")
# Reading LOCATION from an imported target succeeds.
cmake_language(PRINT_PROPERTIES TARGETS myimp NAMED LOCATION)
@@ -0,0 +1,19 @@
CMake Warning at PrintPropertiesLocationNonImported\.cmake:[0-9]+ \(cmake_language\):
The LOCATION property may not be read from non-imported target "mylib";
skipping\.
Call Stack \(most recent call first\):
CMakeLists\.txt:[0-9]+ \(include\)
CMake Warning at PrintPropertiesLocationNonImported\.cmake:[0-9]+ \(cmake_language\):
The LOCATION_Debug property may not be read from non-imported target
"mylib"; skipping\.
Call Stack \(most recent call first\):
CMakeLists\.txt:[0-9]+ \(include\)
CMake Warning at PrintPropertiesLocationNonImported\.cmake:[0-9]+ \(cmake_language\):
The Debug_LOCATION property may not be read from non-imported target
"mylib"; skipping\.
Call Stack \(most recent call first\):
CMakeLists\.txt:[0-9]+ \(include\)
@@ -0,0 +1,4 @@
-- Printing properties\.\.\.
+Properties for TARGET mylib:
+mylib\.TYPE = "STATIC_LIBRARY"
+mylib\.NAME = "mylib"
@@ -0,0 +1,8 @@
enable_language(C)
add_library(mylib STATIC nothing.c)
# Reading a computed location property from a non-imported target
# is skipped with a warning instead of read. Any other requested properties
# should still be printed.
cmake_language(PRINT_PROPERTIES TARGETS mylib
NAMED TYPE LOCATION LOCATION_Debug Debug_LOCATION NAME)
@@ -0,0 +1,19 @@
CMake Warning at PrintPropertiesLocationOnlySkipped\.cmake:[0-9]+ \(cmake_language\):
The LOCATION property may not be read from non-imported target "mylib";
skipping\.
Call Stack \(most recent call first\):
CMakeLists\.txt:[0-9]+ \(include\)
CMake Warning at PrintPropertiesLocationOnlySkipped\.cmake:[0-9]+ \(cmake_language\):
The LOCATION_Debug property may not be read from non-imported target
"mylib"; skipping\.
Call Stack \(most recent call first\):
CMakeLists\.txt:[0-9]+ \(include\)
CMake Warning at PrintPropertiesLocationOnlySkipped\.cmake:[0-9]+ \(cmake_language\):
The Debug_LOCATION property may not be read from non-imported target
"mylib"; skipping\.
Call Stack \(most recent call first\):
CMakeLists\.txt:[0-9]+ \(include\)
@@ -0,0 +1,7 @@
enable_language(C)
add_library(mylib STATIC nothing.c)
# If we only read LOCATION based properties on a non-imported target, print just
# the warning and suppress the header.
cmake_language(PRINT_PROPERTIES TARGETS mylib
NAMED LOCATION LOCATION_Debug Debug_LOCATION)
@@ -0,0 +1,12 @@
-- Printing properties\.\.\.
+No such TARGET "does_not_exist" !
-- Configuring done .*
-- Printing properties\.\.\.
+No such TARGET "does_not_exist" !
-- Generating done .*
@@ -0,0 +1,14 @@
# Configure-time: prints a status-line note and continues.
cmake_language(
PRINT_PROPERTIES
TARGETS does_not_exist
NAMED MY_PROP
)
# Deferred: prints a status-line note at generate time and continues.
cmake_language(
PRINT_PROPERTIES
TARGETS does_not_exist
DEFERRED
NAMED MY_PROP
)
@@ -0,0 +1,3 @@
^CMake Error at PrintPropertiesNameRegexError\.cmake:[0-9]+ \(cmake_language\):
cmake_language PROPERTY_NAME_REGEX regular expression "\[unbalanced" cannot
compile\.
@@ -0,0 +1,10 @@
enable_language(C)
add_library(mylib STATIC nothing.c)
# Unbalanced bracket - regex compile must fail with a fatal error.
cmake_language(
PRINT_PROPERTIES
TARGETS mylib
ALL
PROPERTY_NAME_REGEX "[unbalanced"
)
@@ -0,0 +1,3 @@
^CMake Error at PrintPropertiesNamedRequired\.cmake:[0-9]+ \(cmake_language\):
cmake_language NAMED keyword missing in cmake_language\(PRINT_PROPERTIES\)
call with SOURCES scope\.
@@ -0,0 +1,5 @@
# Non-TARGETS scopes require NAMED.
cmake_language(
PRINT_PROPERTIES
SOURCES nothing.c
)
@@ -0,0 +1,4 @@
^CMake Error at PrintPropertiesRegexRequiresTargets\.cmake:[0-9]+ \(cmake_language\):
cmake_language PROPERTY_NAME_REGEX and PROPERTY_VALUE_REGEX in
cmake_language\(PRINT_PROPERTIES\) call are only valid with the TARGETS scope
and ALL\.
@@ -0,0 +1,7 @@
# Regex filters are only valid with the TARGETS scope and ALL.
cmake_language(
PRINT_PROPERTIES
SOURCES nothing.c
NAMED LANGUAGE
PROPERTY_NAME_REGEX "LANG"
)
@@ -0,0 +1,3 @@
^CMake Error at PrintPropertiesRegexWithNamed\.cmake:[0-9]+ \(cmake_language\):
cmake_language PROPERTY_NAME_REGEX and PROPERTY_VALUE_REGEX in
cmake_language\(PRINT_PROPERTIES\) call are only valid with ALL, not NAMED\.
@@ -0,0 +1,10 @@
enable_language(C)
add_library(mylib STATIC nothing.c)
# Regex filters require ALL; combining with NAMED is an error.
cmake_language(
PRINT_PROPERTIES
TARGETS mylib
NAMED MY_PROP
PROPERTY_NAME_REGEX "MY_"
)
@@ -0,0 +1,9 @@
-- Printing properties\.\.\.
+Properties for TARGET rot13:
+rot13.SOURCES = \"rot13.c;rot13.h\"
+rot13.POSITION_INDEPENDENT_CODE = \"True\"
-- Printing properties\.\.\.
+Properties for SOURCE rot13.c:
+rot13.c.LOCATION = \"[^\"]*/cmake_language/rot13.c\"
+rot13.c.LANGUAGE = \"C\"
@@ -0,0 +1,21 @@
enable_language(C)
set_property(SOURCE rot13.c PROPERTY LANGUAGE C)
add_library(rot13 SHARED rot13.c rot13.h)
cmake_language(
PRINT_PROPERTIES
TARGETS rot13
NAMED
SOURCES
POSITION_INDEPENDENT_CODE
)
cmake_language(
PRINT_PROPERTIES
SOURCES rot13.c
NAMED
LOCATION
LANGUAGE
)
@@ -0,0 +1,9 @@
-- Printing properties\.\.\.
Properties for TEST test_a:
test_a\.MY_PROP = "a_val"
test_a\.TIMEOUT = "30"
test_a\.NOT_SET = <NOTFOUND>
Properties for TEST test_b:
test_b\.MY_PROP = "b_val"
test_b\.TIMEOUT = "60"
test_b\.NOT_SET = <NOTFOUND>
@@ -0,0 +1,14 @@
enable_testing()
add_test(NAME test_a COMMAND "${CMAKE_COMMAND}" -E true)
add_test(NAME test_b COMMAND "${CMAKE_COMMAND}" -E true)
set_tests_properties(test_a PROPERTIES MY_PROP "a_val" TIMEOUT 30)
set_tests_properties(test_b PROPERTIES MY_PROP "b_val" TIMEOUT 60)
cmake_language(
PRINT_PROPERTIES
TESTS test_a test_b
NAMED
MY_PROP
TIMEOUT
NOT_SET
)
@@ -0,0 +1,3 @@
^CMake Error at PrintPropertiesValueRegexError\.cmake:[0-9]+ \(cmake_language\):
cmake_language PROPERTY_VALUE_REGEX regular expression "\[unbalanced" cannot
compile\.
@@ -0,0 +1,10 @@
enable_language(C)
add_library(mylib STATIC nothing.c)
cmake_language(
PRINT_PROPERTIES
TARGETS mylib
ALL
PROPERTY_VALUE_REGEX "[unbalanced"
)
@@ -167,6 +167,47 @@ run_cmake(Experimental/ExportPackageDependencies-wrong)
run_cmake(Experimental/ExportPackageDependencies-quiet)
run_cmake(Experimental/Unknown)
run_cmake(PrintProperties)
run_cmake(PrintPropertiesSources)
run_cmake(PrintPropertiesDirectories)
run_cmake(PrintPropertiesTests)
run_cmake(PrintPropertiesCacheEntries)
run_cmake(PrintPropertiesLegacyFlagRequiresNamed)
run_cmake(PrintPropertiesAll)
run_cmake(PrintPropertiesImplicitAll)
run_cmake(PrintPropertiesFiltering)
run_cmake(PrintPropertiesDeferred)
run_cmake(PrintPropertiesFollowDependencies)
# Merge stdout+stderr so the expected output can assert that warnings /
# generator-time messages fall between "Configuring done" and
# "Generating done" - i.e. from the generator-action lambda.
block()
set(RunCMake_TEST_OUTPUT_MERGE 1)
run_cmake(PrintPropertiesAllFollowDependencies)
run_cmake(PrintPropertiesAllFollowBothRegex)
run_cmake(PrintPropertiesFollowImpliesDeferred)
run_cmake(PrintPropertiesEmptyMatch)
run_cmake(PrintPropertiesMissingTarget)
set(RunCMake_TEST_OPTIONS -DCMAKE_BUILD_TYPE=Debug)
run_cmake(PrintPropertiesConfigSpecific)
endblock()
run_cmake(PrintPropertiesAllAndNamed)
run_cmake(PrintPropertiesRegexWithNamed)
run_cmake(PrintPropertiesNamedRequired)
run_cmake(PrintPropertiesAllRequiresTargets)
run_cmake(PrintPropertiesRegexRequiresTargets)
run_cmake(PrintPropertiesDeferredRequiresTargets)
run_cmake(PrintPropertiesFollowRequiresTargets)
run_cmake(PrintPropertiesNameRegexError)
run_cmake(PrintPropertiesValueRegexError)
run_cmake(PrintPropertiesLocationNonImported)
run_cmake(PrintPropertiesLocationImported)
block()
# All requested properties are skipped, so no block header is emitted.
set(RunCMake_TEST_NOT_EXPECT_stdout "Properties for TARGET")
run_cmake(PrintPropertiesLocationOnlySkipped)
endblock()
run_cmake(trace)
run_cmake(trace_seq)
run_cmake(trace_expand)
+6
View File
@@ -0,0 +1,6 @@
#include "nothing.h"
void nothing(void)
{
(void*)0;
}
+8
View File
@@ -0,0 +1,8 @@
#ifndef NOTHING_H
#define NOTHING_H
#include <stdlib.h>
void nothing();
#endif
+15
View File
@@ -0,0 +1,15 @@
#include "rot13.h"
void rot13(char* in)
{
char* end = in + strlen(in);
for (char* c = in; c < end; c++) {
if (*c >= 'a' && *c <= 'z') {
*c += (*c < 'n') ? 13 : -13;
continue;
}
if (*c >= 'A' && *c <= 'Z') {
*c += (*c < 'N') ? 13 : -13;
}
}
}
+9
View File
@@ -0,0 +1,9 @@
#ifndef ROT13_H
#define ROT13_H
#include <stdlib.h>
#include <string.h>
void rot13(char* in);
#endif
@@ -0,0 +1,7 @@
#include "something.h"
int main(void)
{
nothing();
return 0;
}
@@ -0,0 +1,8 @@
#ifndef SOMETHING_H
#define SOMETHING_H
#include <stdlib.h>
#include "nothing.h"
#endif