fixup! Add Grpc.Tools MsBuild taks assembly, test and scripting

pull/13207/head
kkm 7 years ago
parent a93e3d2753
commit 17df1f8cf5
  1. 14
      src/csharp/Grpc.Tools.Tests/Grpc.Tools.Tests.csproj
  2. 12
      src/csharp/Grpc.Tools/Common.cs
  3. 77
      src/csharp/Grpc.Tools/DepFileUtil.cs
  4. 12
      src/csharp/Grpc.Tools/GeneratorServices.cs
  5. 31
      src/csharp/Grpc.Tools/Grpc.Tools.csproj
  6. 8
      src/csharp/Grpc.Tools/ProtoCompile.cs
  7. 2
      src/csharp/Grpc.Tools/ProtoCompilerOutputs.cs
  8. 2
      src/csharp/Grpc.Tools/ProtoReadDependencies.cs
  9. 4
      src/csharp/Grpc.Tools/build/_protobuf/Google.Protobuf.Tools.targets

@ -1,23 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <Project Sdk="Microsoft.NET.Sdk" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="..\Grpc.Core\Version.csproj.include" />
<PropertyGroup> <PropertyGroup>
<TargetFrameworks>netcoreapp1.0;net45</TargetFrameworks> <TargetFrameworks>net45;netcoreapp1.0</TargetFrameworks>
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
</PropertyGroup> </PropertyGroup>
<Import Project="..\Grpc.Core\SourceLink.csproj.include" />
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Grpc.Tools\Grpc.Tools.csproj" /> <ProjectReference Include="..\Grpc.Tools\Grpc.Tools.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Moq" Version="4.7.145" /> <PackageReference Include="Moq" Version="4.8.3" />
<PackageReference Include="NUnit" Version="3.9.0" /> <PackageReference Include="NUnit; NUnitLite" Version="3.10.1" />
<PackageReference Include="NUnitLite" Version="3.9.0" />
</ItemGroup> </ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'netcoreapp1.0' "> <ItemGroup Condition=" '$(TargetFramework)' == 'netcoreapp1.0' ">
<PackageReference Include="Microsoft.Build.Framework" Version="15.5.180" /> <PackageReference Include="Microsoft.Build.Framework; Microsoft.Build.Utilities.Core" Version="15.6.85" />
<PackageReference Include="Microsoft.Build.Utilities.Core" Version="15.5.180" />
</ItemGroup> </ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'net45' "> <ItemGroup Condition=" '$(TargetFramework)' == 'net45' ">

