1
0
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:
Matthew Woehlke
2022-09-21 13:34:02 -04:00
parent 620cf9efa7
commit cb14ae2b87
15 changed files with 193 additions and 14 deletions

View File

@@ -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

View File

@@ -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:

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,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;
}

View File

@@ -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,

View File

@@ -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)

View File

@@ -0,0 +1 @@
1

View 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\)

View File

@@ -0,0 +1 @@
try_compile(RESULT SOURCE_FROM_ARG bad/name.c "int main();")

View File

@@ -0,0 +1 @@
1

View 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\)

View File

@@ -0,0 +1 @@
try_compile(RESULT SOURCE_FROM_ARG test.c)

View File

@@ -0,0 +1 @@
1

View File

@@ -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\)

View File

@@ -0,0 +1 @@
try_compile(RESULT SOURCE_FROM_ARG test.c "int" "main();")

View File

@@ -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