Vs: fix CSharp custom command by introducing inline MSBuild <Targets>s

The custom command implementation is based on the Microsoft support article:

https://docs.microsoft.com/en-us/visualstudio/msbuild/how-to-extend-the-visual-studio-build-process

Fixes: #16960
This commit is contained in:
Michael Stürmer 2017-06-22 21:40:48 +02:00
parent dcdab5cf23
commit ec409a116f
9 changed files with 170 additions and 6 deletions

View File

@ -27,6 +27,12 @@ static std::string cmVS10EscapeXML(std::string arg)
return arg;
}
static std::string cmVS10EscapeQuotes(std::string arg)
{
cmSystemTools::ReplaceString(arg, "\"", "&quot;");
return arg;
}
static std::string cmVS10EscapeComment(std::string comment)
{
// MSBuild takes the CDATA of a <Message></Message> element and just
@ -578,6 +584,18 @@ void cmVisualStudio10TargetGenerator::Generate()
this->WriteEvents(*i);
this->WriteString("</PropertyGroup>\n", 1);
}
// make sure custom commands are executed before build (if necessary)
this->WriteString("<PropertyGroup>\n", 1);
this->WriteString("<BuildDependsOn>\n", 2);
for (std::set<std::string>::const_iterator i =
this->CSharpCustomCommandNames.begin();
i != this->CSharpCustomCommandNames.end(); ++i) {
this->WriteString(i->c_str(), 3);
(*this->BuildFileStream) << ";\n";
}
this->WriteString("$(BuildDependsOn)\n", 3);
this->WriteString("</BuildDependsOn>\n", 2);
this->WriteString("</PropertyGroup>\n", 1);
}
this->WriteString("</Project>", 0);
// The groups are stored in a separate file for VS 10
@ -1151,6 +1169,7 @@ void cmVisualStudio10TargetGenerator::WriteNsightTegraConfigurationValues(
void cmVisualStudio10TargetGenerator::WriteCustomCommands()
{
this->SourcesVisited.clear();
this->CSharpCustomCommandNames.clear();
std::vector<cmSourceFile const*> customCommands;
this->GeneratorTarget->GetCustomCommands(customCommands, "");
for (std::vector<cmSourceFile const*>::const_iterator si =
@ -1172,9 +1191,14 @@ void cmVisualStudio10TargetGenerator::WriteCustomCommand(
}
}
if (cmCustomCommand const* command = sf->GetCustomCommand()) {
this->WriteString("<ItemGroup>\n", 1);
// C# projects write their <Target> within WriteCustomRule()
if (this->ProjectType != csproj) {
this->WriteString("<ItemGroup>\n", 1);
}
this->WriteCustomRule(sf, *command);
this->WriteString("</ItemGroup>\n", 1);
if (this->ProjectType != csproj) {
this->WriteString("</ItemGroup>\n", 1);
}
}
}
}
@ -1209,8 +1233,20 @@ void cmVisualStudio10TargetGenerator::WriteCustomRule(
}
cmLocalVisualStudio7Generator* lg = this->LocalGenerator;
this->WriteSource("CustomBuild", source, ">\n");
if (this->ProjectType != csproj) {
this->WriteSource("CustomBuild", source, ">\n");
} else {
this->WriteString("<ItemGroup>\n", 1);
std::string link;
this->GetCSharpSourceLink(source, link);
this->WriteSource("None", source, ">\n");
if (!link.empty()) {
this->WriteString("<Link>", 3);
(*this->BuildFileStream) << link << "</Link>\n";
}
this->WriteString("</None>\n", 2);
this->WriteString("</ItemGroup>\n", 1);
}
for (std::vector<std::string>::const_iterator i =
this->Configurations.begin();
i != this->Configurations.end(); ++i) {
@ -1240,9 +1276,25 @@ void cmVisualStudio10TargetGenerator::WriteCustomRule(
outputs << sep << cmVS10EscapeXML(out);
sep = ";";
}
this->WriteCustomRuleCpp(*i, script, inputs.str(), outputs.str(), comment);
if (this->ProjectType == csproj) {
std::string name = "CustomCommand_" + *i + "_" +
cmSystemTools::ComputeStringMD5(sourcePath);
std::string inputs_s = inputs.str();
std::string outputs_s = outputs.str();
comment = cmVS10EscapeQuotes(comment);
script = cmVS10EscapeQuotes(script);
inputs_s = cmVS10EscapeQuotes(inputs_s);
outputs_s = cmVS10EscapeQuotes(outputs_s);
this->WriteCustomRuleCSharp(*i, name, script, inputs_s, outputs_s,
comment);
} else {
this->WriteCustomRuleCpp(*i, script, inputs.str(), outputs.str(),
comment);
}
}
if (this->ProjectType != csproj) {
this->WriteString("</CustomBuild>\n", 2);
}
this->WriteString("</CustomBuild>\n", 2);
}
void cmVisualStudio10TargetGenerator::WriteCustomRuleCpp(
@ -1267,6 +1319,28 @@ void cmVisualStudio10TargetGenerator::WriteCustomRuleCpp(
}
}
void cmVisualStudio10TargetGenerator::WriteCustomRuleCSharp(
std::string const& config, std::string const& name,
std::string const& script, std::string const& inputs,
std::string const& outputs, std::string const& comment)
{
this->CSharpCustomCommandNames.insert(name);
std::stringstream attributes;
attributes << "\n Name=\"" << name << "\"";
attributes << "\n Inputs=\"" << inputs << "\"";
attributes << "\n Outputs=\"" << outputs << "\"";
this->WritePlatformConfigTag("Target", config, 1, attributes.str().c_str(),
"\n");
if (!comment.empty()) {
this->WriteString("<Exec Command=\"", 2);
(*this->BuildFileStream) << "echo " << cmVS10EscapeXML(comment)
<< "\" />\n";
}
this->WriteString("<Exec Command=\"", 2);
(*this->BuildFileStream) << script << "\" />\n";
this->WriteString("</Target>\n", 1);
}
std::string cmVisualStudio10TargetGenerator::ConvertPath(
std::string const& path, bool forceRelative)
{

View File

@ -131,6 +131,12 @@ private:
std::string const& inputs,
std::string const& outputs,
std::string const& comment);
void WriteCustomRuleCSharp(std::string const& config,
std::string const& commandName,
std::string const& script,
std::string const& inputs,
std::string const& outputs,
std::string const& comment);
void WriteCustomCommands();
void WriteCustomCommand(cmSourceFile const* sf);
void WriteGroups();
@ -198,6 +204,7 @@ private:
cmGeneratedFileStream* BuildFileStream;
cmLocalVisualStudio7Generator* LocalGenerator;
std::set<cmSourceFile const*> SourcesVisited;
std::set<std::string> CSharpCustomCommandNames;
bool IsMissingFiles;
std::vector<std::string> AddedFiles;
std::string DefaultArtifactDir;

