diff --git a/Help/manual/cmake-variables.7.rst b/Help/manual/cmake-variables.7.rst index 181fbbcd1..ec741ae12 100644 --- a/Help/manual/cmake-variables.7.rst +++ b/Help/manual/cmake-variables.7.rst @@ -281,6 +281,7 @@ Variables that Control the Build /variable/CMAKE_MAP_IMPORTED_CONFIG_CONFIG /variable/CMAKE_MODULE_LINKER_FLAGS_CONFIG /variable/CMAKE_MODULE_LINKER_FLAGS + /variable/CMAKE_NINJA_OUTPUT_PATH_PREFIX /variable/CMAKE_NO_BUILTIN_CHRPATH /variable/CMAKE_NO_SYSTEM_FROM_IMPORTED /variable/CMAKE_OSX_ARCHITECTURES diff --git a/Help/release/dev/ninja-output-path-prefix.rst b/Help/release/dev/ninja-output-path-prefix.rst new file mode 100644 index 000000000..47a96603a --- /dev/null +++ b/Help/release/dev/ninja-output-path-prefix.rst @@ -0,0 +1,6 @@ +ninja-output-path-prefix +------------------------ + +* The :generator:`Ninja` generator learned to read a new + :variable:`CMAKE_NINJA_OUTPUT_PATH_PREFIX` variable to configure + the generated ``build.ninja`` file for use as a ``subninja``. diff --git a/Help/variable/CMAKE_NINJA_OUTPUT_PATH_PREFIX.rst b/Help/variable/CMAKE_NINJA_OUTPUT_PATH_PREFIX.rst new file mode 100644 index 000000000..64091aaaf --- /dev/null +++ b/Help/variable/CMAKE_NINJA_OUTPUT_PATH_PREFIX.rst @@ -0,0 +1,27 @@ +CMAKE_NINJA_OUTPUT_PATH_PREFIX +------------------------------ + +Set output files path prefix for the :generator:`Ninja` generator. + +Every output files listed in the generated ``build.ninja`` will be +prefixed by the contents of this variable (a trailing slash is +appended if missing). This is useful when the generated ninja file is +meant to be embedded as a ``subninja`` file into a *super* ninja +project. For example, a ninja build file generated with a command +like:: + + cd top-build-dir/sub && + cmake -G Ninja -DCMAKE_NINJA_OUTPUT_PATH_PREFIX=sub/ path/to/source + +can be embedded in ``top-build-dir/build.ninja`` with a directive like +this:: + + subninja sub/build.ninja + +The ``auto-regeneration`` rule in ``top-build-dir/build.ninja`` must have an +order-only dependency on ``sub/build.ninja``. + +.. note:: + When ``CMAKE_NINJA_OUTPUT_PATH_PREFIX`` is set, the project generated + by CMake cannot be used as a standalone project. No default targets + are specified. diff --git a/Source/cmAlgorithms.h b/Source/cmAlgorithms.h index 76acaca8f..ee803c8e8 100644 --- a/Source/cmAlgorithms.h +++ b/Source/cmAlgorithms.h @@ -379,4 +379,19 @@ std::reverse_iterator cmMakeReverseIterator(Iter it) return std::reverse_iterator(it); } +inline bool cmHasSuffix(const std::string& str, const std::string& suffix) +{ + if (str.size() < suffix.size()) { + return false; + } + return str.compare(str.size() - suffix.size(), suffix.size(), suffix) == 0; +} + +inline void cmStripSuffixIfExists(std::string& str, const std::string& suffix) +{ + if (cmHasSuffix(str, suffix)) { + str.resize(str.size() - suffix.size()); + } +} + #endif diff --git a/Source/cmGlobalNinjaGenerator.cxx b/Source/cmGlobalNinjaGenerator.cxx index 012ef9010..d65b463de 100644 --- a/Source/cmGlobalNinjaGenerator.cxx +++ b/Source/cmGlobalNinjaGenerator.cxx @@ -488,6 +488,10 @@ void cmGlobalNinjaGenerator::Generate() this->OpenBuildFileStream(); this->OpenRulesFileStream(); + this->InitOutputPathPrefix(); + this->TargetAll = this->NinjaOutputPath("all"); + this->CMakeCacheFile = this->NinjaOutputPath("CMakeCache.txt"); + this->PolicyCMP0058 = this->LocalGenerators[0]->GetMakefile()->GetPolicyStatus( cmPolicies::CMP0058); @@ -714,11 +718,29 @@ void cmGlobalNinjaGenerator::CloseRulesFileStream() } } +static void EnsureTrailingSlash(std::string& path) +{ + if (path.empty()) { + return; + } + std::string::value_type last = path[path.size() - 1]; +#ifdef _WIN32 + if (last != '\\') { + path += '\\'; + } +#else + if (last != '/') { + path += '/'; + } +#endif +} + std::string cmGlobalNinjaGenerator::ConvertToNinjaPath(const std::string& path) { cmLocalNinjaGenerator* ng = static_cast(this->LocalGenerators[0]); std::string convPath = ng->Convert(path, cmOutputConverter::HOME_OUTPUT); + convPath = this->NinjaOutputPath(convPath); #ifdef _WIN32 cmSystemTools::ReplaceString(convPath, "/", "\\"); #endif @@ -731,6 +753,7 @@ std::string cmGlobalNinjaGenerator::ConvertToNinjaFolderRule( cmLocalNinjaGenerator* ng = static_cast(this->LocalGenerators[0]); std::string convPath = ng->Convert(path + "/all", cmOutputConverter::HOME); + convPath = this->NinjaOutputPath(convPath); #ifdef _WIN32 cmSystemTools::ReplaceString(convPath, "/", "\\"); #endif @@ -836,22 +859,17 @@ void cmGlobalNinjaGenerator::AppendTargetOutputs( } case cmState::OBJECT_LIBRARY: case cmState::UTILITY: { - std::string path = this->ConvertToNinjaPath( - target->GetLocalGenerator()->GetCurrentBinaryDirectory()); - if (path.empty() || path == ".") - outputs.push_back(target->GetName()); - else { - path += "/"; - path += target->GetName(); - outputs.push_back(path); - } + std::string path = + target->GetLocalGenerator()->GetCurrentBinaryDirectory() + + std::string("/") + target->GetName(); + outputs.push_back(this->ConvertToNinjaPath(path)); break; } case cmState::GLOBAL_TARGET: // Always use the target in HOME instead of an unused duplicate in a // subdirectory. - outputs.push_back(target->GetName()); + outputs.push_back(this->NinjaOutputPath(target->GetName())); break; default: @@ -885,6 +903,7 @@ void cmGlobalNinjaGenerator::AppendTargetDepends( void cmGlobalNinjaGenerator::AddTargetAlias(const std::string& alias, cmGeneratorTarget* target) { + std::string buildAlias = this->NinjaOutputPath(alias); cmNinjaDeps outputs; this->AppendTargetOutputs(target, outputs); // Mark the target's outputs as ambiguous to ensure that no other target uses @@ -895,7 +914,7 @@ void cmGlobalNinjaGenerator::AddTargetAlias(const std::string& alias, // Insert the alias into the map. If the alias was already present in the // map and referred to another target, mark it as ambiguous. std::pair newAlias = - TargetAliases.insert(std::make_pair(alias, target)); + TargetAliases.insert(std::make_pair(buildAlias, target)); if (newAlias.second && newAlias.first->second != target) newAlias.first->second = 0; } @@ -1043,7 +1062,7 @@ void cmGlobalNinjaGenerator::WriteUnknownExplicitDependencies(std::ostream& os) } } } - knownDependencies.insert("CMakeCache.txt"); + knownDependencies.insert(this->CMakeCacheFile); for (TargetAliasMap::const_iterator i = this->TargetAliases.begin(); i != this->TargetAliases.end(); ++i) { @@ -1064,7 +1083,7 @@ void cmGlobalNinjaGenerator::WriteUnknownExplicitDependencies(std::ostream& os) // should all match now. std::vector unknownExplicitDepends; - this->CombinedCustomCommandExplicitDependencies.erase("all"); + this->CombinedCustomCommandExplicitDependencies.erase(this->TargetAll); std::set_difference(this->CombinedCustomCommandExplicitDependencies.begin(), this->CombinedCustomCommandExplicitDependencies.end(), @@ -1130,13 +1149,15 @@ void cmGlobalNinjaGenerator::WriteBuiltinTargets(std::ostream& os) void cmGlobalNinjaGenerator::WriteTargetAll(std::ostream& os) { cmNinjaDeps outputs; - outputs.push_back("all"); + outputs.push_back(this->TargetAll); this->WritePhonyBuild(os, "The main all target.", outputs, this->AllDependencies); - cmGlobalNinjaGenerator::WriteDefault(os, outputs, - "Make the all target the default."); + if (!this->HasOutputPathPrefix()) { + cmGlobalNinjaGenerator::WriteDefault(os, outputs, + "Make the all target the default."); + } } void cmGlobalNinjaGenerator::WriteTargetRebuildManifest(std::ostream& os) @@ -1171,7 +1192,7 @@ void cmGlobalNinjaGenerator::WriteTargetRebuildManifest(std::ostream& os) implicitDeps.push_back(this->ConvertToNinjaPath(*fi)); } } - implicitDeps.push_back("CMakeCache.txt"); + implicitDeps.push_back(this->CMakeCacheFile); std::sort(implicitDeps.begin(), implicitDeps.end()); implicitDeps.erase(std::unique(implicitDeps.begin(), implicitDeps.end()), @@ -1184,9 +1205,10 @@ void cmGlobalNinjaGenerator::WriteTargetRebuildManifest(std::ostream& os) variables["pool"] = "console"; } + std::string const ninjaBuildFile = this->NinjaOutputPath(NINJA_BUILD_FILE); this->WriteBuild(os, "Re-run CMake if any of its inputs changed.", "RERUN_CMAKE", - /*outputs=*/cmNinjaDeps(1, NINJA_BUILD_FILE), + /*outputs=*/cmNinjaDeps(1, ninjaBuildFile), /*explicitDeps=*/cmNinjaDeps(), implicitDeps, /*orderOnlyDeps=*/cmNinjaDeps(), variables); @@ -1223,7 +1245,7 @@ void cmGlobalNinjaGenerator::WriteTargetClean(std::ostream& os) /*restat=*/"", /*generator=*/false); WriteBuild(os, "Clean all the built files.", "CLEAN", - /*outputs=*/cmNinjaDeps(1, "clean"), + /*outputs=*/cmNinjaDeps(1, this->NinjaOutputPath("clean")), /*explicitDeps=*/cmNinjaDeps(), /*implicitDeps=*/cmNinjaDeps(), /*orderOnlyDeps=*/cmNinjaDeps(), @@ -1242,9 +1264,35 @@ void cmGlobalNinjaGenerator::WriteTargetHelp(std::ostream& os) /*restat=*/"", /*generator=*/false); WriteBuild(os, "Print all primary targets available.", "HELP", - /*outputs=*/cmNinjaDeps(1, "help"), + /*outputs=*/cmNinjaDeps(1, this->NinjaOutputPath("help")), /*explicitDeps=*/cmNinjaDeps(), /*implicitDeps=*/cmNinjaDeps(), /*orderOnlyDeps=*/cmNinjaDeps(), /*variables=*/cmNinjaVars()); } + +void cmGlobalNinjaGenerator::InitOutputPathPrefix() +{ + this->OutputPathPrefix = + this->LocalGenerators[0]->GetMakefile()->GetSafeDefinition( + "CMAKE_NINJA_OUTPUT_PATH_PREFIX"); + EnsureTrailingSlash(this->OutputPathPrefix); +} + +std::string cmGlobalNinjaGenerator::NinjaOutputPath(std::string const& path) +{ + if (!this->HasOutputPathPrefix() || cmSystemTools::FileIsFullPath(path)) { + return path; + } + return this->OutputPathPrefix + path; +} + +void cmGlobalNinjaGenerator::StripNinjaOutputPathPrefixAsSuffix( + std::string& path) +{ + if (path.empty()) { + return; + } + EnsureTrailingSlash(path); + cmStripSuffixIfExists(path, this->OutputPathPrefix); +} diff --git a/Source/cmGlobalNinjaGenerator.h b/Source/cmGlobalNinjaGenerator.h index 881104fa9..6d9bfe835 100644 --- a/Source/cmGlobalNinjaGenerator.h +++ b/Source/cmGlobalNinjaGenerator.h @@ -314,6 +314,10 @@ public: static std::string RequiredNinjaVersionForConsolePool() { return "1.5"; } bool SupportsConsolePool() const; + std::string NinjaOutputPath(std::string const& path); + bool HasOutputPathPrefix() const { return !this->OutputPathPrefix.empty(); } + void StripNinjaOutputPathPrefixAsSuffix(std::string& path); + protected: virtual void Generate(); @@ -397,6 +401,13 @@ private: std::string NinjaCommand; std::string NinjaVersion; + +private: + void InitOutputPathPrefix(); + + std::string OutputPathPrefix; + std::string TargetAll; + std::string CMakeCacheFile; }; #endif // ! cmGlobalNinjaGenerator_h diff --git a/Source/cmLocalNinjaGenerator.cxx b/Source/cmLocalNinjaGenerator.cxx index 671d8a089..c7e1a90f8 100644 --- a/Source/cmLocalNinjaGenerator.cxx +++ b/Source/cmLocalNinjaGenerator.cxx @@ -123,7 +123,8 @@ cmGlobalNinjaGenerator* cmLocalNinjaGenerator::GetGlobalNinjaGenerator() std::string cmLocalNinjaGenerator::ConvertToLinkReference( std::string const& lib, OutputFormat format) { - return this->Convert(lib, HOME_OUTPUT, format); + std::string path = this->GetGlobalNinjaGenerator()->ConvertToNinjaPath(lib); + return this->ConvertToOutputFormat(path, format); } std::string cmLocalNinjaGenerator::ConvertToIncludeReference( @@ -224,8 +225,13 @@ void cmLocalNinjaGenerator::WriteNinjaFilesInclusion(std::ostream& os) cmGlobalNinjaGenerator::WriteDivider(os); os << "# Include auxiliary files.\n" << "\n"; - cmGlobalNinjaGenerator::WriteInclude( - os, cmGlobalNinjaGenerator::NINJA_RULES_FILE, "Include rules file."); + cmGlobalNinjaGenerator* ng = this->GetGlobalNinjaGenerator(); + std::string const ninjaRulesFile = + ng->NinjaOutputPath(cmGlobalNinjaGenerator::NINJA_RULES_FILE); + std::string const rulesFilePath = + ng->EncodeIdent(ng->EncodePath(ninjaRulesFile), os); + cmGlobalNinjaGenerator::WriteInclude(os, rulesFilePath, + "Include rules file."); os << "\n"; } diff --git a/Source/cmNinjaNormalTargetGenerator.cxx b/Source/cmNinjaNormalTargetGenerator.cxx index 831d44b9b..8d7a89272 100644 --- a/Source/cmNinjaNormalTargetGenerator.cxx +++ b/Source/cmNinjaNormalTargetGenerator.cxx @@ -652,7 +652,8 @@ void cmNinjaNormalTargetGenerator::WriteLinkStatement() cmNinjaDeps(1, targetOutputReal), emptyDeps, emptyDeps, symlinkVars); } else { cmNinjaDeps symlinks; - const std::string soName = this->GetTargetFilePath(this->TargetNameSO); + std::string const soName = + this->ConvertToNinjaPath(this->GetTargetFilePath(this->TargetNameSO)); // If one link has to be created. if (targetOutputReal == soName || targetOutput == soName) { symlinkVars["SONAME"] = soName; diff --git a/Source/cmNinjaTargetGenerator.cxx b/Source/cmNinjaTargetGenerator.cxx index c214c8a4a..4d58242a5 100644 --- a/Source/cmNinjaTargetGenerator.cxx +++ b/Source/cmNinjaTargetGenerator.cxx @@ -521,8 +521,10 @@ void cmNinjaTargetGenerator::WriteObjectBuildStatement( std::string const language = source->GetLanguage(); std::string const sourceFileName = language == "RC" ? source->GetFullPath() : this->GetSourceFilePath(source); - std::string const objectDir = this->GeneratorTarget->GetSupportDirectory(); - std::string const objectFileName = this->GetObjectFilePath(source); + std::string const objectDir = + this->ConvertToNinjaPath(this->GeneratorTarget->GetSupportDirectory()); + std::string const objectFileName = + this->ConvertToNinjaPath(this->GetObjectFilePath(source)); std::string const objectFileDir = cmSystemTools::GetFilenamePath(objectFileName); @@ -582,9 +584,9 @@ void cmNinjaTargetGenerator::WriteObjectBuildStatement( EnsureParentDirectoryExists(objectFileName); vars["OBJECT_DIR"] = this->GetLocalGenerator()->ConvertToOutputFormat( - ConvertToNinjaPath(objectDir), cmLocalGenerator::SHELL); + objectDir, cmLocalGenerator::SHELL); vars["OBJECT_FILE_DIR"] = this->GetLocalGenerator()->ConvertToOutputFormat( - ConvertToNinjaPath(objectFileDir), cmLocalGenerator::SHELL); + objectFileDir, cmLocalGenerator::SHELL); this->addPoolNinjaVariable("JOB_POOL_COMPILE", this->GetGeneratorTarget(), vars); @@ -667,10 +669,12 @@ void cmNinjaTargetGenerator::EnsureDirectoryExists( if (cmSystemTools::FileIsFullPath(path.c_str())) { cmSystemTools::MakeDirectory(path.c_str()); } else { - const std::string fullPath = std::string(this->GetGlobalGenerator() - ->GetCMakeInstance() - ->GetHomeOutputDirectory()) + - "/" + path; + cmGlobalNinjaGenerator* gg = this->GetGlobalGenerator(); + std::string fullPath = + std::string(gg->GetCMakeInstance()->GetHomeOutputDirectory()); + // Also ensures their is a trailing slash. + gg->StripNinjaOutputPathPrefixAsSuffix(fullPath); + fullPath += path; cmSystemTools::MakeDirectory(fullPath.c_str()); } } diff --git a/Source/cmNinjaUtilityTargetGenerator.cxx b/Source/cmNinjaUtilityTargetGenerator.cxx index b0b11476e..d07341c80 100644 --- a/Source/cmNinjaUtilityTargetGenerator.cxx +++ b/Source/cmNinjaUtilityTargetGenerator.cxx @@ -33,6 +33,8 @@ void cmNinjaUtilityTargetGenerator::Generate() { std::string utilCommandName = cmake::GetCMakeFilesDirectoryPostSlash(); utilCommandName += this->GetTargetName() + ".util"; + utilCommandName = + this->GetGlobalGenerator()->NinjaOutputPath(utilCommandName); std::vector commands; cmNinjaDeps deps, outputs, util_outputs(1, utilCommandName); diff --git a/Tests/RunCMake/Ninja/CheckNoPrefixSubDir.cmake b/Tests/RunCMake/Ninja/CheckNoPrefixSubDir.cmake new file mode 100644 index 000000000..b845f2f4a --- /dev/null +++ b/Tests/RunCMake/Ninja/CheckNoPrefixSubDir.cmake @@ -0,0 +1,7 @@ +add_custom_target(check_no_prefix_sub_dir ALL + COMMAND "${CMAKE_COMMAND}" + "-DNINJA_OUTPUT_PATH_PREFIX=${CMAKE_NINJA_OUTPUT_PATH_PREFIX}" + "-DCUR_BIN_DIR=${CMAKE_CURRENT_BINARY_DIR}" + -P "${CMAKE_CURRENT_SOURCE_DIR}/CheckNoPrefixSubDirScript.cmake" + VERBATIM + ) diff --git a/Tests/RunCMake/Ninja/CheckNoPrefixSubDirScript.cmake b/Tests/RunCMake/Ninja/CheckNoPrefixSubDirScript.cmake new file mode 100644 index 000000000..5a03fcb7e --- /dev/null +++ b/Tests/RunCMake/Ninja/CheckNoPrefixSubDirScript.cmake @@ -0,0 +1,8 @@ +# Check that the prefix sub-directory is not repeated. + +if(EXISTS "${CUR_BIN_DIR}/${NINJA_OUTPUT_PATH_PREFIX}") + message(FATAL_ERROR + "no sub directory named after the CMAKE_NINJA_OUTPUT_PATH_PREFIX " + "should be in the binary directory." + ) +endif() diff --git a/Tests/RunCMake/Ninja/CheckOutput.cmake b/Tests/RunCMake/Ninja/CheckOutput.cmake new file mode 100644 index 000000000..ddb35a75e --- /dev/null +++ b/Tests/RunCMake/Ninja/CheckOutput.cmake @@ -0,0 +1,23 @@ +# Add rules to check the generated executable works. + +set(hello_output "${CMAKE_CURRENT_BINARY_DIR}/hello.output") +add_custom_command( + OUTPUT "${hello_output}" + COMMAND "$" > "${hello_output}" + DEPENDS hello + VERBATIM + ) + +if(NOT DEFINED HELLO_OUTPUT_STRING) + set(HELLO_OUTPUT_STRING "Hello world!\n") +endif() + +set(hello_output_ref "${CMAKE_CURRENT_BINARY_DIR}/hello.output.ref") +file(WRITE "${hello_output_ref}" "${HELLO_OUTPUT_STRING}") + +add_custom_target(check_output ALL + COMMAND "${CMAKE_COMMAND}" -E compare_files + "${hello_output}" "${hello_output_ref}" + DEPENDS "${hello_output}" "${hello_output_ref}" + VERBATIM + ) diff --git a/Tests/RunCMake/Ninja/CustomCommandWorkingDirectory.cmake b/Tests/RunCMake/Ninja/CustomCommandWorkingDirectory.cmake new file mode 100644 index 000000000..8e01c8c65 --- /dev/null +++ b/Tests/RunCMake/Ninja/CustomCommandWorkingDirectory.cmake @@ -0,0 +1,13 @@ +cmake_minimum_required(VERSION 3.5) +project(hello NONE) + +add_custom_command( + OUTPUT hello.copy.c + COMMAND "${CMAKE_COMMAND}" -E copy + "${CMAKE_CURRENT_SOURCE_DIR}/hello.c" + hello.copy.c + WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" + ) +add_custom_target(copy ALL DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/hello.copy.c") + +include(CheckNoPrefixSubDir.cmake) diff --git a/Tests/RunCMake/Ninja/Executable.cmake b/Tests/RunCMake/Ninja/Executable.cmake new file mode 100644 index 000000000..4e17d6889 --- /dev/null +++ b/Tests/RunCMake/Ninja/Executable.cmake @@ -0,0 +1,5 @@ +cmake_minimum_required(VERSION 3.5) +project(hello C) +add_executable(hello hello.c) +include(CheckOutput.cmake) +include(CheckNoPrefixSubDir.cmake) diff --git a/Tests/RunCMake/Ninja/RunCMakeTest.cmake b/Tests/RunCMake/Ninja/RunCMakeTest.cmake index 4e068882d..c73f852ae 100644 --- a/Tests/RunCMake/Ninja/RunCMakeTest.cmake +++ b/Tests/RunCMake/Ninja/RunCMakeTest.cmake @@ -1,5 +1,20 @@ include(RunCMake) +# Detect ninja version so we know what tests can be supported. +execute_process( + COMMAND "${RunCMake_MAKE_PROGRAM}" --version + OUTPUT_VARIABLE ninja_out + ERROR_VARIABLE ninja_out + RESULT_VARIABLE ninja_res + OUTPUT_STRIP_TRAILING_WHITESPACE + ) +if(ninja_res EQUAL 0 AND "x${ninja_out}" MATCHES "^x[0-9]+\\.[0-9]+") + set(ninja_version "${ninja_out}") + message(STATUS "ninja version: ${ninja_version}") +else() + message(FATAL_ERROR "'ninja --version' reported:\n${ninja_out}") +endif() + function(run_CMP0058 case) # Use a single build tree for a few tests without cleaning. set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/CMP0058-${case}-build) @@ -32,3 +47,153 @@ function(run_SubDir) run_cmake_command(SubDir-build ${CMAKE_COMMAND} --build . --target ${SubDir_all}) endfunction() run_SubDir() + +function(run_ninja dir) + execute_process( + COMMAND "${RunCMake_MAKE_PROGRAM}" + WORKING_DIRECTORY "${dir}" + OUTPUT_VARIABLE ninja_stdout + ERROR_VARIABLE ninja_stderr + RESULT_VARIABLE ninja_result + ) + if(NOT ninja_result EQUAL 0) + message(STATUS " +============ beginning of ninja's stdout ============ +${ninja_stdout} +=============== end of ninja's stdout =============== +") + message(STATUS " +============ beginning of ninja's stderr ============ +${ninja_stderr} +=============== end of ninja's stderr =============== +") + message(FATAL_ERROR + "top ninja build failed exited with status ${ninja_result}") + endif() +endfunction(run_ninja) + +function(sleep delay) + execute_process( + COMMAND ${CMAKE_COMMAND} -E sleep ${delay} + RESULT_VARIABLE result + ) + if(NOT result EQUAL 0) + message(FATAL_ERROR "failed to sleep for ${delay} second.") + endif() +endfunction(sleep) + +function(touch path) + execute_process( + COMMAND ${CMAKE_COMMAND} -E touch ${path} + RESULT_VARIABLE result + ) + if(NOT result EQUAL 0) + message(FATAL_ERROR "failed to touch main ${path} file.") + endif() +endfunction(touch) + +macro(ninja_escape_path path out) + string(REPLACE "\$ " "\$\$" "${out}" "${path}") + string(REPLACE " " "\$ " "${out}" "${${out}}") + string(REPLACE ":" "\$:" "${out}" "${${out}}") +endmacro(ninja_escape_path) + +macro(shell_escape string out) + string(REPLACE "\"" "\\\"" "${out}" "${string}") +endmacro(shell_escape) + +function(run_sub_cmake test ninja_output_path_prefix) + set(top_build_dir "${RunCMake_BINARY_DIR}/${test}-build/") + file(REMOVE_RECURSE "${top_build_dir}") + file(MAKE_DIRECTORY "${top_build_dir}") + + ninja_escape_path("${ninja_output_path_prefix}" + escaped_ninja_output_path_prefix) + + # Generate top build ninja file. + set(top_build_ninja "${top_build_dir}/build.ninja") + shell_escape("${top_build_ninja}" escaped_top_build_ninja) + set(build_ninja_dep "${top_build_dir}/build_ninja_dep") + ninja_escape_path("${build_ninja_dep}" escaped_build_ninja_dep) + shell_escape("${CMAKE_COMMAND}" escaped_CMAKE_COMMAND) + file(WRITE "${build_ninja_dep}" "fake dependency of top build.ninja file\n") + if(WIN32) + set(cmd_prefix "cmd.exe /C \"") + set(cmd_suffix "\"") + else() + set(cmd_prefix "") + set(cmd_suffix "") + endif() + file(WRITE "${top_build_ninja}" "\ +subninja ${escaped_ninja_output_path_prefix}/build.ninja +default ${escaped_ninja_output_path_prefix}/all + +# Sleep for 1 second before to regenerate to make sure the timestamp of +# the top build.ninja will be strictly greater than the timestamp of the +# sub/build.ninja file. We assume the system as 1 sec timestamp resolution. +rule RERUN + command = ${cmd_prefix}\"${escaped_CMAKE_COMMAND}\" -E sleep 1 && \"${escaped_CMAKE_COMMAND}\" -E touch \"${escaped_top_build_ninja}\"${cmd_suffix} + description = Testing regeneration + generator = 1 + +build build.ninja: RERUN ${escaped_build_ninja_dep} || ${escaped_ninja_output_path_prefix}/build.ninja + pool = console +") + + # Run sub cmake project. + set(RunCMake_TEST_OPTIONS "-DCMAKE_NINJA_OUTPUT_PATH_PREFIX=${ninja_output_path_prefix}") + set(RunCMake_TEST_BINARY_DIR "${top_build_dir}/${ninja_output_path_prefix}") + run_cmake(${test}) + + # Check there is no 'default' statement in Ninja file generated by CMake. + set(sub_build_ninja "${RunCMake_TEST_BINARY_DIR}/build.ninja") + file(READ "${sub_build_ninja}" sub_build_ninja_file) + if(sub_build_ninja_file MATCHES "\ndefault [^\n][^\n]*all\n") + message(FATAL_ERROR + "unexpected 'default' statement found in '${sub_build_ninja}'") + endif() + + # Run ninja from the top build directory. + run_ninja("${top_build_dir}") + + # Test regeneration rules run in order. + set(main_cmakelists "${RunCMake_SOURCE_DIR}/CMakeLists.txt") + sleep(1) # Assume the system as 1 sec timestamp resolution. + touch("${main_cmakelists}") + touch("${build_ninja_dep}") + run_ninja("${top_build_dir}") + file(TIMESTAMP "${main_cmakelists}" mtime_main_cmakelists UTC) + file(TIMESTAMP "${sub_build_ninja}" mtime_sub_build_ninja UTC) + file(TIMESTAMP "${top_build_ninja}" mtime_top_build_ninja UTC) + + # Check sub build.ninja is regenerated. + if(mtime_main_cmakelists STRGREATER mtime_sub_build_ninja) + message(FATAL_ERROR + "sub build.ninja not regenerated: + CMakeLists.txt = ${mtime_main_cmakelists} + sub/build.ninja = ${mtime_sub_build_ninja}") + endif() + + # Check top build.ninja is regenerated after sub build.ninja. + if(NOT mtime_top_build_ninja STRGREATER mtime_sub_build_ninja) + message(FATAL_ERROR + "top build.ninja not regenerated strictly after sub build.ninja: + sub/build.ninja = ${mtime_sub_build_ninja} + build.ninja = ${mtime_top_build_ninja}") + endif() + +endfunction(run_sub_cmake) + +if("${ninja_version}" VERSION_LESS 1.6) + message(WARNING "Ninja is too old; skipping rest of test.") + return() +endif() + +foreach(ninja_output_path_prefix "sub space" "sub") + run_sub_cmake(Executable "${ninja_output_path_prefix}") + run_sub_cmake(StaticLib "${ninja_output_path_prefix}") + run_sub_cmake(SharedLib "${ninja_output_path_prefix}") + run_sub_cmake(TwoLibs "${ninja_output_path_prefix}") + run_sub_cmake(SubDirPrefix "${ninja_output_path_prefix}") + run_sub_cmake(CustomCommandWorkingDirectory "${ninja_output_path_prefix}") +endforeach(ninja_output_path_prefix) diff --git a/Tests/RunCMake/Ninja/SharedLib.cmake b/Tests/RunCMake/Ninja/SharedLib.cmake new file mode 100644 index 000000000..1a78390e3 --- /dev/null +++ b/Tests/RunCMake/Ninja/SharedLib.cmake @@ -0,0 +1,8 @@ +cmake_minimum_required(VERSION 3.5) +project(hello C) +add_library(greeting SHARED greeting.c) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}) +add_executable(hello hello_with_greeting.c) +target_link_libraries(hello greeting) +include(CheckOutput.cmake) +include(CheckNoPrefixSubDir.cmake) diff --git a/Tests/RunCMake/Ninja/StaticLib.cmake b/Tests/RunCMake/Ninja/StaticLib.cmake new file mode 100644 index 000000000..0f815ae5e --- /dev/null +++ b/Tests/RunCMake/Ninja/StaticLib.cmake @@ -0,0 +1,9 @@ +cmake_minimum_required(VERSION 3.5) +project(hello C) +add_definitions(-DGREETING_STATIC) +add_library(greeting STATIC greeting.c) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}) +add_executable(hello hello_with_greeting.c) +target_link_libraries(hello greeting) +include(CheckOutput.cmake) +include(CheckNoPrefixSubDir.cmake) diff --git a/Tests/RunCMake/Ninja/SubDirPrefix.cmake b/Tests/RunCMake/Ninja/SubDirPrefix.cmake new file mode 100644 index 000000000..30ad1e607 --- /dev/null +++ b/Tests/RunCMake/Ninja/SubDirPrefix.cmake @@ -0,0 +1,8 @@ +cmake_minimum_required(VERSION 3.5) +project(hello C) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/bin") +add_subdirectory(SubDirPrefix) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}) +add_executable(hello hello_sub_greeting.c) +target_link_libraries(hello greeting) +include(CheckOutput.cmake) diff --git a/Tests/RunCMake/Ninja/SubDirPrefix/CMakeLists.txt b/Tests/RunCMake/Ninja/SubDirPrefix/CMakeLists.txt new file mode 100644 index 000000000..575597d48 --- /dev/null +++ b/Tests/RunCMake/Ninja/SubDirPrefix/CMakeLists.txt @@ -0,0 +1,2 @@ +add_library(greeting SHARED greeting.c) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}) diff --git a/Tests/RunCMake/Ninja/SubDirPrefix/greeting.c b/Tests/RunCMake/Ninja/SubDirPrefix/greeting.c new file mode 100644 index 000000000..ba3e55beb --- /dev/null +++ b/Tests/RunCMake/Ninja/SubDirPrefix/greeting.c @@ -0,0 +1,9 @@ +#include + +#if defined(_WIN32) && !defined(GREETING_STATIC) +__declspec(dllexport) +#endif + void greeting(void) +{ + printf("Hello world!\n"); +} diff --git a/Tests/RunCMake/Ninja/SubDirPrefix/greeting.h b/Tests/RunCMake/Ninja/SubDirPrefix/greeting.h new file mode 100644 index 000000000..ee4053ef7 --- /dev/null +++ b/Tests/RunCMake/Ninja/SubDirPrefix/greeting.h @@ -0,0 +1,4 @@ +#if defined(_WIN32) && !defined(GREETING_STATIC) +__declspec(dllimport) +#endif + void greeting(void); diff --git a/Tests/RunCMake/Ninja/TwoLibs.cmake b/Tests/RunCMake/Ninja/TwoLibs.cmake new file mode 100644 index 000000000..666452f34 --- /dev/null +++ b/Tests/RunCMake/Ninja/TwoLibs.cmake @@ -0,0 +1,17 @@ +cmake_minimum_required(VERSION 3.5) +project(hello C) + +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin") +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib-static") +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib") + +add_library(greeting SHARED greeting.c) +add_library(greeting2 STATIC greeting2.c) +include_directories(${CMAKE_CURRENT_SOURCE_DIR}) +add_executable(hello hello_with_two_greetings.c) +target_link_libraries(hello greeting greeting2) + +set(HELLO_OUTPUT_STRING "Hello world!\nHello world 2!\n") +include(CheckOutput.cmake) + +include(CheckNoPrefixSubDir.cmake) diff --git a/Tests/RunCMake/Ninja/greeting.c b/Tests/RunCMake/Ninja/greeting.c new file mode 100644 index 000000000..ba3e55beb --- /dev/null +++ b/Tests/RunCMake/Ninja/greeting.c @@ -0,0 +1,9 @@ +#include + +#if defined(_WIN32) && !defined(GREETING_STATIC) +__declspec(dllexport) +#endif + void greeting(void) +{ + printf("Hello world!\n"); +} diff --git a/Tests/RunCMake/Ninja/greeting.h b/Tests/RunCMake/Ninja/greeting.h new file mode 100644 index 000000000..ee4053ef7 --- /dev/null +++ b/Tests/RunCMake/Ninja/greeting.h @@ -0,0 +1,4 @@ +#if defined(_WIN32) && !defined(GREETING_STATIC) +__declspec(dllimport) +#endif + void greeting(void); diff --git a/Tests/RunCMake/Ninja/greeting2.c b/Tests/RunCMake/Ninja/greeting2.c new file mode 100644 index 000000000..c6ed87daa --- /dev/null +++ b/Tests/RunCMake/Ninja/greeting2.c @@ -0,0 +1,6 @@ +#include + +void greeting2(void) +{ + printf("Hello world 2!\n"); +} diff --git a/Tests/RunCMake/Ninja/greeting2.h b/Tests/RunCMake/Ninja/greeting2.h new file mode 100644 index 000000000..c4d8f9b29 --- /dev/null +++ b/Tests/RunCMake/Ninja/greeting2.h @@ -0,0 +1 @@ +void greeting2(void); diff --git a/Tests/RunCMake/Ninja/hello.c b/Tests/RunCMake/Ninja/hello.c new file mode 100644 index 000000000..aac8b4e0f --- /dev/null +++ b/Tests/RunCMake/Ninja/hello.c @@ -0,0 +1,7 @@ +#include + +int main(void) +{ + printf("Hello world!\n"); + return 0; +} diff --git a/Tests/RunCMake/Ninja/hello_sub_greeting.c b/Tests/RunCMake/Ninja/hello_sub_greeting.c new file mode 100644 index 000000000..fc4bd60f1 --- /dev/null +++ b/Tests/RunCMake/Ninja/hello_sub_greeting.c @@ -0,0 +1,7 @@ +#include + +int main(void) +{ + greeting(); + return 0; +} diff --git a/Tests/RunCMake/Ninja/hello_with_greeting.c b/Tests/RunCMake/Ninja/hello_with_greeting.c new file mode 100644 index 000000000..dbf90e314 --- /dev/null +++ b/Tests/RunCMake/Ninja/hello_with_greeting.c @@ -0,0 +1,7 @@ +#include + +int main(void) +{ + greeting(); + return 0; +} diff --git a/Tests/RunCMake/Ninja/hello_with_two_greetings.c b/Tests/RunCMake/Ninja/hello_with_two_greetings.c new file mode 100644 index 000000000..cbbdaadc0 --- /dev/null +++ b/Tests/RunCMake/Ninja/hello_with_two_greetings.c @@ -0,0 +1,9 @@ +#include +#include + +int main(void) +{ + greeting(); + greeting2(); + return 0; +}