mirror of
https://github.com/Kitware/CMake.git
synced 2025-10-16 14:08:35 +08:00
try_compile: Add SOURCE_FROM_{ARG,VAR}
Add ability to "feed" try_compile (and try_run) sources more directly, either from literal content, or from a CMake variable which contains literal content. This saves the user from needing a separate step to write the content to a file, and allows for the sources to only exist in the scratch directory.
This commit is contained in:
@@ -55,7 +55,10 @@ 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>] >...
|
||||
[CMAKE_FLAGS <flags>...]
|
||||
[COMPILE_DEFINITIONS <defs>...]
|
||||
[LINK_OPTIONS <options>...]
|
||||
@@ -74,10 +77,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 +168,27 @@ 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_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
|
||||
|
||||
|
@@ -12,7 +12,10 @@ 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>] >...
|
||||
[CMAKE_FLAGS <flags>...]
|
||||
[COMPILE_DEFINITIONS <defs>...]
|
||||
[LINK_OPTIONS <options>...]
|
||||
@@ -40,6 +43,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:
|
||||
|
||||
|
@@ -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,12 @@ 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)
|
||||
/* keep semicolon on own line */;
|
||||
|
||||
auto const TryCompileBaseProjectArgParser =
|
||||
cmArgumentParser<Arguments>{ TryCompileBaseArgParser }
|
||||
.Bind("PROJECT"_s, &Arguments::ProjectName)
|
||||
@@ -182,10 +188,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)
|
||||
@@ -196,7 +202,7 @@ auto const TryRunProjectArgParser =
|
||||
makeTryRunParser(TryCompileBaseProjectArgParser);
|
||||
|
||||
auto const TryRunSourcesArgParser =
|
||||
makeTryRunParser(TryCompileBaseNonProjectArgParser);
|
||||
makeTryRunParser(TryCompileBaseNewSourcesArgParser);
|
||||
|
||||
auto const TryRunOldArgParser = makeTryRunParser(TryCompileOldArgParser);
|
||||
|
||||
@@ -397,8 +403,21 @@ bool cmCoreTryCompile::TryCompileCode(Arguments& arguments,
|
||||
return false;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
} else {
|
||||
// only valid for srcfile signatures
|
||||
if (!this->SrcFileSignature) {
|
||||
if (!arguments.LangProps.empty()) {
|
||||
this->Makefile->IssueMessage(
|
||||
MessageType::FATAL_ERROR,
|
||||
@@ -419,6 +438,7 @@ bool cmCoreTryCompile::TryCompileCode(Arguments& arguments,
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// make sure the binary directory exists
|
||||
if (useUniqueBinaryDirectory) {
|
||||
this->BinaryDirectory =
|
||||
@@ -449,10 +469,35 @@ 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));
|
||||
}
|
||||
}
|
||||
// TODO: ensure sources is not empty
|
||||
|
||||
// Detect languages to enable.
|
||||
cmGlobalGenerator* gg = this->Makefile->GetGlobalGenerator();
|
||||
@@ -1132,3 +1177,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;
|
||||
}
|
||||
|
@@ -40,6 +40,10 @@ 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;
|
||||
ArgumentParser::MaybeEmpty<std::vector<std::string>> CMakeFlags{
|
||||
1, "CMAKE_FLAGS"
|
||||
}; // fake argv[0]
|
||||
@@ -103,6 +107,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,10 @@ 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(ProjectCopyFile)
|
||||
run_cmake(NonSourceCopyFile)
|
||||
run_cmake(NonSourceCompileDefinitions)
|
||||
|
1
Tests/RunCMake/try_compile/SourceFromBadName-result.txt
Normal file
1
Tests/RunCMake/try_compile/SourceFromBadName-result.txt
Normal file
@@ -0,0 +1 @@
|
||||
1
|
4
Tests/RunCMake/try_compile/SourceFromBadName-stderr.txt
Normal file
4
Tests/RunCMake/try_compile/SourceFromBadName-stderr.txt
Normal file
@@ -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\)
|
1
Tests/RunCMake/try_compile/SourceFromBadName.cmake
Normal file
1
Tests/RunCMake/try_compile/SourceFromBadName.cmake
Normal file
@@ -0,0 +1 @@
|
||||
try_compile(RESULT SOURCE_FROM_ARG bad/name.c "int main();")
|
1
Tests/RunCMake/try_compile/SourceFromOneArg-result.txt
Normal file
1
Tests/RunCMake/try_compile/SourceFromOneArg-result.txt
Normal file
@@ -0,0 +1 @@
|
||||
1
|
4
Tests/RunCMake/try_compile/SourceFromOneArg-stderr.txt
Normal file
4
Tests/RunCMake/try_compile/SourceFromOneArg-stderr.txt
Normal file
@@ -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\)
|
1
Tests/RunCMake/try_compile/SourceFromOneArg.cmake
Normal file
1
Tests/RunCMake/try_compile/SourceFromOneArg.cmake
Normal file
@@ -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\)
|
1
Tests/RunCMake/try_compile/SourceFromThreeArgs.cmake
Normal file
1
Tests/RunCMake/try_compile/SourceFromThreeArgs.cmake
Normal file
@@ -0,0 +1 @@
|
||||
try_compile(RESULT SOURCE_FROM_ARG test.c "int" "main();")
|
@@ -64,6 +64,48 @@ 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 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)
|
||||
if(NOT SHOULD_COMPILE)
|
||||
message(SEND_ERROR " SOURCE_FROM_ARG failed compiling: ${COMPILE_OUTPUT}")
|
||||
endif()
|
||||
if(NOT SHOULD_EXIT_WITH_ERROR EQUAL 42)
|
||||
message(SEND_ERROR " SOURCE_FROM_ARG gave unexpected run result: ${SHOULD_EXIT_WITH_ERROR}")
|
||||
endif()
|
||||
|
||||
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)
|
||||
if(NOT SHOULD_COMPILE)
|
||||
message(SEND_ERROR " SOURCE_FROM_VAR failed compiling: ${COMPILE_OUTPUT}")
|
||||
endif()
|
||||
if(NOT SHOULD_EXIT_WITH_ERROR EQUAL 42)
|
||||
message(SEND_ERROR " SOURCE_FROM_VAR gave unexpected run result: ${SHOULD_EXIT_WITH_ERROR}")
|
||||
endif()
|
||||
|
||||
# try to compile a project (old signature)
|
||||
message("Testing try_compile project mode (old signature)")
|
||||
try_compile(TEST_INNER
|
||||
|
Reference in New Issue
Block a user