@ -25,15 +25,15 @@ using System.Security;
[assembly: InternalsVisibleTo("Grpc.Tools.Tests")] [assembly: InternalsVisibleTo("Grpc.Tools.Tests")]
namespace Grpc.Tools { namespace Grpc.Tools {
// Metadata names that we refer to often. // Metadata names (MSBuild item attributes) that we refer to often.
static class Metadata { static class Metadata {
// On output dependency lists. // On output dependency lists.
public static string kSource = "Source"; public static string Source = "Source";
// On ProtoBuf items. // On ProtoBuf items.
public static string kProtoRoot = "ProtoRoot"; public static string ProtoRoot = "ProtoRoot";
public static string kOutputDir = "OutputDir"; public static string OutputDir = "OutputDir";
public static string kGrpcServices = "GrpcServices"; public static string GrpcServices = "GrpcServices";
public static string kGrpcOutputDir = "GrpcOutputDir"; public static string GrpcOutputDir = "GrpcOutputDir";
}; };
// A few flags used to control the behavior under various platforms. // A few flags used to control the behavior under various platforms.

@ -25,7 +25,7 @@ using Microsoft.Build.Utilities;
namespace Grpc.Tools { namespace Grpc.Tools {
internal static class DepFileUtil { internal static class DepFileUtil {
/* /*
Sample dependency files. Notable features we have to deal with: Sample dependency files. Notable features we have to deal with:
* Slash doubling, must normalize them. * Slash doubling, must normalize them.
* Spaces in file names. Cannot just "unwrap" the line on backslash at eof; * Spaces in file names. Cannot just "unwrap" the line on backslash at eof;
@ -33,21 +33,30 @@ namespace Grpc.Tools {
the ':' separator, as containing exactly two. the ':' separator, as containing exactly two.
* Deal with ':' also being drive letter separator (second example). * Deal with ':' also being drive letter separator (second example).
obj\Release\net45\/Foo.cs \ obj\Release\net45\/Foo.cs \
obj\Release\net45\/FooGrpc.cs: C:/foo/include/google/protobuf/wrappers.proto\ obj\Release\net45\/FooGrpc.cs: C:/foo/include/google/protobuf/wrappers.proto\
C:/projects/foo/src//foo.proto C:/projects/foo/src//foo.proto
C:\projects\foo\src\./foo.grpc.pb.cc \ C:\projects\foo\src\./foo.grpc.pb.cc \
C:\projects\foo\src\./foo.grpc.pb.h \ C:\projects\foo\src\./foo.grpc.pb.h \
C:\projects\foo\src\./foo.pb.cc \ C:\projects\foo\src\./foo.pb.cc \
C:\projects\foo\src\./foo.pb.h: C:/foo/include/google/protobuf/wrappers.proto\ C:\projects\foo\src\./foo.pb.h: C:/foo/include/google/protobuf/wrappers.proto\
C:/foo/include/google/protobuf/any.proto\ C:/foo/include/google/protobuf/any.proto\
C:/foo/include/google/protobuf/source_context.proto\ C:/foo/include/google/protobuf/source_context.proto\
C:/foo/include/google/protobuf/type.proto\ C:/foo/include/google/protobuf/type.proto\
foo.proto foo.proto
*/ */
// Read file names from the dependency file to the right of ':'. /// <summary>
/// Read file names from the dependency file to the right of ':'
/// </summary>
/// <param name="protoDepDir">Relative path to the dependency cache, e. g. "out"</param>
/// <param name="proto">Relative path to the proto item, e. g. "foo/file.proto"</param>
/// <param name="log">A <see cref="TaskLoggingHelper"/> for logging</param>
/// <returns>
/// Array of the proto file <b>input</b> dependencies as written by protoc, or empty
/// array if the dependency file does not exist or cannot be parsed.
/// </returns>
public static string[] ReadDependencyInputs(string protoDepDir, string proto, public static string[] ReadDependencyInputs(string protoDepDir, string proto,
TaskLoggingHelper log) { TaskLoggingHelper log) {
string depFilename = GetDepFilenameForProto(protoDepDir, proto); string depFilename = GetDepFilenameForProto(protoDepDir, proto);
@ -80,7 +89,20 @@ C:\projects\foo\src\./foo.pb.h: C:/foo/include/google/protobuf/wrappers.proto\
return result.ToArray(); return result.ToArray();
} }
// Read file names from the dependency file to the left of ':'. /// <summary>
/// Read file names from the dependency file to the left of ':'
/// </summary>
/// <param name="depFilename">Path to dependency file written by protoc</param>
/// <param name="log">A <see cref="TaskLoggingHelper"/> for logging</param>
/// <returns>
/// Array of the protoc-generated outputs from the given dependency file
/// written by protoc, or empty array if the file does not exist or cannot
/// be parsed.
/// </returns>
/// <remarks>
/// Since this is called after a protoc invocation, an unparsable or missing
/// file causes an error-level message to be logged.
/// </remarks>
public static string[] ReadDependencyOutputs(string depFilename, public static string[] ReadDependencyOutputs(string depFilename,
TaskLoggingHelper log) { TaskLoggingHelper log) {
string[] lines = ReadDepFileLines(depFilename, true, log); string[] lines = ReadDepFileLines(depFilename, true, log);
@ -106,10 +128,31 @@ C:\projects\foo\src\./foo.pb.h: C:/foo/include/google/protobuf/wrappers.proto\
return result.ToArray(); return result.ToArray();
} }
// Get complete dependency file name from directory hash and file name, /// <summary>
// tucked onto protoDepDir, e. g. /// Construct relative dependency file name from directory hash and file name
// ("out", "foo/file.proto") => "out/deadbeef12345678_file.protodep". /// </summary>
// This way, the filenames are unique but still possible to make sense of. /// <param name="protoDepDir">Relative path to the dependency cache, e. g. "out"</param>
/// <param name="proto">Relative path to the proto item, e. g. "foo/file.proto"</param>
/// <returns>
/// Full relative path to the dependency file, e. g.
/// "out/deadbeef12345678_file.protodep"
/// </returns>
/// <remarks>
/// Since a project may contain proto files with the same filename but in different
/// directories, a unique filename for the dependency file is constructed based on the
/// proto file name both name and directory. The directory path can be arbitrary,
/// for example, it can be outside of the project, or an absolute path including
/// a drive letter, or a UNC network path. A name constructed from such a path by,
/// for example, replacing disallowed name characters with an underscore, may well
/// be over filesystem's allowed path length, since it will be located under the
/// project and solution directories, which are also some level deep from the root.
/// Instead of creating long and unwieldy names for these proto sources, we cache
/// the full path of the name without the filename, and append the filename to it,
/// as in e. g. "foo/file.proto" will yield the name "deadbeef12345678_file", where
/// "deadbeef12345678" is a presumed hash value of the string "foo/". This allows
/// the file names be short, unique (up to a hash collision), and still allowing
/// the user to guess their provenance.
/// </remarks>
public static string GetDepFilenameForProto(string protoDepDir, string proto) { public static string GetDepFilenameForProto(string protoDepDir, string proto) {
string dirname = Path.GetDirectoryName(proto); string dirname = Path.GetDirectoryName(proto);
if (Platform.IsFsCaseInsensitive) { if (Platform.IsFsCaseInsensitive) {
@ -177,7 +220,7 @@ C:\projects\foo\src\./foo.pb.h: C:/foo/include/google/protobuf/wrappers.proto\
// Read entire dependency file. The 'required' parameter controls error // Read entire dependency file. The 'required' parameter controls error
// logging behavior in case the file not found. We require this file when // logging behavior in case the file not found. We require this file when
// compiling, but reading it is optional when computing depnedencies. // compiling, but reading it is optional when computing dependencies.
static string[] ReadDepFileLines(string filename, bool required, static string[] ReadDepFileLines(string filename, bool required,
TaskLoggingHelper log) { TaskLoggingHelper log) {
try { try {
@ -189,7 +232,7 @@ C:\projects\foo\src\./foo.pb.h: C:/foo/include/google/protobuf/wrappers.proto\
if (required) { if (required) {
log.LogError($"Unable to load {filename}: {ex.GetType().Name}: {ex.Message}"); log.LogError($"Unable to load {filename}: {ex.GetType().Name}: {ex.Message}");
} else { } else {
log.LogMessage(MessageImportance.Low, $"Skippping {filename}: {ex.Message}"); log.LogMessage(MessageImportance.Low, $"Skipping {filename}: {ex.Message}");
} }
return new string[0]; return new string[0];
} }

@ -49,7 +49,7 @@ namespace Grpc.Tools {
// we do not try to validate the value; scripts take care of that. // we do not try to validate the value; scripts take care of that.
// It is safe to assume that gRPC is requested for any other value. // It is safe to assume that gRPC is requested for any other value.
protected bool GrpcOutputPossible(ITaskItem proto) { protected bool GrpcOutputPossible(ITaskItem proto) {
string gsm = proto.GetMetadata(Metadata.kGrpcServices); string gsm = proto.GetMetadata(Metadata.GrpcServices);
return !gsm.EqualNoCase("") && !gsm.EqualNoCase("none") return !gsm.EqualNoCase("") && !gsm.EqualNoCase("none")
&& !gsm.EqualNoCase("false"); && !gsm.EqualNoCase("false");
} }
@ -67,12 +67,12 @@ namespace Grpc.Tools {
Path.GetFileNameWithoutExtension(protoItem.ItemSpec)); Path.GetFileNameWithoutExtension(protoItem.ItemSpec));
var outputs = new string[doGrpc ? 2 : 1]; var outputs = new string[doGrpc ? 2 : 1];
string outdir = protoItem.GetMetadata(Metadata.kOutputDir); string outdir = protoItem.GetMetadata(Metadata.OutputDir);
string fileStem = Path.Combine(outdir, filename); string fileStem = Path.Combine(outdir, filename);
outputs[0] = fileStem + ".cs"; outputs[0] = fileStem + ".cs";
if (doGrpc) { if (doGrpc) {
// Override outdir if kGrpcOutputDir present, default to proto output. // Override outdir if kGrpcOutputDir present, default to proto output.
outdir = protoItem.GetMetadata(Metadata.kGrpcOutputDir); outdir = protoItem.GetMetadata(Metadata.GrpcOutputDir);
if (outdir != "") { if (outdir != "") {
fileStem = Path.Combine(outdir, filename); fileStem = Path.Combine(outdir, filename);
} }
@ -105,20 +105,20 @@ namespace Grpc.Tools {
public override string[] GetPossibleOutputs(ITaskItem protoItem) { public override string[] GetPossibleOutputs(ITaskItem protoItem) {
bool doGrpc = GrpcOutputPossible(protoItem); bool doGrpc = GrpcOutputPossible(protoItem);
string root = protoItem.GetMetadata(Metadata.kProtoRoot); string root = protoItem.GetMetadata(Metadata.ProtoRoot);
string proto = protoItem.ItemSpec; string proto = protoItem.ItemSpec;
string filename = Path.GetFileNameWithoutExtension(proto); string filename = Path.GetFileNameWithoutExtension(proto);
// E. g., ("foo/", "foo/bar/x.proto") => "bar" // E. g., ("foo/", "foo/bar/x.proto") => "bar"
string relative = GetRelativeDir(root, proto); string relative = GetRelativeDir(root, proto);
var outputs = new string[doGrpc ? 4 : 2]; var outputs = new string[doGrpc ? 4 : 2];
string outdir = protoItem.GetMetadata(Metadata.kOutputDir); string outdir = protoItem.GetMetadata(Metadata.OutputDir);
string fileStem = Path.Combine(outdir, relative, filename); string fileStem = Path.Combine(outdir, relative, filename);
outputs[0] = fileStem + ".pb.cc"; outputs[0] = fileStem + ".pb.cc";
outputs[1] = fileStem + ".pb.h"; outputs[1] = fileStem + ".pb.h";
if (doGrpc) { if (doGrpc) {
// Override outdir if kGrpcOutputDir present, default to proto output. // Override outdir if kGrpcOutputDir present, default to proto output.
outdir = protoItem.GetMetadata(Metadata.kGrpcOutputDir); outdir = protoItem.GetMetadata(Metadata.GrpcOutputDir);
if (outdir != "") { if (outdir != "") {
fileStem = Path.Combine(outdir, relative, filename); fileStem = Path.Combine(outdir, relative, filename);
} }

@ -6,7 +6,19 @@
<AssemblyName>Protobuf.MSBuild</AssemblyName> <AssemblyName>Protobuf.MSBuild</AssemblyName>
<Version>$(GrpcCsharpVersion)</Version> <Version>$(GrpcCsharpVersion)</Version>
<!-- If changing targets, change also paths in Google.Protobuf.Tools.targets. --> <!-- If changing targets, change also paths in Google.Protobuf.Tools.targets. -->
<TargetFrameworks>netstandard1.3;net40</TargetFrameworks> <TargetFrameworks>net45;netstandard1.3</TargetFrameworks>
</PropertyGroup>
<!-- This is copied verbatim from Grpc.Core/Common.csproj.include. Other settings
in that file conflict with the intent of this build, as it cannot be signed,
and may not compile Grpc.Core/Version.cs, as that file references constants
in Grpc.Core.dll.
TODO(kkm): Refactor imports. -->
<PropertyGroup Condition="'$(OS)' != 'Windows_NT'">
<!-- Workaround for https://github.com/dotnet/sdk/issues/335 -->
<FrameworkPathOverride Condition="Exists('/usr/lib/mono/4.5-api')">/usr/lib/mono/4.5-api</FrameworkPathOverride>
<FrameworkPathOverride Condition="Exists('/usr/local/lib/mono/4.5-api')">/usr/local/lib/mono/4.5-api</FrameworkPathOverride>
<FrameworkPathOverride Condition="Exists('/Library/Frameworks/Mono.framework/Versions/Current/lib/mono/4.5-api')">/Library/Frameworks/Mono.framework/Versions/Current/lib/mono/4.5-api</FrameworkPathOverride>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Label="Asset root folders. TODO(kkm): Change with package separation."> <PropertyGroup Label="Asset root folders. TODO(kkm): Change with package separation.">
@ -32,9 +44,6 @@
</PropertyGroup> </PropertyGroup>
<PropertyGroup Label="NuGet package definition" Condition=" '$(Configuration)' == 'Release' "> <PropertyGroup Label="NuGet package definition" Condition=" '$(Configuration)' == 'Release' ">
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<PackageOutputPath>../../../artifacts</PackageOutputPath>
<!-- TODO(kkm): Change to "build\" after splitting. --> <!-- TODO(kkm): Change to "build\" after splitting. -->
<BuildOutputTargetFolder>build\_protobuf\</BuildOutputTargetFolder> <BuildOutputTargetFolder>build\_protobuf\</BuildOutputTargetFolder>
<DevelopmentDependency>true</DevelopmentDependency> <DevelopmentDependency>true</DevelopmentDependency>
@ -57,10 +66,9 @@ Linux and MacOS. Managed runtime is supplied separately in the Grpc.Core package
<None Pack="true" PackagePath="build\" Include="build\**\*.xml; build\**\*.props; build\**\*.targets;" /> <None Pack="true" PackagePath="build\" Include="build\**\*.xml; build\**\*.props; build\**\*.targets;" />
<!-- Protobuf assets (for Google.Protobuf.Tools) --> <!-- Protobuf assets (for Google.Protobuf.Tools) -->
<_ProtoTemp Include="any.proto;api.proto;descriptor.proto;duration.proto;" /> <_ProtoAssetName Include="any;api;descriptor;duration;empty;field_mask;
<_ProtoTemp Include="empty.proto;field_mask.proto;source_context.proto;" /> source_context;struct;timestamp;type;wrappers" />
<_ProtoTemp Include="struct.proto;timestamp.proto;type.proto;wrappers.proto" /> <_Asset PackagePath="build/native/include/google/protobuf/" Include="@(_ProtoAssetName->'$(Assets_ProtoInclude)%(Identity).proto')" />
<_Asset PackagePath="build/native/include/google/protobuf/" Include="@(_ProtoTemp->'$(Assets_ProtoInclude)%(Identity)')" />
<!-- TODO(kkm): GPB builds assets into "macosx", GRPC into "macos". --> <!-- TODO(kkm): GPB builds assets into "macosx", GRPC into "macos". -->
<_Asset PackagePath="build/native/bin/windows/protoc.exe" Include="$(Assets_ProtoCompiler)windows_x86/protoc.exe" /> <_Asset PackagePath="build/native/bin/windows/protoc.exe" Include="$(Assets_ProtoCompiler)windows_x86/protoc.exe" />
@ -85,10 +93,9 @@ Linux and MacOS. Managed runtime is supplied separately in the Grpc.Core package
</ItemGroup> </ItemGroup>
<ItemGroup Condition="$(_NetStandard)"> <ItemGroup Condition="$(_NetStandard)">
<PackageReference Include="Microsoft.Build.Framework" Version="15.5.180" /> <PackageReference Include="Microsoft.Build.Framework; Microsoft.Build.Utilities.Core" Version="15.6.85" />
<PackageReference Include="Microsoft.Build.Utilities.Core" Version="15.5.180" /> <!-- Set PrivateAssets="All" on all items, even those implicitly added,
<!-- Set PrivateAssets="All" on all items, so that even implicit package so that they do not become dependencies of this package. -->
dependencies do not become dependencies of this package. -->
<PackageReference Update="@(PackageReference)" PrivateAssets="All" /> <PackageReference Update="@(PackageReference)" PrivateAssets="All" />
</ItemGroup> </ItemGroup>

@ -32,10 +32,10 @@ namespace Grpc.Tools {
/// any language outputs. /// any language outputs.
/// </summary> /// </summary>
public class ProtoCompile : ToolTask { public class ProtoCompile : ToolTask {
/* /*
Usage: /home/kkm/work/protobuf/src/.libs/lt-protoc [OPTION] PROTO_FILES Usage: /home/kkm/work/protobuf/src/.libs/lt-protoc [OPTION] PROTO_FILES
Parse PROTO_FILES and generate output based on the options given: Parse PROTO_FILES and generate output based on the options given:
-IPATH, --proto_path=PATH Specify the directory in which to search for -IPATH, --proto_path=PATH Specify the directory in which to search for
imports. May be specified multiple times; imports. May be specified multiple times;
directories will be searched in order. If not directories will be searched in order. If not
@ -116,7 +116,7 @@ Parse PROTO_FILES and generate output based on the options given:
quotes, wildcards, escapes, commands, etc.). quotes, wildcards, escapes, commands, etc.).
Each line corresponds to a single argument, Each line corresponds to a single argument,
even if it contains spaces. even if it contains spaces.
*/ */
static string[] s_supportedGenerators = new[] { static string[] s_supportedGenerators = new[] {
"cpp", "csharp", "java", "cpp", "csharp", "java",
"javanano", "js", "objc", "javanano", "js", "objc",

@ -68,7 +68,7 @@ namespace Grpc.Tools {
var outputs = generator.GetPossibleOutputs(proto); var outputs = generator.GetPossibleOutputs(proto);
foreach (string output in outputs) { foreach (string output in outputs) {
var ti = new TaskItem(output); var ti = new TaskItem(output);
ti.SetMetadata(Metadata.kSource, proto.ItemSpec); ti.SetMetadata(Metadata.Source, proto.ItemSpec);
possible.Add(ti); possible.Add(ti);
} }
} }

@ -55,7 +55,7 @@ namespace Grpc.Tools {
string[] deps = DepFileUtil.ReadDependencyInputs(ProtoDepDir, proto.ItemSpec, Log); string[] deps = DepFileUtil.ReadDependencyInputs(ProtoDepDir, proto.ItemSpec, Log);
foreach (string dep in deps) { foreach (string dep in deps) {
var ti = new TaskItem(dep); var ti = new TaskItem(dep);
ti.SetMetadata(Metadata.kSource, proto.ItemSpec); ti.SetMetadata(Metadata.Source, proto.ItemSpec);
dependencies.Add(ti); dependencies.Add(ti);
} }
} }

@ -7,7 +7,7 @@
<!-- Configuration is passing the smoke test. --> <!-- Configuration is passing the smoke test. -->
<Protobuf_ProjectSupported Condition=" '$(Protobuf_Generator)' != '' ">true</Protobuf_ProjectSupported> <Protobuf_ProjectSupported Condition=" '$(Protobuf_Generator)' != '' ">true</Protobuf_ProjectSupported>
<_Protobuf_MsBuildAssembly Condition=" '$(MSBuildRuntimeType)' == 'Core' ">netstandard1.3\Protobuf.MSBuild.dll</_Protobuf_MsBuildAssembly> <_Protobuf_MsBuildAssembly Condition=" '$(MSBuildRuntimeType)' == 'Core' ">netstandard1.3\Protobuf.MSBuild.dll</_Protobuf_MsBuildAssembly>
<_Protobuf_MsBuildAssembly Condition=" '$(MSBuildRuntimeType)' != 'Core' ">net40\Protobuf.MSBuild.dll</_Protobuf_MsBuildAssembly> <_Protobuf_MsBuildAssembly Condition=" '$(MSBuildRuntimeType)' != 'Core' ">net45\Protobuf.MSBuild.dll</_Protobuf_MsBuildAssembly>
</PropertyGroup> </PropertyGroup>
<UsingTask AssemblyFile="$(_Protobuf_MsBuildAssembly)" TaskName="Grpc.Tools.ProtoToolsPlatform" /> <UsingTask AssemblyFile="$(_Protobuf_MsBuildAssembly)" TaskName="Grpc.Tools.ProtoToolsPlatform" />
@ -370,7 +370,7 @@
Design-time support Design-time support
=================================================================================--> =================================================================================-->
<!-- Add all .proto files to the SourceFilesProjectOutputGroupOutput, so that <!-- Add all .proto files to the SourceFilesProjectOutputGroupOutput, so that:
* Visual Studio triggers a build when any of them changed; * Visual Studio triggers a build when any of them changed;
* The Pack target includes .proto files into the source package. --> * The Pack target includes .proto files into the source package. -->
<Target Name="_Protobuf_SourceFilesProjectOutputGroup" <Target Name="_Protobuf_SourceFilesProjectOutputGroup"

Loading…
Cancel
Save