diff --git a/Help/manual/cmake-generator-expressions.7.rst b/Help/manual/cmake-generator-expressions.7.rst index 4514dbcd05..85c2d52d25 100644 --- a/Help/manual/cmake-generator-expressions.7.rst +++ b/Help/manual/cmake-generator-expressions.7.rst @@ -2836,6 +2836,13 @@ In the following, the phrase "the ``tgt`` filename" means the name of the This generator expression can e.g. be used to create a batch file using :command:`file(GENERATE)` which sets the PATH environment variable accordingly. +.. genex:: $ + + .. versionadded:: 4.2 + + The full path to the directory where intermediate target files, such as + object and dependency files, are stored. + Export And Install Expressions ------------------------------ diff --git a/Help/release/dev/genex-target-intermediate-dir.rst b/Help/release/dev/genex-target-intermediate-dir.rst new file mode 100644 index 0000000000..8fef3a4734 --- /dev/null +++ b/Help/release/dev/genex-target-intermediate-dir.rst @@ -0,0 +1,6 @@ +genex-target-intermediate-dir +----------------------------- + +* The :genex:`TARGET_INTERMEDIATE_DIR` generator expression was + added to refer to a target's intermediate files directory in + the build tree. diff --git a/Source/cmGeneratorExpressionNode.cxx b/Source/cmGeneratorExpressionNode.cxx index b8bf6a3598..d5d59376a0 100644 --- a/Source/cmGeneratorExpressionNode.cxx +++ b/Source/cmGeneratorExpressionNode.cxx @@ -52,6 +52,31 @@ #include "cmValue.h" #include "cmake.h" +namespace { + +bool HasKnownObjectFileLocation(cm::GenEx::Evaluation* eval, + GeneratorExpressionContent const* content, + std::string const& genex, + cmGeneratorTarget const* target) +{ + std::string reason; + if (!eval->EvaluateForBuildsystem && + !target->Target->HasKnownObjectFileLocation(&reason)) { + std::ostringstream e; + e << "The evaluation of the " << genex + << " generator expression " + "is only suitable for consumption by CMake (limited" + << reason + << "). " + "It is not suitable for writing out elsewhere."; + reportError(eval, content->GetOriginalExpression(), e.str()); + return false; + } + return true; +} + +} // namespace + std::string cmGeneratorExpressionNode::EvaluateDependentExpression( std::string const& prop, cm::GenEx::Evaluation* eval, cmGeneratorTarget const* headTarget, @@ -3227,6 +3252,71 @@ static const struct TargetPropertyNode : public cmGeneratorExpressionNode } } targetPropertyNode; +static const struct targetIntermediateDirNode + : public cmGeneratorExpressionNode +{ + targetIntermediateDirNode() {} // NOLINT(modernize-use-equals-default) + + static char const* GetErrorText(std::string const& targetName) + { + static cmsys::RegularExpression propertyNameValidator("^[A-Za-z0-9_]+$"); + if (targetName.empty()) { + return "$ expression requires a non-empty " + "target name."; + } + if (!cmGeneratorExpression::IsValidTargetName(targetName)) { + return "Target name not supported."; + } + return nullptr; + } + + std::string Evaluate( + std::vector const& parameters, cm::GenEx::Evaluation* eval, + GeneratorExpressionContent const* content, + cmGeneratorExpressionDAGChecker* /*dagChecker*/) const override + { + cmGeneratorTarget const* target = nullptr; + std::string targetName; + + if (parameters.size() == 1) { + targetName = parameters[0]; + + if (char const* e = GetErrorText(targetName)) { + reportError(eval, content->GetOriginalExpression(), e); + return std::string(); + } + cmLocalGenerator const* lg = eval->CurrentTarget + ? eval->CurrentTarget->GetLocalGenerator() + : eval->Context.LG; + target = lg->FindGeneratorTargetToUse(targetName); + + if (!target) { + std::ostringstream e; + e << "Target \"" << targetName << "\" not found."; + reportError(eval, content->GetOriginalExpression(), e.str()); + return std::string(); + } + eval->AllTargets.insert(target); + + } else { + reportError( + eval, content->GetOriginalExpression(), + "$ expression requires one parameter"); + return std::string(); + } + + assert(target); + + if (!HasKnownObjectFileLocation(eval, content, "TARGET_INTERMEDIATE_DIR", + target)) { + return std::string(); + } + + return cmSystemTools::CollapseFullPath( + target->GetObjectDirectory(eval->Context.Config)); + } +} targetIntermediateDirNode; + static const struct TargetNameNode : public cmGeneratorExpressionNode { TargetNameNode() {} // NOLINT(modernize-use-equals-default) @@ -3282,19 +3372,8 @@ static const struct TargetObjectsNode : public cmGeneratorExpressionNode return std::string(); } cmGlobalGenerator const* gg = eval->Context.LG->GetGlobalGenerator(); - { - std::string reason; - if (!eval->EvaluateForBuildsystem && - !gt->Target->HasKnownObjectFileLocation(&reason)) { - std::ostringstream e; - e << "The evaluation of the TARGET_OBJECTS generator expression " - "is only suitable for consumption by CMake (limited" - << reason - << "). " - "It is not suitable for writing out elsewhere."; - reportError(eval, content->GetOriginalExpression(), e.str()); - return std::string(); - } + if (!HasKnownObjectFileLocation(eval, content, "TARGET_OBJECTS", gt)) { + return std::string(); } cmList objects; @@ -4809,6 +4888,7 @@ cmGeneratorExpressionNode const* cmGeneratorExpressionNode::GetNode( { "SEMICOLON", &semicolonNode }, { "QUOTE", "eNode }, { "TARGET_PROPERTY", &targetPropertyNode }, + { "TARGET_INTERMEDIATE_DIR", &targetIntermediateDirNode }, { "TARGET_NAME", &targetNameNode }, { "TARGET_OBJECTS", &targetObjectsNode }, { "TARGET_POLICY", &targetPolicyNode }, diff --git a/Tests/RunCMake/CMakeLists.txt b/Tests/RunCMake/CMakeLists.txt index 5cae523981..e63bf420e1 100644 --- a/Tests/RunCMake/CMakeLists.txt +++ b/Tests/RunCMake/CMakeLists.txt @@ -526,8 +526,13 @@ add_RunCMake_test(GenEx-TARGET_RUNTIME_DLLS) add_RunCMake_test(GenEx-PATH) add_RunCMake_test(GenEx-PATH_EQUAL) add_RunCMake_test(GenEx-LIST) -add_RunCMake_test(GeneratorExpression -DCMake_TEST_OBJC=${CMake_TEST_OBJC} -DCMake_TEST_Fortran=${CMake_TEST_Fortran} - -DCMake_TEST_CUDA=${CMake_TEST_CUDA} -DCMake_TEST_HIP=${CMake_TEST_HIP}) +add_RunCMake_test(GeneratorExpression + -DCMake_TEST_OBJC=${CMake_TEST_OBJC} + -DCMake_TEST_Fortran=${CMake_TEST_Fortran} + -DCMake_TEST_CUDA=${CMake_TEST_CUDA} + -DCMake_TEST_HIP=${CMake_TEST_HIP} + -DCMAKE_C_OUTPUT_EXTENSION=${CMAKE_C_OUTPUT_EXTENSION} +) add_RunCMake_test(GeneratorExpressionShortCircuit) add_RunCMake_test(GeneratorInstance) add_RunCMake_test(GeneratorPlatform) diff --git a/Tests/RunCMake/GeneratorExpression/RunCMakeTest.cmake b/Tests/RunCMake/GeneratorExpression/RunCMakeTest.cmake index db825b7be3..9c968c6d8d 100644 --- a/Tests/RunCMake/GeneratorExpression/RunCMakeTest.cmake +++ b/Tests/RunCMake/GeneratorExpression/RunCMakeTest.cmake @@ -56,7 +56,10 @@ function(run_cmake_build test) list(APPEND RunCMake_TEST_OPTIONS -DCMAKE_BUILD_TYPE=Release) endif() - run_cmake(${test}) + block() + unset(RunCMake-check-file) + run_cmake(${test}) + endblock() set(RunCMake_TEST_NO_CLEAN TRUE) run_cmake_command(${test}-build ${CMAKE_COMMAND} --build . --config Release) @@ -138,3 +141,19 @@ run_cmake(CMP0085-WARN) set(RunCMake_TEST_OPTIONS -DCMAKE_POLICY_DEFAULT_CMP0085:STRING=NEW) run_cmake(CMP0085-NEW) unset(RunCMake_TEST_OPTIONS) + +if(RunCMake_GENERATOR STREQUAL "Xcode" AND "$ENV{CMAKE_OSX_ARCHITECTURES}" MATCHES "[;$]") + run_cmake(TARGET_INTERMEDIATE_DIR-Xcode) +else() + block() + set(RunCMake-check-file TARGET_INTERMEDIATE_DIR-build-check.cmake) + foreach(strategy IN ITEMS SHORT FULL) + set(CMAKE_INTERMEDIATE_DIR_STRATEGY ${strategy}) + set(RunCMake_TEST_OPTIONS -DCMAKE_INTERMEDIATE_DIR_STRATEGY=${strategy}) + run_cmake_build(TARGET_INTERMEDIATE_DIR-${strategy}) + endforeach() + endblock() +endif() +run_cmake(TARGET_INTERMEDIATE_DIR-bad-arg) +run_cmake(TARGET_INTERMEDIATE_DIR-bad-target) +run_cmake(TARGET_INTERMEDIATE_DIR-not-a-target) diff --git a/Tests/RunCMake/GeneratorExpression/TARGET_INTERMEDIATE_DIR-FULL.cmake b/Tests/RunCMake/GeneratorExpression/TARGET_INTERMEDIATE_DIR-FULL.cmake new file mode 100644 index 0000000000..55cc5e3188 --- /dev/null +++ b/Tests/RunCMake/GeneratorExpression/TARGET_INTERMEDIATE_DIR-FULL.cmake @@ -0,0 +1 @@ +include(TARGET_INTERMEDIATE_DIR.cmake) diff --git a/Tests/RunCMake/GeneratorExpression/TARGET_INTERMEDIATE_DIR-SHORT.cmake b/Tests/RunCMake/GeneratorExpression/TARGET_INTERMEDIATE_DIR-SHORT.cmake new file mode 100644 index 0000000000..55cc5e3188 --- /dev/null +++ b/Tests/RunCMake/GeneratorExpression/TARGET_INTERMEDIATE_DIR-SHORT.cmake @@ -0,0 +1 @@ +include(TARGET_INTERMEDIATE_DIR.cmake) diff --git a/Tests/RunCMake/GeneratorExpression/TARGET_INTERMEDIATE_DIR-Xcode-result.txt b/Tests/RunCMake/GeneratorExpression/TARGET_INTERMEDIATE_DIR-Xcode-result.txt new file mode 100644 index 0000000000..d00491fd7e --- /dev/null +++ b/Tests/RunCMake/GeneratorExpression/TARGET_INTERMEDIATE_DIR-Xcode-result.txt @@ -0,0 +1 @@ +1 diff --git a/Tests/RunCMake/GeneratorExpression/TARGET_INTERMEDIATE_DIR-Xcode-stderr.txt b/Tests/RunCMake/GeneratorExpression/TARGET_INTERMEDIATE_DIR-Xcode-stderr.txt new file mode 100644 index 0000000000..3cbb3f9cc8 --- /dev/null +++ b/Tests/RunCMake/GeneratorExpression/TARGET_INTERMEDIATE_DIR-Xcode-stderr.txt @@ -0,0 +1,8 @@ +CMake Error at TARGET_INTERMEDIATE_DIR\.cmake:[0-9]+ \(file\): + Error evaluating generator expression: + + \$\ + + The evaluation of the TARGET_INTERMEDIATE_DIR generator expression is only + suitable for consumption by CMake \(limited under Xcode with multiple + architectures\)\. It is not suitable for writing out elsewhere\. diff --git a/Tests/RunCMake/GeneratorExpression/TARGET_INTERMEDIATE_DIR-Xcode.cmake b/Tests/RunCMake/GeneratorExpression/TARGET_INTERMEDIATE_DIR-Xcode.cmake new file mode 100644 index 0000000000..55cc5e3188 --- /dev/null +++ b/Tests/RunCMake/GeneratorExpression/TARGET_INTERMEDIATE_DIR-Xcode.cmake @@ -0,0 +1 @@ +include(TARGET_INTERMEDIATE_DIR.cmake) diff --git a/Tests/RunCMake/GeneratorExpression/TARGET_INTERMEDIATE_DIR-bad-arg-result.txt b/Tests/RunCMake/GeneratorExpression/TARGET_INTERMEDIATE_DIR-bad-arg-result.txt new file mode 100644 index 0000000000..d00491fd7e --- /dev/null +++ b/Tests/RunCMake/GeneratorExpression/TARGET_INTERMEDIATE_DIR-bad-arg-result.txt @@ -0,0 +1 @@ +1 diff --git a/Tests/RunCMake/GeneratorExpression/TARGET_INTERMEDIATE_DIR-bad-arg-stderr.txt b/Tests/RunCMake/GeneratorExpression/TARGET_INTERMEDIATE_DIR-bad-arg-stderr.txt new file mode 100644 index 0000000000..a999a5af77 --- /dev/null +++ b/Tests/RunCMake/GeneratorExpression/TARGET_INTERMEDIATE_DIR-bad-arg-stderr.txt @@ -0,0 +1,8 @@ +CMake Error at TARGET_INTERMEDIATE_DIR-bad-arg.cmake:1 \(file\): + Error evaluating generator expression: + + \$ + + \$ expression requires exactly one parameter. +Call Stack \(most recent call first\): + CMakeLists.txt:[0-9]+ \(include\) diff --git a/Tests/RunCMake/GeneratorExpression/TARGET_INTERMEDIATE_DIR-bad-arg.cmake b/Tests/RunCMake/GeneratorExpression/TARGET_INTERMEDIATE_DIR-bad-arg.cmake new file mode 100644 index 0000000000..111b92a82a --- /dev/null +++ b/Tests/RunCMake/GeneratorExpression/TARGET_INTERMEDIATE_DIR-bad-arg.cmake @@ -0,0 +1,2 @@ +file(GENERATE OUTPUT TARGET_INTERMEDIATE_DIR-no-arg-generated.txt + CONTENT "$") diff --git a/Tests/RunCMake/GeneratorExpression/TARGET_INTERMEDIATE_DIR-bad-target-result.txt b/Tests/RunCMake/GeneratorExpression/TARGET_INTERMEDIATE_DIR-bad-target-result.txt new file mode 100644 index 0000000000..d00491fd7e --- /dev/null +++ b/Tests/RunCMake/GeneratorExpression/TARGET_INTERMEDIATE_DIR-bad-target-result.txt @@ -0,0 +1 @@ +1 diff --git a/Tests/RunCMake/GeneratorExpression/TARGET_INTERMEDIATE_DIR-bad-target-stderr.txt b/Tests/RunCMake/GeneratorExpression/TARGET_INTERMEDIATE_DIR-bad-target-stderr.txt new file mode 100644 index 0000000000..e8eeab0559 --- /dev/null +++ b/Tests/RunCMake/GeneratorExpression/TARGET_INTERMEDIATE_DIR-bad-target-stderr.txt @@ -0,0 +1,8 @@ +CMake Error at TARGET_INTERMEDIATE_DIR-bad-target.cmake:1 \(file\): + Error evaluating generator expression: + + \$ + + Target name not supported. +Call Stack \(most recent call first\): + CMakeLists.txt:[0-9]+ \(include\) diff --git a/Tests/RunCMake/GeneratorExpression/TARGET_INTERMEDIATE_DIR-bad-target.cmake b/Tests/RunCMake/GeneratorExpression/TARGET_INTERMEDIATE_DIR-bad-target.cmake new file mode 100644 index 0000000000..97f59a48cd --- /dev/null +++ b/Tests/RunCMake/GeneratorExpression/TARGET_INTERMEDIATE_DIR-bad-target.cmake @@ -0,0 +1,2 @@ +file(GENERATE OUTPUT TARGET_INTERMEDIATE_DIR-no-arg-generated.txt + CONTENT "$") diff --git a/Tests/RunCMake/GeneratorExpression/TARGET_INTERMEDIATE_DIR-build-check.cmake b/Tests/RunCMake/GeneratorExpression/TARGET_INTERMEDIATE_DIR-build-check.cmake new file mode 100644 index 0000000000..5a88577b96 --- /dev/null +++ b/Tests/RunCMake/GeneratorExpression/TARGET_INTERMEDIATE_DIR-build-check.cmake @@ -0,0 +1,46 @@ +file(READ "${RunCMake_TEST_BINARY_DIR}/TARGET_INTERMEDIATE_DIR-generated.txt" dir) + +# Default Case +set(expected "foo\\.dir") + +# Short Object Names +if (CMAKE_INTERMEDIATE_DIR_STRATEGY STREQUAL "SHORT") + if (RunCMake_GENERATOR MATCHES "Ninja|Make|Visual Studio") + set(expected "[\\\\/][._]o[\\\\/][0-9a-f]+") + endif() +endif() + +# Xcode +if (RunCMake_GENERATOR MATCHES "Xcode") + set(expected "foo.build") +endif() + +# Append Config subdirectory +if (RunCMake_GENERATOR_IS_MULTI_CONFIG) + string(APPEND expected "[\\\\/]Release") +endif() + +# Xcode has additional paths +if (NOT RunCMake_GENERATOR MATCHES "Xcode") + string(APPEND expected "$") +endif() + +if(NOT dir MATCHES "${expected}") + set(RunCMake_TEST_FAILED "actual content:\n [[${dir}]]\nbut expected to match:\n [[${expected}]]") +elseif(NOT IS_DIRECTORY "${dir}") + set(RunCMake_TEST_FAILED "target intermediate directory does not exist: [[${dir}]]") +else() + file(GLOB object_files "${dir}/*${CMAKE_C_OUTPUT_EXTENSION}") + if (NOT object_files) + set(RunCMake_TEST_FAILED "no object files found in intermediate directory: [[${dir}]]") + endif() + if (CMAKE_INTERMEDIATE_DIR_STRATEGY STREQUAL "FULL") + set(object_name "${dir}/simple.c${CMAKE_C_OUTPUT_EXTENSION}") + if (RunCMake_GENERATOR MATCHES "Xcode|Visual Studio") + set(object_name "${dir}/simple${CMAKE_C_OUTPUT_EXTENSION}") + endif() + if (NOT EXISTS "${object_name}") + set(RunCMake_TEST_FAILED "simple.c object file not found in intermediate directory: [[${dir}]]") + endif() + endif() +endif() diff --git a/Tests/RunCMake/GeneratorExpression/TARGET_INTERMEDIATE_DIR-not-a-target-result.txt b/Tests/RunCMake/GeneratorExpression/TARGET_INTERMEDIATE_DIR-not-a-target-result.txt new file mode 100644 index 0000000000..d00491fd7e --- /dev/null +++ b/Tests/RunCMake/GeneratorExpression/TARGET_INTERMEDIATE_DIR-not-a-target-result.txt @@ -0,0 +1 @@ +1 diff --git a/Tests/RunCMake/GeneratorExpression/TARGET_INTERMEDIATE_DIR-not-a-target-stderr.txt b/Tests/RunCMake/GeneratorExpression/TARGET_INTERMEDIATE_DIR-not-a-target-stderr.txt new file mode 100644 index 0000000000..29a4cdd784 --- /dev/null +++ b/Tests/RunCMake/GeneratorExpression/TARGET_INTERMEDIATE_DIR-not-a-target-stderr.txt @@ -0,0 +1,8 @@ +CMake Error at TARGET_INTERMEDIATE_DIR-not-a-target.cmake:1 \(file\): + Error evaluating generator expression: + + \$ + + Target \"bar\" not found. +Call Stack \(most recent call first\): + CMakeLists.txt:[0-9]+ \(include\) diff --git a/Tests/RunCMake/GeneratorExpression/TARGET_INTERMEDIATE_DIR-not-a-target.cmake b/Tests/RunCMake/GeneratorExpression/TARGET_INTERMEDIATE_DIR-not-a-target.cmake new file mode 100644 index 0000000000..2f4fdab857 --- /dev/null +++ b/Tests/RunCMake/GeneratorExpression/TARGET_INTERMEDIATE_DIR-not-a-target.cmake @@ -0,0 +1,2 @@ +file(GENERATE OUTPUT TARGET_INTERMEDIATE_DIR-not-a-target-generated.txt + CONTENT "$") diff --git a/Tests/RunCMake/GeneratorExpression/TARGET_INTERMEDIATE_DIR.cmake b/Tests/RunCMake/GeneratorExpression/TARGET_INTERMEDIATE_DIR.cmake new file mode 100644 index 0000000000..0109b92dcc --- /dev/null +++ b/Tests/RunCMake/GeneratorExpression/TARGET_INTERMEDIATE_DIR.cmake @@ -0,0 +1,4 @@ +enable_language(C) +add_executable(foo simple.c) +file(GENERATE OUTPUT TARGET_INTERMEDIATE_DIR-generated.txt + CONTENT "$") diff --git a/Tests/RunCMake/GeneratorExpression/simple.c b/Tests/RunCMake/GeneratorExpression/simple.c new file mode 100644 index 0000000000..8488f4e58f --- /dev/null +++ b/Tests/RunCMake/GeneratorExpression/simple.c @@ -0,0 +1,4 @@ +int main(void) +{ + return 0; +}