View File

@ -376,3 +376,7 @@ if(CMake_TEST_ANDROID_NDK OR CMake_TEST_ANDROID_STANDALONE_TOOLCHAIN)
endif()
set_property(TEST RunCMake.Android PROPERTY TIMEOUT ${CMake_TEST_ANDROID_TIMEOUT})
endif()
if(${CMAKE_GENERATOR} MATCHES "Visual Studio ([^89]|[89][0-9])")
add_RunCMake_test(CSharpCustomCommand)
endif()

View File

@ -0,0 +1,3 @@
cmake_minimum_required(VERSION 3.3)
project(${RunCMake_TEST} NONE)
include(${RunCMake_TEST}.cmake)

View File

@ -0,0 +1,21 @@
if(checkLevel EQUAL 0)
message("checking generation (${srcName} does not exist)")
if(EXISTS "${generatedFileName}")
set(RunCMake_TEST_FAILED "file \"${generatedFileName}\" should not exist")
endif()
elseif(checkLevel EQUAL 1)
message("checking build 1 (generate ${srcName})")
if(NOT "${actual_stdout}" MATCHES "${commandComment}")
set(RunCMake_TEST_FAILED "command not executed")
endif()
elseif(checkLevel EQUAL 2)
message("checking build 2 (no change in ${srcName}.in)")
if("${actual_stdout}" MATCHES "${commandComment}")
set(RunCMake_TEST_FAILED "command executed")
endif()
elseif(checkLevel EQUAL 3)
message("checking build 3 (update ${srcName})")
if(NOT "${actual_stdout}" MATCHES "${commandComment}")
set(RunCMake_TEST_FAILED "command not executed")
endif()
endif()

View File

@ -0,0 +1,13 @@
enable_language(CSharp)
add_executable(CSharpCustomCommand dummy.cs)
add_custom_command(OUTPUT ${generatedFileName}
COMMAND ${CMAKE_COMMAND} -E copy_if_different
${inputFileName} ${generatedFileName}
MAIN_DEPENDENCY ${inputFileName}
COMMENT "${commandComment}")
target_sources(CSharpCustomCommand PRIVATE
${inputFileName}
${generatedFileName})

View File

@ -0,0 +1,34 @@
include(RunCMake)
# Use a single build tree for a few tests without cleaning.
set(RunCMake_TEST_BINARY_DIR ${RunCMake_BINARY_DIR}/CommandWithOutput-build)
set(RunCMake_TEST_NO_CLEAN 1)
file(REMOVE_RECURSE "${RunCMake_TEST_BINARY_DIR}")
file(MAKE_DIRECTORY "${RunCMake_TEST_BINARY_DIR}")
set(RunCMake-check-file CommandWithOutput-check.cmake)
set(srcName "test.cs")
set(srcFileName "${CMAKE_CURRENT_LIST_DIR}/${srcName}.in")
set(inputFileName "${RunCMake_TEST_BINARY_DIR}/${srcName}.in")
set(generatedFileName "${RunCMake_TEST_BINARY_DIR}/${srcName}")
set(commandComment "Generating ${srcName}")
# copy the input file to build dir to avoid changing files in cmake
# source tree.
file(COPY "${srcFileName}" DESTINATION "${RunCMake_TEST_BINARY_DIR}")
set(RunCMake_TEST_OPTIONS ${RunCMake_TEST_OPTIONS}
"-DinputFileName=${inputFileName}"
"-DgeneratedFileName=${generatedFileName}"
"-DcommandComment=${commandComment}")
set(checkLevel 0)
run_cmake(CommandWithOutput)
set(checkLevel 1)
run_cmake_command(CommandWithOutput-build1 ${CMAKE_COMMAND} --build . --config Debug)
set(checkLevel 2)
run_cmake_command(CommandWithOutput-build2 ${CMAKE_COMMAND} --build . --config Debug)
# change file content to trigger custom command with next build
file(APPEND ${inputFileName} "\n")
set(checkLevel 3)
run_cmake_command(CommandWithOutput-build3 ${CMAKE_COMMAND} --build . --config Debug)

View File

@ -0,0 +1,8 @@
class TestCs
{
public static int Main(string[] args)
{
System.Console.WriteLine("Test C#");
return 0;
}
}