Add support for *.manifest source files with MSVC tools
Classify .manifest sources separately, add dependencies on them, and pass them to the MS manifest tool to merge with linker-generated manifest files. Inspired-by: Gilles Khouzam <gillesk@microsoft.com>
This commit is contained in:
parent
da00be6359
commit
e134e53b47
|
@ -0,0 +1,7 @@
|
|||
ms-manifest-files
|
||||
-----------------
|
||||
|
||||
* CMake learned to honor ``*.manifest`` source files with MSVC tools.
|
||||
Manifest files named as sources of ``.exe`` and ``.dll`` targets
|
||||
will be merged with linker-generated manifests and embedded in the
|
||||
binary.
|
|
@ -274,8 +274,8 @@ set (CMAKE_MODULE_LINKER_FLAGS_MINSIZEREL_INIT ${CMAKE_EXE_LINKER_FLAGS_MINSIZER
|
|||
macro(__windows_compiler_msvc lang)
|
||||
if(NOT MSVC_VERSION LESS 1400)
|
||||
# for 2005 make sure the manifest is put in the dll with mt
|
||||
set(_CMAKE_VS_LINK_DLL "<CMAKE_COMMAND> -E vs_link_dll --intdir=<OBJECT_DIR> ")
|
||||
set(_CMAKE_VS_LINK_EXE "<CMAKE_COMMAND> -E vs_link_exe --intdir=<OBJECT_DIR> ")
|
||||
set(_CMAKE_VS_LINK_DLL "<CMAKE_COMMAND> -E vs_link_dll --intdir=<OBJECT_DIR> --manifests <MANIFESTS> -- ")
|
||||
set(_CMAKE_VS_LINK_EXE "<CMAKE_COMMAND> -E vs_link_exe --intdir=<OBJECT_DIR> --manifests <MANIFESTS> -- ")
|
||||
endif()
|
||||
set(CMAKE_${lang}_CREATE_SHARED_LIBRARY
|
||||
"${_CMAKE_VS_LINK_DLL}<CMAKE_LINKER> ${CMAKE_CL_NOLOGO} <OBJECTS> ${CMAKE_START_TEMP_FILE} /out:<TARGET> /implib:<TARGET_IMPLIB> /pdb:<TARGET_PDB> /dll /version:<TARGET_VERSION_MAJOR>.<TARGET_VERSION_MINOR>${_PLATFORM_LINK_FLAGS} <LINK_FLAGS> <LINK_LIBRARIES> ${CMAKE_END_TEMP_FILE}")
|
||||
|
|
|
@ -412,3 +412,20 @@ cmCommonTargetGenerator::GetLinkedTargetDirectories() const
|
|||
}
|
||||
return dirs;
|
||||
}
|
||||
|
||||
std::string cmCommonTargetGenerator::GetManifests()
|
||||
{
|
||||
std::vector<cmSourceFile const*> manifest_srcs;
|
||||
this->GeneratorTarget->GetManifests(manifest_srcs, this->ConfigName);
|
||||
|
||||
std::vector<std::string> manifests;
|
||||
for (std::vector<cmSourceFile const*>::iterator mi = manifest_srcs.begin();
|
||||
mi != manifest_srcs.end(); ++mi)
|
||||
{
|
||||
manifests.push_back(this->Convert((*mi)->GetFullPath(),
|
||||
this->WorkingDirectory,
|
||||
cmOutputConverter::SHELL));
|
||||
}
|
||||
|
||||
return cmJoin(manifests, " ");
|
||||
}
|
||||
|
|
|
@ -88,6 +88,7 @@ protected:
|
|||
ByLanguageMap DefinesByLanguage;
|
||||
std::string GetIncludes(std::string const& l);
|
||||
ByLanguageMap IncludesByLanguage;
|
||||
std::string GetManifests();
|
||||
|
||||
std::vector<std::string> GetLinkedTargetDirectories() const;
|
||||
};
|
||||
|
|
|
@ -75,6 +75,7 @@ struct IDLSourcesTag {};
|
|||
struct ResxTag {};
|
||||
struct ModuleDefinitionFileTag {};
|
||||
struct AppManifestTag{};
|
||||
struct ManifestsTag{};
|
||||
struct CertificatesTag{};
|
||||
struct XamlTag{};
|
||||
|
||||
|
@ -216,6 +217,10 @@ struct TagVisitor
|
|||
{
|
||||
DoAccept<IsSameTag<Tag, AppManifestTag>::Result>::Do(this->Data, sf);
|
||||
}
|
||||
else if (ext == "manifest")
|
||||
{
|
||||
DoAccept<IsSameTag<Tag, ManifestsTag>::Result>::Do(this->Data, sf);
|
||||
}
|
||||
else if (ext == "pfx")
|
||||
{
|
||||
DoAccept<IsSameTag<Tag, CertificatesTag>::Result>::Do(this->Data, sf);
|
||||
|
@ -623,6 +628,15 @@ cmGeneratorTarget
|
|||
IMPLEMENT_VISIT(AppManifest);
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
void
|
||||
cmGeneratorTarget
|
||||
::GetManifests(std::vector<cmSourceFile const*>& data,
|
||||
const std::string& config) const
|
||||
{
|
||||
IMPLEMENT_VISIT(Manifests);
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
void
|
||||
cmGeneratorTarget
|
||||
|
|
|
@ -71,6 +71,8 @@ public:
|
|||
const std::string& config) const;
|
||||
void GetAppManifest(std::vector<cmSourceFile const*>&,
|
||||
const std::string& config) const;
|
||||
void GetManifests(std::vector<cmSourceFile const*>&,
|
||||
const std::string& config) const;
|
||||
void GetCertificates(std::vector<cmSourceFile const*>&,
|
||||
const std::string& config) const;
|
||||
void GetXamlSources(std::vector<cmSourceFile const*>&,
|
||||
|
|
|
@ -525,6 +525,13 @@ cmLocalGenerator::ExpandRuleVariable(std::string const& variable,
|
|||
return replaceValues.LinkFlags;
|
||||
}
|
||||
}
|
||||
if(replaceValues.Manifests)
|
||||
{
|
||||
if(variable == "MANIFESTS")
|
||||
{
|
||||
return replaceValues.Manifests;
|
||||
}
|
||||
}
|
||||
if(replaceValues.Flags)
|
||||
{
|
||||
if(variable == "FLAGS")
|
||||
|
|
|
@ -219,6 +219,7 @@ public:
|
|||
const char* TargetSOName;
|
||||
const char* TargetInstallNameDir;
|
||||
const char* LinkFlags;
|
||||
const char* Manifests;
|
||||
const char* LanguageCompileFlags;
|
||||
const char* Defines;
|
||||
const char* Includes;
|
||||
|
|
|
@ -984,6 +984,20 @@ void cmLocalVisualStudio7Generator::WriteConfiguration(std::ostream& fout,
|
|||
"\t\t\t<Tool\n"
|
||||
"\t\t\t\tName=\"" << manifestTool << "\"";
|
||||
|
||||
std::vector<cmSourceFile const*> manifest_srcs;
|
||||
gt->GetManifests(manifest_srcs, configName);
|
||||
if (!manifest_srcs.empty())
|
||||
{
|
||||
fout << "\n\t\t\t\tAdditionalManifestFiles=\"";
|
||||
for (std::vector<cmSourceFile const*>::const_iterator
|
||||
mi = manifest_srcs.begin(); mi != manifest_srcs.end(); ++mi)
|
||||
{
|
||||
std::string m = (*mi)->GetFullPath();
|
||||
fout << this->ConvertToXMLOutputPath(m.c_str()) << ";";
|
||||
}
|
||||
fout << "\"";
|
||||
}
|
||||
|
||||
// Check if we need the FAT32 workaround.
|
||||
// Check the filesystem type where the target will be written.
|
||||
if (cmLVS6G_IsFAT(target.GetDirectory(configName).c_str()))
|
||||
|
|
|
@ -353,6 +353,8 @@ void cmMakefileExecutableTargetGenerator::WriteExecutableRule(bool relink)
|
|||
useResponseFileForObjects, buildObjs, depends,
|
||||
useWatcomQuote);
|
||||
|
||||
std::string manifests = this->GetManifests();
|
||||
|
||||
cmLocalGenerator::RuleVariables vars;
|
||||
vars.RuleLauncher = "RULE_LAUNCH_LINK";
|
||||
vars.CMTarget = this->Target;
|
||||
|
@ -391,6 +393,8 @@ void cmMakefileExecutableTargetGenerator::WriteExecutableRule(bool relink)
|
|||
vars.LinkLibraries = linkLibs.c_str();
|
||||
vars.Flags = flags.c_str();
|
||||
vars.LinkFlags = linkFlags.c_str();
|
||||
vars.Manifests = manifests.c_str();
|
||||
|
||||
// Expand placeholders in the commands.
|
||||
this->LocalGenerator->TargetImplib = targetOutPathImport;
|
||||
for(std::vector<std::string>::iterator i = real_link_commands.begin();
|
||||
|
|
|
@ -616,6 +616,8 @@ void cmMakefileLibraryTargetGenerator::WriteLibraryRules
|
|||
}
|
||||
}
|
||||
|
||||
std::string manifests = this->GetManifests();
|
||||
|
||||
cmLocalGenerator::RuleVariables vars;
|
||||
vars.TargetPDB = targetOutPathPDB.c_str();
|
||||
|
||||
|
@ -660,6 +662,8 @@ void cmMakefileLibraryTargetGenerator::WriteLibraryRules
|
|||
}
|
||||
vars.LinkFlags = linkFlags.c_str();
|
||||
|
||||
vars.Manifests = manifests.c_str();
|
||||
|
||||
// Compute the directory portion of the install_name setting.
|
||||
std::string install_name_dir;
|
||||
if(this->Target->GetType() == cmTarget::SHARED_LIBRARY)
|
||||
|
|
|
@ -1493,6 +1493,15 @@ void cmMakefileTargetGenerator
|
|||
depends.push_back(this->ModuleDefinitionFile);
|
||||
}
|
||||
|
||||
// Add a dependency on user-specified manifest files, if any.
|
||||
std::vector<cmSourceFile const*> manifest_srcs;
|
||||
this->GeneratorTarget->GetManifests(manifest_srcs, this->ConfigName);
|
||||
for (std::vector<cmSourceFile const*>::iterator mi = manifest_srcs.begin();
|
||||
mi != manifest_srcs.end(); ++mi)
|
||||
{
|
||||
depends.push_back((*mi)->GetFullPath());
|
||||
}
|
||||
|
||||
// Add user-specified dependencies.
|
||||
if(const char* linkDepends =
|
||||
this->Target->GetProperty("LINK_DEPENDS"))
|
||||
|
|
|
@ -237,6 +237,7 @@ cmNinjaNormalTargetGenerator
|
|||
|
||||
vars.Flags = "$FLAGS";
|
||||
vars.LinkFlags = "$LINK_FLAGS";
|
||||
vars.Manifests = "$MANIFESTS";
|
||||
|
||||
std::string langFlags;
|
||||
if (targetType != cmTarget::EXECUTABLE)
|
||||
|
@ -509,6 +510,8 @@ void cmNinjaNormalTargetGenerator::WriteLinkStatement()
|
|||
vars["LINK_FLAGS"] = cmGlobalNinjaGenerator
|
||||
::EncodeLiteral(vars["LINK_FLAGS"]);
|
||||
|
||||
vars["MANIFESTS"] = this->GetManifests();
|
||||
|
||||
vars["LINK_PATH"] = frameworkPath + linkPath;
|
||||
|
||||
// Compute architecture specific link flags. Yes, these go into a different
|
||||
|
|
|
@ -209,6 +209,15 @@ cmNinjaDeps cmNinjaTargetGenerator::ComputeLinkDeps() const
|
|||
result.push_back(this->ConvertToNinjaPath(this->ModuleDefinitionFile));
|
||||
}
|
||||
|
||||
// Add a dependency on user-specified manifest files, if any.
|
||||
std::vector<cmSourceFile const*> manifest_srcs;
|
||||
this->GeneratorTarget->GetManifests(manifest_srcs, this->ConfigName);
|
||||
for (std::vector<cmSourceFile const*>::iterator mi = manifest_srcs.begin();
|
||||
mi != manifest_srcs.end(); ++mi)
|
||||
{
|
||||
result.push_back(this->ConvertToNinjaPath((*mi)->GetFullPath()));
|
||||
}
|
||||
|
||||
// Add user-specified dependencies.
|
||||
if (const char* linkDepends = this->Target->GetProperty("LINK_DEPENDS"))
|
||||
{
|
||||
|
|
|
@ -2203,6 +2203,33 @@ cmVisualStudio10TargetGenerator::WriteLibOptions(std::string const& config)
|
|||
}
|
||||
}
|
||||
|
||||
void cmVisualStudio10TargetGenerator::WriteManifestOptions(
|
||||
std::string const& config)
|
||||
{
|
||||
if (this->Target->GetType() != cmTarget::EXECUTABLE &&
|
||||
this->Target->GetType() != cmTarget::SHARED_LIBRARY &&
|
||||
this->Target->GetType() != cmTarget::MODULE_LIBRARY)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<cmSourceFile const*> manifest_srcs;
|
||||
this->GeneratorTarget->GetManifests(manifest_srcs, config);
|
||||
if (!manifest_srcs.empty())
|
||||
{
|
||||
this->WriteString("<Manifest>\n", 2);
|
||||
this->WriteString("<AdditionalManifestFiles>", 3);
|
||||
for (std::vector<cmSourceFile const*>::const_iterator
|
||||
mi = manifest_srcs.begin(); mi != manifest_srcs.end(); ++mi)
|
||||
{
|
||||
std::string m = this->ConvertPath((*mi)->GetFullPath(), false);
|
||||
this->ConvertToWindowsSlash(m);
|
||||
(*this->BuildFileStream) << m << ";";
|
||||
}
|
||||
(*this->BuildFileStream) << "</AdditionalManifestFiles>\n";
|
||||
this->WriteString("</Manifest>\n", 2);
|
||||
}
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
void cmVisualStudio10TargetGenerator::WriteAntBuildOptions(
|
||||
|
@ -2740,6 +2767,8 @@ void cmVisualStudio10TargetGenerator::WriteItemDefinitionGroups()
|
|||
this->WriteLinkOptions(*i);
|
||||
// output lib flags <Lib></Lib>
|
||||
this->WriteLibOptions(*i);
|
||||
// output manifest flags <Manifest></Manifest>
|
||||
this->WriteManifestOptions(*i);
|
||||
if(this->NsightTegra &&
|
||||
this->Target->GetType() == cmTarget::EXECUTABLE &&
|
||||
this->Target->GetPropertyAsBool("ANDROID_GUI"))
|
||||
|
|
|
@ -111,6 +111,7 @@ private:
|
|||
void AddLibraries(cmComputeLinkInformation& cli,
|
||||
std::vector<std::string>& libVec);
|
||||
void WriteLibOptions(std::string const& config);
|
||||
void WriteManifestOptions(std::string const& config);
|
||||
void WriteEvents(std::string const& configName);
|
||||
void WriteEvent(const char* name,
|
||||
std::vector<cmCustomCommand> const& commands,
|
||||
|
|
|
@ -1362,6 +1362,7 @@ class cmVSLink
|
|||
bool Incremental;
|
||||
bool LinkGeneratesManifest;
|
||||
std::vector<std::string> LinkCommand;
|
||||
std::vector<std::string> UserManifests;
|
||||
std::string LinkerManifestFile;
|
||||
std::string ManifestFile;
|
||||
std::string ManifestFileRC;
|
||||
|
@ -1480,6 +1481,13 @@ bool cmVSLink::Parse(std::vector<std::string>::const_iterator argBeg,
|
|||
++arg;
|
||||
break;
|
||||
}
|
||||
else if (*arg == "--manifests")
|
||||
{
|
||||
for (++arg; arg != argEnd && !cmHasLiteralPrefix(*arg, "-"); ++arg)
|
||||
{
|
||||
this->UserManifests.push_back(*arg);
|
||||
}
|
||||
}
|
||||
else if (cmHasLiteralPrefix(*arg, "--intdir="))
|
||||
{
|
||||
intDir = arg->substr(9);
|
||||
|
@ -1544,10 +1552,11 @@ bool cmVSLink::Parse(std::vector<std::string>::const_iterator argBeg,
|
|||
this->ManifestFileRes = intDir + "/manifest.res";
|
||||
this->LinkCommand.push_back(this->ManifestFileRes);
|
||||
}
|
||||
else
|
||||
else if (this->UserManifests.empty())
|
||||
{
|
||||
// CMake places the linker-generated manifest next to the binary (as if it
|
||||
// were not to be embedded) when not linking incrementally.
|
||||
// Prior to support for user-specified manifests CMake placed the
|
||||
// linker-generated manifest next to the binary (as if it were not to be
|
||||
// embedded) when not linking incrementally. Preserve this behavior.
|
||||
this->ManifestFile = this->TargetFile + ".manifest";
|
||||
this->LinkerManifestFile = this->ManifestFile;
|
||||
}
|
||||
|
@ -1564,7 +1573,7 @@ bool cmVSLink::Parse(std::vector<std::string>::const_iterator argBeg,
|
|||
int cmVSLink::Link()
|
||||
{
|
||||
if (this->Incremental &&
|
||||
this->LinkGeneratesManifest)
|
||||
(this->LinkGeneratesManifest || !this->UserManifests.empty()))
|
||||
{
|
||||
if (this->Verbose)
|
||||
{
|
||||
|
@ -1688,7 +1697,7 @@ int cmVSLink::LinkNonIncremental()
|
|||
}
|
||||
|
||||
// If we have no manifest files we are done.
|
||||
if (!this->LinkGeneratesManifest)
|
||||
if (!this->LinkGeneratesManifest && this->UserManifests.empty())
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
@ -1709,6 +1718,8 @@ int cmVSLink::RunMT(std::string const& out, bool notify)
|
|||
{
|
||||
mtCommand.push_back(this->LinkerManifestFile);
|
||||
}
|
||||
mtCommand.insert(mtCommand.end(),
|
||||
this->UserManifests.begin(), this->UserManifests.end());
|
||||
mtCommand.push_back(out);
|
||||
if (notify)
|
||||
{
|
||||
|
|
|
@ -276,6 +276,7 @@ if(BUILD_TESTING)
|
|||
if(TEST_RESOURCES)
|
||||
ADD_TEST_MACRO(VSResource VSResource)
|
||||
endif()
|
||||
ADD_TEST_MACRO(MSManifest MSManifest)
|
||||
ADD_TEST_MACRO(Simple Simple)
|
||||
ADD_TEST_MACRO(PreOrder PreOrder)
|
||||
ADD_TEST_MACRO(MissingSourceFile MissingSourceFile)
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
cmake_minimum_required(VERSION 3.3)
|
||||
project(MSManifest C)
|
||||
|
||||
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
|
||||
add_subdirectory(Subdir)
|
|
@ -0,0 +1,9 @@
|
|||
configure_file(test.manifest.in test.manifest)
|
||||
add_executable(MSManifest main.c ${CMAKE_CURRENT_BINARY_DIR}/test.manifest)
|
||||
|
||||
if(MSVC AND NOT MSVC_VERSION LESS 1400)
|
||||
add_custom_command(TARGET MSManifest POST_BUILD VERBATIM
|
||||
COMMAND ${CMAKE_COMMAND} -Dexe=$<TARGET_FILE:MSManifest>
|
||||
-P ${CMAKE_CURRENT_SOURCE_DIR}/check.cmake
|
||||
)
|
||||
endif()
|
|
@ -0,0 +1,6 @@
|
|||
file(STRINGS "${exe}" content REGEX "name=\"Kitware.CMake.MSManifestTest\"")
|
||||
if(content)
|
||||
message(STATUS "Expected manifest content found:\n ${content}")
|
||||
else()
|
||||
message(FATAL_ERROR "Expected manifest content not found in\n ${exe}")
|
||||
endif()
|
|
@ -0,0 +1 @@
|
|||
int main(void) { return 0; }
|
|
@ -0,0 +1,4 @@
|
|||
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
|
||||
<assemblyIdentity type="win32" version="1.0.0.0"
|
||||
name="Kitware.CMake.MSManifestTest"/>
|
||||
</assembly>
|
|
@ -0,0 +1,19 @@
|
|||
enable_language(C)
|
||||
|
||||
add_executable(main main.c ${CMAKE_CURRENT_BINARY_DIR}/test.manifest)
|
||||
|
||||
if(MSVC AND NOT MSVC_VERSION LESS 1400)
|
||||
set(EXTRA_CHECK [[
|
||||
file(STRINGS "$<TARGET_FILE:main>" content REGEX "name=\"Kitware.CMake.C-Exe-Manifest-step[0-9]\"")
|
||||
if(NOT "${content}" MATCHES "name=\"Kitware.CMake.C-Exe-Manifest-step${check_step}\"")
|
||||
set(RunCMake_TEST_FAILED "Binary has no manifest with name=\"Kitware.CMake.C-Exe-Manifest-step${check_step}\":\n ${content}")
|
||||
endif()
|
||||
]])
|
||||
endif()
|
||||
|
||||
file(GENERATE OUTPUT check-$<LOWER_CASE:$<CONFIG>>.cmake CONTENT "
|
||||
set(check_pairs
|
||||
\"$<TARGET_FILE:main>|${CMAKE_CURRENT_BINARY_DIR}/test.manifest\"
|
||||
)
|
||||
${EXTRA_CHECK}
|
||||
")
|
|
@ -0,0 +1,6 @@
|
|||
file(WRITE "${RunCMake_TEST_BINARY_DIR}/test.manifest" [[
|
||||
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
|
||||
<assemblyIdentity type="win32" version="1.0.0.0"
|
||||
name="Kitware.CMake.C-Exe-Manifest-step1"/>
|
||||
</assembly>
|
||||
]])
|
|
@ -0,0 +1,6 @@
|
|||
file(WRITE "${RunCMake_TEST_BINARY_DIR}/test.manifest" [[
|
||||
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
|
||||
<assemblyIdentity type="win32" version="1.0.0.0"
|
||||
name="Kitware.CMake.C-Exe-Manifest-step2"/>
|
||||
</assembly>
|
||||
]])
|
|
@ -14,6 +14,9 @@ function(run_BuildDepends CASE)
|
|||
set(RunCMake-check-file check.cmake)
|
||||
set(check_step 1)
|
||||
run_cmake_command(${CASE}-build1 ${CMAKE_COMMAND} --build . --config Debug)
|
||||
if(run_BuildDepends_skip_step_2)
|
||||
return()
|
||||
endif()
|
||||
execute_process(COMMAND ${CMAKE_COMMAND} -E sleep 1.125) # handle 1s resolution
|
||||
include(${RunCMake_SOURCE_DIR}/${CASE}.step2.cmake OPTIONAL)
|
||||
set(check_step 2)
|
||||
|
@ -21,3 +24,11 @@ function(run_BuildDepends CASE)
|
|||
endfunction()
|
||||
|
||||
run_BuildDepends(C-Exe)
|
||||
if(NOT RunCMake_GENERATOR MATCHES "Visual Studio [67]|Xcode")
|
||||
if(RunCMake_GENERATOR MATCHES "Visual Studio 10")
|
||||
# VS 10 forgets to re-link when a manifest changes
|
||||
set(run_BuildDepends_skip_step_2 1)
|
||||
endif()
|
||||
run_BuildDepends(C-Exe-Manifest)
|
||||
unset(run_BuildDepends_skip_step_2)
|
||||
endif()
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
if(EXISTS ${RunCMake_TEST_BINARY_DIR}/check-debug.cmake)
|
||||
include(${RunCMake_TEST_BINARY_DIR}/check-debug.cmake)
|
||||
if(RunCMake_TEST_FAILED)
|
||||
return()
|
||||
endif()
|
||||
foreach(exe IN LISTS check_exes)
|
||||
execute_process(COMMAND ${exe} RESULT_VARIABLE res)
|
||||
if(NOT res EQUAL ${check_step})
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
int main(void) { return 0; }
|
Loading…
Reference in New Issue