Merge topic 'try_compile-source-from'

611d801790 try_compile: Add SOURCE_FROM_FILE
a04eaf6742 Tests: Clean up and simplify TryCompile tests
cb14ae2b87 try_compile: Add SOURCE_FROM_{ARG,VAR}

Acked-by: Kitware Robot <kwrobot@kitware.com>
Acked-by: buildbot <buildbot@kitware.com>
Merge-request: !7700
This commit is contained in:
Brad King
2022-09-23 12:52:02 +00:00
committed by Kitware Robot
20 changed files with 269 additions and 48 deletions
+42 -5
View File
@@ -55,7 +55,11 @@ Try Compiling Source Files
.. code-block:: cmake
try_compile(<resultVar> SOURCES <srcfile...>
try_compile(<resultVar>
<SOURCES <srcfile...>] |
SOURCE_FROM_ARG <name> <content>] |
SOURCE_FROM_VAR <name> <var>] |
SOURCE_FROM_FILE <name> <path> >...
[CMAKE_FLAGS <flags>...]
[COMPILE_DEFINITIONS <defs>...]
[LINK_OPTIONS <options>...]
@@ -74,10 +78,12 @@ Try building an executable or static library from one or more source files
variable). The success or failure of the ``try_compile``, i.e. ``TRUE`` or
``FALSE`` respectively, is returned in ``<resultVar>``.
In this form, one or more source files must be provided. If
:variable:`CMAKE_TRY_COMPILE_TARGET_TYPE` is unset or is set to ``EXECUTABLE``,
the sources must include a definition for ``main`` and CMake will create a
``CMakeLists.txt`` file to build the source(s) as an executable.
In this form, one or more source files must be provided. Additionally, one of
``SOURCES`` and/or ``SOURCE_FROM_*`` must precede other keywords.
If :variable:`CMAKE_TRY_COMPILE_TARGET_TYPE` is unset or is set to
``EXECUTABLE``, the sources must include a definition for ``main`` and CMake
will create a ``CMakeLists.txt`` file to build the source(s) as an executable.
If :variable:`CMAKE_TRY_COMPILE_TARGET_TYPE` is set to ``STATIC_LIBRARY``,
a static library will be built instead and no definition for ``main`` is
required. For an executable, the generated ``CMakeLists.txt`` file would
@@ -163,6 +169,37 @@ The options are:
``OUTPUT_VARIABLE <var>``
Store the output from the build process in the given variable.
``SOURCE_FROM_ARG <name> <content>``
.. versionadded:: 3.25
Write ``<content>`` to a file named ``<name>`` in the operation directory.
This can be used to bypass the need to separately write a source file when
the contents of the file are dynamically specified. The specified ``<name>``
is not allowed to contain path components.
``SOURCE_FROM_ARG`` may be specified multiple times.
``SOURCE_FROM_FILE <name> <path>``
.. versionadded:: 3.25
Copy ``<path>`` to a file named ``<name>`` in the operation directory. This
can be used to consolidate files into the operation directory, which may be
useful if a source which already exists (i.e. as a stand-alone file in a
project's source repository) needs to refer to other file(s) created by
``SOURCE_FROM_*``. (Otherwise, ``SOURCES`` is usually more convenient.) The
specified ``<name>`` is not allowed to contain path components.
``SOURCE_FROM_VAR <name> <content>``
.. versionadded:: 3.25
Write the contents of ``<var>`` to a file named ``<name>`` in the operation
directory. This is the same as ``SOURCE_FROM_ARG``, but takes the contents
from the specified CMake variable, rather than directly, which may be useful
when passing arguments through a function which wraps ``try_compile``. The
specified ``<name>`` is not allowed to contain path components.
``SOURCE_FROM_VAR`` may be specified multiple times.
``<LANG>_STANDARD <std>``
.. versionadded:: 3.8
+8 -1
View File
@@ -12,7 +12,11 @@ Try Compiling and Running Source Files
.. code-block:: cmake
try_run(<runResultVar> <compileResultVar> SOURCES <srcfile...>
try_run(<runResultVar> <compileResultVar>
<SOURCES <srcfile...>] |
SOURCE_FROM_ARG <name> <content>] |
SOURCE_FROM_VAR <name> <var>] |
SOURCE_FROM_FILE <name> <path> >...
[CMAKE_FLAGS <flags>...]
[COMPILE_DEFINITIONS <defs>...]
[LINK_OPTIONS <options>...]
@@ -40,6 +44,9 @@ set to ``FAILED_TO_RUN``. See the :command:`try_compile` command for
documentation of options common to both commands, and for information on how
the test project is constructed to build the source file.
One or more source files must be provided. Additionally, one of ``SOURCES``
and/or ``SOURCE_FROM_*`` must precede other keywords.
This command also supports an alternate signature
which was present in older versions of CMake:
+116 -8
View File
@@ -153,7 +153,7 @@ auto const TryCompileBaseArgParser =
.Bind("__CMAKE_INTERNAL"_s, &Arguments::CMakeInternal)
/* keep semicolon on own line */;
auto const TryCompileBaseNonProjectArgParser =
auto const TryCompileBaseSourcesArgParser =
cmArgumentParser<Arguments>{ TryCompileBaseArgParser }
.Bind("SOURCES"_s, &Arguments::Sources)
.Bind("COMPILE_DEFINITIONS"_s, TryCompileCompileDefs,
@@ -170,6 +170,13 @@ auto const TryCompileBaseNonProjectArgParser =
.BIND_LANG_PROPS(OBJCXX)
/* keep semicolon on own line */;
auto const TryCompileBaseNewSourcesArgParser =
cmArgumentParser<Arguments>{ TryCompileBaseSourcesArgParser }
.Bind("SOURCE_FROM_ARG"_s, &Arguments::SourceFromArg)
.Bind("SOURCE_FROM_VAR"_s, &Arguments::SourceFromVar)
.Bind("SOURCE_FROM_FILE"_s, &Arguments::SourceFromFile)
/* keep semicolon on own line */;
auto const TryCompileBaseProjectArgParser =
cmArgumentParser<Arguments>{ TryCompileBaseArgParser }
.Bind("PROJECT"_s, &Arguments::ProjectName)
@@ -182,10 +189,10 @@ auto const TryCompileProjectArgParser =
makeTryCompileParser(TryCompileBaseProjectArgParser);
auto const TryCompileSourcesArgParser =
makeTryCompileParser(TryCompileBaseNonProjectArgParser);
makeTryCompileParser(TryCompileBaseNewSourcesArgParser);
auto const TryCompileOldArgParser =
makeTryCompileParser(TryCompileBaseNonProjectArgParser)
makeTryCompileParser(TryCompileBaseSourcesArgParser)
.Bind(1, &Arguments::BinaryDirectory)
.Bind(2, &Arguments::SourceDirectoryOrFile)
.Bind(3, &Arguments::ProjectName)
@@ -193,7 +200,7 @@ auto const TryCompileOldArgParser =
/* keep semicolon on own line */;
auto const TryRunSourcesArgParser =
makeTryRunParser(TryCompileBaseNonProjectArgParser);
makeTryRunParser(TryCompileBaseNewSourcesArgParser);
auto const TryRunOldArgParser = makeTryRunParser(TryCompileOldArgParser);
@@ -393,8 +400,27 @@ bool cmCoreTryCompile::TryCompileCode(Arguments& arguments,
return false;
}
// only valid for srcfile signatures
if (!this->SrcFileSignature) {
if (this->SrcFileSignature) {
if (arguments.SourceFromArg && arguments.SourceFromArg->size() % 2) {
this->Makefile->IssueMessage(
MessageType::FATAL_ERROR,
"SOURCE_FROM_ARG requires exactly two arguments");
return false;
}
if (arguments.SourceFromVar && arguments.SourceFromVar->size() % 2) {
this->Makefile->IssueMessage(
MessageType::FATAL_ERROR,
"SOURCE_FROM_VAR requires exactly two arguments");
return false;
}
if (arguments.SourceFromFile && arguments.SourceFromFile->size() % 2) {
this->Makefile->IssueMessage(
MessageType::FATAL_ERROR,
"SOURCE_FROM_FILE requires exactly two arguments");
return false;
}
} else {
// only valid for srcfile signatures
if (!arguments.LangProps.empty()) {
this->Makefile->IssueMessage(
MessageType::FATAL_ERROR,
@@ -415,6 +441,7 @@ bool cmCoreTryCompile::TryCompileCode(Arguments& arguments,
return false;
}
}
// make sure the binary directory exists
if (useUniqueBinaryDirectory) {
this->BinaryDirectory =
@@ -445,10 +472,60 @@ bool cmCoreTryCompile::TryCompileCode(Arguments& arguments,
std::vector<std::string> sources;
if (arguments.Sources) {
sources = std::move(*arguments.Sources);
} else {
// TODO: ensure SourceDirectoryOrFile has a value
} else if (arguments.SourceDirectoryOrFile) {
sources.emplace_back(*arguments.SourceDirectoryOrFile);
}
if (arguments.SourceFromArg) {
auto const k = arguments.SourceFromArg->size();
for (auto i = decltype(k){ 0 }; i < k; i += 2) {
const auto& name = (*arguments.SourceFromArg)[i + 0];
const auto& content = (*arguments.SourceFromArg)[i + 1];
auto out = this->WriteSource(name, content, "SOURCES_FROM_ARG");
if (out.empty()) {
return false;
}
sources.emplace_back(std::move(out));
}
}
if (arguments.SourceFromVar) {
auto const k = arguments.SourceFromVar->size();
for (auto i = decltype(k){ 0 }; i < k; i += 2) {
const auto& name = (*arguments.SourceFromVar)[i + 0];
const auto& var = (*arguments.SourceFromVar)[i + 1];
const auto& content = this->Makefile->GetDefinition(var);
auto out = this->WriteSource(name, content, "SOURCES_FROM_VAR");
if (out.empty()) {
return false;
}
sources.emplace_back(std::move(out));
}
}
if (arguments.SourceFromFile) {
auto const k = arguments.SourceFromFile->size();
for (auto i = decltype(k){ 0 }; i < k; i += 2) {
const auto& dst = (*arguments.SourceFromFile)[i + 0];
const auto& src = (*arguments.SourceFromFile)[i + 1];
if (!cmSystemTools::GetFilenamePath(dst).empty()) {
const auto& msg =
cmStrCat("SOURCE_FROM_FILE given invalid filename \"", dst, "\"");
this->Makefile->IssueMessage(MessageType::FATAL_ERROR, msg);
return false;
}
auto dstPath = cmStrCat(this->BinaryDirectory, "/", dst);
auto const result = cmSystemTools::CopyFileAlways(src, dstPath);
if (!result.IsSuccess()) {
const auto& msg = cmStrCat("SOURCE_FROM_FILE failed to copy \"", src,
"\": ", result.GetString());
this->Makefile->IssueMessage(MessageType::FATAL_ERROR, msg);
return false;
}
sources.emplace_back(std::move(dstPath));
}
}
// TODO: ensure sources is not empty
// Detect languages to enable.
cmGlobalGenerator* gg = this->Makefile->GetGlobalGenerator();
@@ -1128,3 +1205,34 @@ void cmCoreTryCompile::FindOutputFile(const std::string& targetName)
this->OutputFile = cmSystemTools::CollapseFullPath(outputFileLocation);
}
std::string cmCoreTryCompile::WriteSource(std::string const& filename,
std::string const& content,
char const* command) const
{
if (!cmSystemTools::GetFilenamePath(filename).empty()) {
const auto& msg =
cmStrCat(command, " given invalid filename \"", filename, "\"");
this->Makefile->IssueMessage(MessageType::FATAL_ERROR, msg);
return {};
}
auto filepath = cmStrCat(this->BinaryDirectory, "/", filename);
cmsys::ofstream file{ filepath.c_str(), std::ios::out };
if (!file) {
const auto& msg =
cmStrCat(command, " failed to open \"", filename, "\" for writing");
this->Makefile->IssueMessage(MessageType::FATAL_ERROR, msg);
return {};
}
file << content;
if (!file) {
const auto& msg = cmStrCat(command, " failed to write \"", filename, "\"");
this->Makefile->IssueMessage(MessageType::FATAL_ERROR, msg);
return {};
}
file.close();
return filepath;
}
+9
View File
@@ -40,6 +40,12 @@ public:
cm::optional<std::string> ProjectName;
cm::optional<std::string> TargetName;
cm::optional<ArgumentParser::NonEmpty<std::vector<std::string>>> Sources;
cm::optional<ArgumentParser::NonEmpty<std::vector<std::string>>>
SourceFromArg;
cm::optional<ArgumentParser::NonEmpty<std::vector<std::string>>>
SourceFromVar;
cm::optional<ArgumentParser::NonEmpty<std::vector<std::string>>>
SourceFromFile;
ArgumentParser::MaybeEmpty<std::vector<std::string>> CMakeFlags{
1, "CMAKE_FLAGS"
}; // fake argv[0]
@@ -103,6 +109,9 @@ public:
cmMakefile* Makefile;
private:
std::string WriteSource(std::string const& name, std::string const& content,
char const* command) const;
Arguments ParseArgs(
const cmRange<std::vector<std::string>::const_iterator>& args,
const cmArgumentParser<Arguments>& parser,
@@ -20,6 +20,11 @@ set(RunCMake_TEST_OPTIONS -Dtry_compile_DEFS=new_signature.cmake)
include(${RunCMake_SOURCE_DIR}/old_and_new_signature_tests.cmake)
unset(RunCMake_TEST_OPTIONS)
run_cmake(SourceFromOneArg)
run_cmake(SourceFromThreeArgs)
run_cmake(SourceFromBadName)
run_cmake(SourceFromBadFile)
run_cmake(ProjectCopyFile)
run_cmake(NonSourceCopyFile)
run_cmake(NonSourceCompileDefinitions)
@@ -0,0 +1 @@
1
@@ -0,0 +1,4 @@
CMake Error at SourceFromBadFile.cmake:[0-9]+ \(try_compile\):
SOURCE_FROM_FILE failed to copy "bad#source.c": No such file or directory
Call Stack \(most recent call first\):
CMakeLists.txt:3 \(include\)
@@ -0,0 +1 @@
try_compile(RESULT SOURCE_FROM_FILE bad.c "bad#source.c")
@@ -0,0 +1 @@
1
@@ -0,0 +1,4 @@
CMake Error at SourceFromBadName.cmake:[0-9]+ \(try_compile\):
SOURCES_FROM_ARG given invalid filename "bad/name.c"
Call Stack \(most recent call first\):
CMakeLists.txt:3 \(include\)
@@ -0,0 +1 @@
try_compile(RESULT SOURCE_FROM_ARG bad/name.c "int main();")
@@ -0,0 +1 @@
1
@@ -0,0 +1,4 @@
CMake Error at SourceFromOneArg.cmake:[0-9]+ \(try_compile\):
SOURCE_FROM_ARG requires exactly two arguments
Call Stack \(most recent call first\):
CMakeLists.txt:3 \(include\)
@@ -0,0 +1 @@
try_compile(RESULT SOURCE_FROM_ARG test.c)
@@ -0,0 +1 @@
1
@@ -0,0 +1,4 @@
CMake Error at SourceFromThreeArgs.cmake:[0-9]+ \(try_compile\):
SOURCE_FROM_ARG requires exactly two arguments
Call Stack \(most recent call first\):
CMakeLists.txt:3 \(include\)
@@ -0,0 +1 @@
try_compile(RESULT SOURCE_FROM_ARG test.c "int" "main();")
+54 -2
View File
@@ -6,13 +6,25 @@ project(TryCompile)
macro(EXPECT_PASS var out)
if(NOT ${var})
message(SEND_ERROR "Should pass failed ${out}")
message(SEND_ERROR "Should pass failed:\n${out}")
endif()
endmacro()
macro(EXPECT_FAIL var out)
if(${var})
message(SEND_ERROR "Should fail passed ${out}")
message(SEND_ERROR "Should fail passed:\n${out}")
endif()
endmacro()
macro(EXPECT_COMPILED name var out)
if(NOT ${var})
message(SEND_ERROR "${name} failed compiling:\n${out}")
endif()
endmacro()
macro(EXPECT_RUN_RESULT name var expected)
if(NOT ${var} EQUAL ${expected})
message(SEND_ERROR " ${name} gave unexpected run result: ${${var}} expected: ${expected}")
endif()
endmacro()
@@ -64,6 +76,46 @@ set(try_compile_compile_output_var COMPILE_OUT)
set(try_compile_run_output_var RUN_OUTPUT)
include(old_and_new_signature_tests.cmake)
# try to compile an empty source specified directly
try_compile(SHOULD_FAIL_DUE_TO_EMPTY_SOURCE
SOURCE_FROM_ARG empty.c "")
if(SHOULD_FAIL_DUE_TO_EMPTY_SOURCE)
message(SEND_ERROR "Trying to compile an empty source succeeded?")
endif()
try_compile(SHOULD_FAIL_DUE_TO_EMPTY_SOURCE
SOURCE_FROM_VAR empty.c NAME_OF_A_VAR_THAT_IS_NOT_SET)
if(SHOULD_FAIL_DUE_TO_EMPTY_SOURCE)
message(SEND_ERROR "Trying to compile an empty source succeeded?")
endif()
# try to compile a copied source
try_compile(SHOULD_PASS
SOURCE_FROM_FILE pass.c ${TryCompile_SOURCE_DIR}/pass.c
OUTPUT_VARIABLE TRY_OUT)
EXPECT_COMPILED("SOURCE_FROM_FILE" SHOULD_PASS "${TRY_OUT}")
# try to run a source specified directly
set(TRY_RUN_MAIN_CODE
"extern int answer(); \n"
"int main() { return answer(); }\n")
set(TRY_RUN_EXT_CODE
"int answer() { return 42; }\n")
try_run(SHOULD_EXIT_WITH_ERROR SHOULD_COMPILE
SOURCE_FROM_ARG main.c "${TRY_RUN_MAIN_CODE}"
SOURCE_FROM_ARG answer.c "${TRY_RUN_EXT_CODE}"
COMPILE_OUTPUT_VARIABLE COMPILE_OUTPUT)
EXPECT_COMPILED("SOURCE_FROM_ARG" SHOULD_COMPILE "${COMPILE_OUTPUT}")
EXPECT_RUN_RESULT("SOURCE_FROM_ARG" SHOULD_EXIT_WITH_ERROR 42)
try_run(SHOULD_EXIT_WITH_ERROR SHOULD_COMPILE
SOURCE_FROM_VAR main.c TRY_RUN_MAIN_CODE
SOURCE_FROM_VAR answer.c TRY_RUN_EXT_CODE
COMPILE_OUTPUT_VARIABLE COMPILE_OUTPUT)
EXPECT_COMPILED("SOURCE_FROM_VAR" SHOULD_COMPILE "${COMPILE_OUTPUT}")
EXPECT_RUN_RESULT("SOURCE_FROM_VAR" SHOULD_EXIT_WITH_ERROR 42)
# try to compile a project (old signature)
message("Testing try_compile project mode (old signature)")
try_compile(TEST_INNER
+1 -1
View File
@@ -3,5 +3,5 @@
int main()
{
printf("hello world\n");
return -1;
return 1;
}
@@ -130,18 +130,14 @@ if(APPLE)
${try_compile_bindir_or_SOURCES}
${TryCompile_SOURCE_DIR}/pass.m
OUTPUT_VARIABLE TRY_OUT)
if(NOT SHOULD_PASS)
message(SEND_ERROR "should pass failed ${TRY_OUT}")
endif()
EXPECT_PASS(SHOULD_PASS "${TRY_OUT}")
# try to compile a file that should not compile
try_compile(SHOULD_FAIL
${try_compile_bindir_or_SOURCES}
${TryCompile_SOURCE_DIR}/fail.m
OUTPUT_VARIABLE TRY_OUT)
if(SHOULD_FAIL)
message(SEND_ERROR "Should fail passed ${TRY_OUT}")
endif()
EXPECT_FAIL(SHOULD_FAIL "${TRY_OUT}")
endif()
######################################
@@ -155,14 +151,9 @@ try_run(SHOULD_RUN SHOULD_COMPILE
${try_compile_bindir_or_SOURCES}
${TryCompile_SOURCE_DIR}/exit_success.c
${try_compile_output_vars})
if(NOT SHOULD_COMPILE)
message(SEND_ERROR
"exit_success failed compiling: ${${try_compile_compile_output_var}}")
endif()
if(NOT "${SHOULD_RUN}" STREQUAL "0")
message(SEND_ERROR
"exit_success failed running with exit code ${SHOULD_RUN}")
endif()
EXPECT_COMPILED("exit_success" SHOULD_COMPILE "${${try_compile_compile_output_var}}")
EXPECT_RUN_RESULT("exit_success" SHOULD_RUN 0)
# check the compile output for the filename
if(NOT "${${try_compile_compile_output_var}}" MATCHES "exit_success")
message(SEND_ERROR
@@ -181,12 +172,8 @@ try_run(ARG_TEST_RUN ARG_TEST_COMPILE
${TryCompile_SOURCE_DIR}/expect_arg.c
COMPILE_OUTPUT_VARIABLE TRY_OUT
ARGS arg1 arg2)
if(NOT ARG_TEST_COMPILE)
message(SEND_ERROR "expect_arg failed compiling: ${TRY_OUT}")
endif()
if(NOT "${ARG_TEST_RUN}" STREQUAL "0")
message(SEND_ERROR "expect_arg failed running with exit code ${ARG_TEST_RUN} ${TRY_OUT}")
endif()
EXPECT_COMPILED("expect_arg" ARG_TEST_COMPILE "${TRY_OUT}")
EXPECT_RUN_RESULT("expect_arg" ARG_TEST_RUN 0)
# try to run a file that should compile and run, but return an error
try_run(SHOULD_EXIT_WITH_ERROR SHOULD_COMPILE
@@ -194,13 +181,8 @@ try_run(SHOULD_EXIT_WITH_ERROR SHOULD_COMPILE
${TryCompile_SOURCE_DIR}/exit_with_error.c
COMPILE_OUTPUT_VARIABLE COMPILE_OUTPUT
RUN_OUTPUT_VARIABLE RUN_OUTPUT)
if(NOT SHOULD_COMPILE)
message(STATUS " exit_with_error failed compiling: ${COMPILE_OUTPUT}")
endif()
if("${SHOULD_EXIT_WITH_ERROR}" STREQUAL "0")
message(SEND_ERROR " exit_with_error passed with exit code ${SHOULD_EXIT_WITH_ERROR}")
endif()
EXPECT_COMPILED("exit_with_error" SHOULD_COMPILE "${COMPILE_OUTPUT}")
EXPECT_RUN_RESULT("exit_with_error" SHOULD_EXIT_WITH_ERROR 1)
# check the compile output, it should contain the filename
if(NOT "${COMPILE_OUTPUT}" MATCHES "exit_with_error")
@@ -224,10 +206,7 @@ try_run(SHOULD_EXIT_WITH_ERROR SHOULD_COMPILE
COMPILE_OUTPUT_VARIABLE COMPILE_OUTPUT
RUN_OUTPUT_STDOUT_VARIABLE RUN_OUTPUT_STDOUT
RUN_OUTPUT_STDERR_VARIABLE RUN_OUTPUT_STDERR)
if(NOT SHOULD_COMPILE)
message(STATUS " exit_with_error failed compiling: ${COMPILE_OUTPUT}")
endif()
EXPECT_PASS(SHOULD_COMPILE "${COMPILE_OUTPUT}")
if(NOT EXISTS "${TryCompile_BINARY_DIR}/CopyOfRun")
message(SEND_ERROR "COPY_FILE to \"${TryCompile_BINARY_DIR}/CopyOfRun\" failed")