Merge branch 'msbuild-integration'

This commit is contained in:
Christian Köllner 2019-10-03 21:36:28 +02:00
commit e11f08f274
151 changed files with 32582 additions and 239 deletions

4
.gitignore vendored
View File

@ -332,3 +332,7 @@ ASALocalRun/
.mfractor/
.vscode/tasks.json
.vscode/launch.json
# Capnp code behind
*.capnp.cs
/globalPackages

View File

@ -4,6 +4,8 @@
<TargetFramework>netcoreapp2.2</TargetFramework>
<IsPackable>false</IsPackable>
<Configurations>Debug;Release</Configurations>
</PropertyGroup>
<ItemGroup>
@ -31,6 +33,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="2.2.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="2.2.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.2.0" />
<PackageReference Include="MSTest.TestAdapter" Version="1.4.0" />

View File

@ -8,6 +8,8 @@
<LangVersion>7.1</LangVersion>
<OutputType>Library</OutputType>
<Configurations>Debug;Release</Configurations>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
@ -15,6 +17,7 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="2.2.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="2.2.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.2.0" />
<PackageReference Include="MSTest.TestAdapter" Version="1.4.0" />

View File

@ -7,6 +7,7 @@
<AssemblyName>Capnp.Net.Runtime</AssemblyName>
<PackageId>Capnp.Net.Runtime</PackageId>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<PackageOutputPath>..\bin\$(Configuration)</PackageOutputPath>
<Authors>Christian Köllner and contributors</Authors>
<Description>A Cap'n Proto implementation for .NET Standard &amp; Core</Description>
<Product>capnproto-dotnetcore</Product>
@ -17,17 +18,14 @@
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<RepositoryType>Git</RepositoryType>
<PackageTags>capnp "Cap'n Proto" RPC serialization cerealization</PackageTags>
<Version>1.0.0</Version>
<Version>1.0-local$([System.DateTime]::UtcNow.ToString(yyMMddHHmm))</Version>
<Configurations>Debug;Release</Configurations>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)'=='Debug'">
<DefineConstants>TRACE</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)'=='Release'">
<DocumentationFile></DocumentationFile>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging" Version="2.2.0" />
</ItemGroup>

View File

@ -11,7 +11,13 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Capnp.Net.Runtime.Tests.Std
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Capnp.Net.Runtime.Tests.Core21", "Capnp.Net.Runtime.Tests.Core21\Capnp.Net.Runtime.Tests.Core21.csproj", "{58E8FFC8-D207-4B0F-842A-58ED9D3D9EEF}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "capnpc-csharp.tests", "capnpc-csharp.tests\capnpc-csharp.tests.csproj", "{B77AC567-E232-4072-85C3-8689566BF3D4}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CapnpC.CSharp.Generator.Tests", "CapnpC.CSharp.Generator.Tests\CapnpC.CSharp.Generator.Tests.csproj", "{B77AC567-E232-4072-85C3-8689566BF3D4}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CapnpC.CSharp.MsBuild.Generation", "CapnpC.CSharp.MsBuild.Generation\CapnpC.CSharp.MsBuild.Generation.csproj", "{1EFC1F20-C7BB-4F44-8BF9-DBB123AACCF4}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CapnpC.CSharp.Generator", "CapnpC.CSharp.Generator\CapnpC.CSharp.Generator.csproj", "{C3A3BB49-356E-4762-A190-76D877BE18F7}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CapnpC.CSharp.MsBuild.Generation.Tests", "CapnpC.CSharp.MsBuild.Generation.Tests\CapnpC.CSharp.MsBuild.Generation.Tests.csproj", "{EF05AD68-DE31-448E-B88D-4144F928ED5D}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@ -39,6 +45,18 @@ Global
{B77AC567-E232-4072-85C3-8689566BF3D4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B77AC567-E232-4072-85C3-8689566BF3D4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B77AC567-E232-4072-85C3-8689566BF3D4}.Release|Any CPU.Build.0 = Release|Any CPU
{1EFC1F20-C7BB-4F44-8BF9-DBB123AACCF4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1EFC1F20-C7BB-4F44-8BF9-DBB123AACCF4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1EFC1F20-C7BB-4F44-8BF9-DBB123AACCF4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1EFC1F20-C7BB-4F44-8BF9-DBB123AACCF4}.Release|Any CPU.Build.0 = Release|Any CPU
{C3A3BB49-356E-4762-A190-76D877BE18F7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C3A3BB49-356E-4762-A190-76D877BE18F7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C3A3BB49-356E-4762-A190-76D877BE18F7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C3A3BB49-356E-4762-A190-76D877BE18F7}.Release|Any CPU.Build.0 = Release|Any CPU
{EF05AD68-DE31-448E-B88D-4144F928ED5D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EF05AD68-DE31-448E-B88D-4144F928ED5D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EF05AD68-DE31-448E-B88D-4144F928ED5D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EF05AD68-DE31-448E-B88D-4144F928ED5D}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

View File

@ -2,9 +2,11 @@
<PropertyGroup>
<TargetFramework>netcoreapp2.2</TargetFramework>
<RootNamespace>capnpc_csharp.Tests</RootNamespace>
<RootNamespace>CapnpC.CSharp.Generator.Tests</RootNamespace>
<IsPackable>false</IsPackable>
<Configurations>Debug;Release</Configurations>
</PropertyGroup>
<ItemGroup>
@ -16,17 +18,18 @@
<PackageReference Include="SpecFlow.Tools.MsBuild.Generation" Version="3.0.225" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\capnpc-csharp\capnpc-csharp.csproj" />
</ItemGroup>
<ItemGroup>
<Compile Remove="*.cs" />
<Compile Remove="Embedded Resources\test.cs" />
<None Remove="Embedded Resources\Empty.capnp" />
<None Remove="Embedded Resources\Empty1.capnp" />
<None Remove="Embedded Resources\invalid.capnp" />
<None Remove="Embedded Resources\null.bin" />
<None Remove="Embedded Resources\test.capnp" />
<None Remove="Embedded Resources\test.capnp.bin" />
<Compile Include="UnitTests.cs" />
<None Remove="Embedded Resources\UnitTest1.capnp" />
<Compile Include="CapnpMessageUnitTests.cs" />
<Compile Include="CodeGeneratorUnitTests.cs" />
</ItemGroup>
<ItemGroup>
@ -34,9 +37,13 @@
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Embedded Resources\Empty.capnp" />
<EmbeddedResource Include="Embedded Resources\Empty1.capnp" />
<EmbeddedResource Include="Embedded Resources\invalid.capnp" />
<EmbeddedResource Include="Embedded Resources\null.bin" />
<EmbeddedResource Include="Embedded Resources\test.capnp.bin" />
<EmbeddedResource Include="Embedded Resources\test.cs" />
<EmbeddedResource Include="Embedded Resources\UnitTest1.capnp" />
<EmbeddedResource Include="Embedded Resources\UnitTest1.capnp.bin" />
<EmbeddedResource Include="Embedded Resources\UnitTest2.capnp.bin" />
<EmbeddedResource Include="Embedded Resources\UnitTest3.capnp.bin" />
@ -47,11 +54,16 @@
<EmbeddedResource Include="Embedded Resources\schema-with-offsets.capnp.bin" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\CapnpC.CSharp.Generator\CapnpC.CSharp.Generator.csproj" />
</ItemGroup>
<ItemGroup>
<SpecFlowFeatureFiles Update="CodeGenerator.feature">
<Generator>SpecFlowSingleFileGenerator</Generator>
<LastGenOutput>CodeGenerator.feature.cs</LastGenOutput>
<Generator Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'"></Generator>
<Generator Condition="'$(Configuration)|$(Platform)'=='ReleaseTest|AnyCPU'"></Generator>
</SpecFlowFeatureFiles>
</ItemGroup>

View File

@ -0,0 +1,107 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Collections.Generic;
using System.Text;
namespace CapnpC.CSharp.Generator.Tests
{
[TestClass]
public class CapnpMessageUnitTests
{
[TestMethod]
public void ParseError()
{
var msg = new CapnpMessage(@"f:\code\invalid.capnp:5:1: error: Parse error.");
Assert.AreEqual(@"f:\code\invalid.capnp:5:1: error: Parse error.", msg.FullMessage);
Assert.IsTrue(msg.IsParseSuccess);
Assert.AreEqual(@"f:\code\invalid.capnp", msg.FileName);
Assert.AreEqual(5, msg.Line);
Assert.AreEqual(1, msg.Column);
Assert.AreEqual(0, msg.EndColumn);
Assert.AreEqual("error", msg.Category);
Assert.AreEqual("Parse error.", msg.MessageText);
}
[TestMethod]
public void ColumnSpan()
{
var msg = new CapnpMessage(@"f:\code\invalid.capnp:10:7-8: error: Duplicate ordinal number.");
Assert.IsTrue(msg.IsParseSuccess);
Assert.AreEqual(@"f:\code\invalid.capnp", msg.FileName);
Assert.AreEqual(10, msg.Line);
Assert.AreEqual(7, msg.Column);
Assert.AreEqual(8, msg.EndColumn);
Assert.AreEqual("error", msg.Category);
Assert.AreEqual("Duplicate ordinal number.", msg.MessageText);
}
[TestMethod]
public void NoSuchFile()
{
var msg = new CapnpMessage(@"C:\ProgramData\chocolatey\lib\capnproto\tools\capnproto-tools-win32-0.7.0\capnp.exe compile: doesnotexist.capnp: no such file");
Assert.IsFalse(msg.IsParseSuccess);
Assert.AreEqual(@"C:\ProgramData\chocolatey\lib\capnproto\tools\capnproto-tools-win32-0.7.0\capnp.exe compile: doesnotexist.capnp: no such file", msg.FullMessage);
}
[TestMethod]
public void NoId()
{
var msg = new CapnpMessage(@"empty.capnp:1:1: error: File does not declare an ID. I've generated one for you. Add this line to your file: @0xc82955a0c779197d;");
Assert.IsTrue(msg.IsParseSuccess);
Assert.AreEqual("empty.capnp", msg.FileName);
Assert.AreEqual(1, msg.Line);
Assert.AreEqual(1, msg.Column);
Assert.AreEqual("error", msg.Category);
Assert.AreEqual("File does not declare an ID. I've generated one for you. Add this line to your file: @0xc82955a0c779197d;", msg.MessageText);
}
[TestMethod]
public void AnnoyingNTFSAlternateDataStream1()
{
var msg = new CapnpMessage(@"3:2:1:1: error: File does not declare an ID. I've generated one for you. Add this line to your file: @0xc82955a0c779197d;");
Assert.IsTrue(msg.IsParseSuccess);
Assert.AreEqual("3:2", msg.FileName);
Assert.AreEqual(1, msg.Line);
Assert.AreEqual(1, msg.Column);
Assert.AreEqual("error", msg.Category);
Assert.AreEqual("File does not declare an ID. I've generated one for you. Add this line to your file: @0xc82955a0c779197d;", msg.MessageText);
}
[TestMethod]
public void AnnoyingNTFSAlternateDataStream2()
{
var msg = new CapnpMessage(@"c:\3:2:1:1: error: File does not declare an ID. I've generated one for you. Add this line to your file: @0xc82955a0c779197d;");
Assert.IsTrue(msg.IsParseSuccess);
Assert.AreEqual(@"c:\3:2", msg.FileName);
Assert.AreEqual(1, msg.Line);
Assert.AreEqual(1, msg.Column);
Assert.AreEqual("error", msg.Category);
Assert.AreEqual("File does not declare an ID. I've generated one for you. Add this line to your file: @0xc82955a0c779197d;", msg.MessageText);
}
[TestMethod]
public void AnnoyingNTFSAlternateDataStream3()
{
var msg = new CapnpMessage(@"\\?\c:\3:2:1:1: error: File does not declare an ID. I've generated one for you. Add this line to your file: @0xc82955a0c779197d;");
Assert.IsTrue(msg.IsParseSuccess);
Assert.AreEqual(@"\\?\c:\3:2", msg.FileName);
Assert.AreEqual(1, msg.Line);
Assert.AreEqual(1, msg.Column);
Assert.AreEqual("error", msg.Category);
Assert.AreEqual("File does not declare an ID. I've generated one for you. Add this line to your file: @0xc82955a0c779197d;", msg.MessageText);
}
[TestMethod]
public void AnnoyingNTFSAlternateDataStream4()
{
var msg = new CapnpMessage(@"1:2-3:10:7-8: error: Duplicate ordinal number.");
Assert.IsTrue(msg.IsParseSuccess);
Assert.AreEqual(@"1:2-3", msg.FileName);
Assert.AreEqual(10, msg.Line);
Assert.AreEqual(7, msg.Column);
Assert.AreEqual(8, msg.EndColumn);
Assert.AreEqual("error", msg.Category);
Assert.AreEqual("Duplicate ordinal number.", msg.MessageText);
}
}
}

View File

@ -0,0 +1,48 @@
Feature: CodeGenerator
In order to ensure that the generator backend produces valid output
As a contributor
I want to get notified when there is any deviation from reference output
Scenario: Comparing backend output with reference
Given I have a binary code generator request "test.capnp.bin"
And my reference output is "test.cs"
When I invoke capnpc-csharp
Then the generated output must match the reference
Scenario Outline: Invalid binary code generator requests
Given I have a binary code generator request <bin>
When I invoke capnpc-csharp
Then the invocation must fail
Examples:
| bin |
| null.bin |
| test.cs |
Scenario: Combining frontend and backend
Given capnp.exe is installed on my system
And I have a schema "UnitTest1.capnp"
When I try to generate code from that schema
Then code generation must succeed
Scenario: Missing frontend
Given capnp.exe is not installed on my system
And I have a schema "UnitTest1.capnp"
When I try to generate code from that schema
Then the invocation must fail
Scenario: Schema without ID
Given capnp.exe is installed on my system
And I have a schema "Empty1.capnp"
When I try to generate code from that schema
Then the invocation must fail
And the reason must be bad input
And the error output must contain "File does not declare an ID"
Scenario: Multiple errors
Given capnp.exe is installed on my system
And I have a schema "invalid.capnp"
When I try to generate code from that schema
Then the invocation must fail
And the reason must be bad input
And the error output must contain multiple messages

View File

@ -10,7 +10,7 @@
// ------------------------------------------------------------------------------
#region Designer generated code
#pragma warning disable
namespace capnpc_csharp.Tests
namespace CapnpC.CSharp.Generator.Tests
{
using TechTalk.SpecFlow;
@ -63,7 +63,7 @@ namespace capnpc_csharp.Tests
if (((testRunner.FeatureContext != null)
&& (testRunner.FeatureContext.FeatureInfo.Title != "CodeGenerator")))
{
global::capnpc_csharp.Tests.CodeGeneratorFeature.FeatureSetup(null);
global::CapnpC.CSharp.Generator.Tests.CodeGeneratorFeature.FeatureSetup(null);
}
}
@ -149,6 +149,98 @@ this.InvalidBinaryCodeGeneratorRequests("null.bin", ((string[])(null)));
this.InvalidBinaryCodeGeneratorRequests("test.cs", ((string[])(null)));
#line hidden
}
[Microsoft.VisualStudio.TestTools.UnitTesting.TestMethodAttribute()]
[Microsoft.VisualStudio.TestTools.UnitTesting.DescriptionAttribute("Combining frontend and backend")]
[Microsoft.VisualStudio.TestTools.UnitTesting.TestPropertyAttribute("FeatureTitle", "CodeGenerator")]
public virtual void CombiningFrontendAndBackend()
{
TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Combining frontend and backend", null, ((string[])(null)));
#line 22
this.ScenarioInitialize(scenarioInfo);
this.ScenarioStart();
#line 23
testRunner.Given("capnp.exe is installed on my system", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given ");
#line 24
testRunner.And("I have a schema \"UnitTest1.capnp\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And ");
#line 25
testRunner.When("I try to generate code from that schema", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When ");
#line 26
testRunner.Then("code generation must succeed", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then ");
#line hidden
this.ScenarioCleanup();
}
[Microsoft.VisualStudio.TestTools.UnitTesting.TestMethodAttribute()]
[Microsoft.VisualStudio.TestTools.UnitTesting.DescriptionAttribute("Missing frontend")]
[Microsoft.VisualStudio.TestTools.UnitTesting.TestPropertyAttribute("FeatureTitle", "CodeGenerator")]
public virtual void MissingFrontend()
{
TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Missing frontend", null, ((string[])(null)));
#line 28
this.ScenarioInitialize(scenarioInfo);
this.ScenarioStart();
#line 29
testRunner.Given("capnp.exe is not installed on my system", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given ");
#line 30
testRunner.And("I have a schema \"UnitTest1.capnp\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And ");
#line 31
testRunner.When("I try to generate code from that schema", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When ");
#line 32
testRunner.Then("the invocation must fail", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then ");
#line hidden
this.ScenarioCleanup();
}
[Microsoft.VisualStudio.TestTools.UnitTesting.TestMethodAttribute()]
[Microsoft.VisualStudio.TestTools.UnitTesting.DescriptionAttribute("Schema without ID")]
[Microsoft.VisualStudio.TestTools.UnitTesting.TestPropertyAttribute("FeatureTitle", "CodeGenerator")]
public virtual void SchemaWithoutID()
{
TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Schema without ID", null, ((string[])(null)));
#line 34
this.ScenarioInitialize(scenarioInfo);
this.ScenarioStart();
#line 35
testRunner.Given("capnp.exe is installed on my system", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given ");
#line 36
testRunner.And("I have a schema \"Empty1.capnp\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And ");
#line 37
testRunner.When("I try to generate code from that schema", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When ");
#line 38
testRunner.Then("the invocation must fail", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then ");
#line 39
testRunner.And("the reason must be bad input", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And ");
#line 40
testRunner.And("the error output must contain \"File does not declare an ID\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And ");
#line hidden
this.ScenarioCleanup();
}
[Microsoft.VisualStudio.TestTools.UnitTesting.TestMethodAttribute()]
[Microsoft.VisualStudio.TestTools.UnitTesting.DescriptionAttribute("Multiple errors")]
[Microsoft.VisualStudio.TestTools.UnitTesting.TestPropertyAttribute("FeatureTitle", "CodeGenerator")]
public virtual void MultipleErrors()
{
TechTalk.SpecFlow.ScenarioInfo scenarioInfo = new TechTalk.SpecFlow.ScenarioInfo("Multiple errors", null, ((string[])(null)));
#line 42
this.ScenarioInitialize(scenarioInfo);
this.ScenarioStart();
#line 43
testRunner.Given("capnp.exe is installed on my system", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Given ");
#line 44
testRunner.And("I have a schema \"invalid.capnp\"", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And ");
#line 45
testRunner.When("I try to generate code from that schema", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "When ");
#line 46
testRunner.Then("the invocation must fail", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "Then ");
#line 47
testRunner.And("the reason must be bad input", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And ");
#line 48
testRunner.And("the error output must contain multiple messages", ((string)(null)), ((TechTalk.SpecFlow.Table)(null)), "And ");
#line hidden
this.ScenarioCleanup();
}
}
}
#pragma warning restore

View File

@ -1,13 +1,13 @@
using Capnp;
using Model = CapnpC.Model;
using Generator = CapnpC.Generator;
using CodeGeneratorRequest = CapnpC.Schema.CodeGeneratorRequest;
using Model = CapnpC.CSharp.Generator.Model;
using CodeGen = CapnpC.CSharp.Generator.CodeGen;
using CodeGeneratorRequest = CapnpC.CSharp.Generator.Schema.CodeGeneratorRequest;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Collections.Generic;
using System.Linq;
namespace capnpc_csharp.Tests
namespace CapnpC.CSharp.Generator.Tests
{
[TestClass]
public class CodeGeneratorUnitTests
@ -98,13 +98,13 @@ namespace capnpc_csharp.Tests
struct Run
{
public Model.SchemaModel Model;
public Generator.CodeGenerator CodeGen;
public CodeGen.CodeGenerator CodeGen;
public Model.GenFile FirstFile;
public string Code;
}
static Generator.CodeGenerator NewGeneratorFor(Model.SchemaModel model)
=> new Generator.CodeGenerator(model, new Generator.GeneratorOptions());
static CodeGen.CodeGenerator NewGeneratorFor(Model.SchemaModel model)
=> new CodeGen.CodeGenerator(model, new CodeGen.GeneratorOptions());
Run LoadAndGenerate(string inputName, int? testNum = null)
{

View File

@ -0,0 +1 @@


View File

@ -0,0 +1 @@


View File

@ -0,0 +1,11 @@
@0xa5ac546b7bf6fbbc
enum Enumerant {
byte @0;
bit @1;
}
struct Foo {
foo @0: UInt8;
bar @0: UInt8;
}

View File

@ -0,0 +1,149 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using TechTalk.SpecFlow;
namespace CapnpC.CSharp.Generator.Tests
{
[Binding]
public class CodeGeneratorSteps
{
Stream _inputStream;
string _inputSchemaFileName;
string _inputSchema;
string _referenceOutputContent;
GenerationResult _result;
public static Stream LoadResource(string name)
{
var assembly = Assembly.GetExecutingAssembly();
string[] names = assembly.GetManifestResourceNames();
string urn = Array.Find(names, n => n.EndsWith(name, StringComparison.OrdinalIgnoreCase));
Assert.IsNotNull(urn, $"Test specification error: {name} does not exist");
return assembly.GetManifestResourceStream(urn);
}
internal static bool IsCapnpExeInstalled()
{
using (var process = Process.Start("where", "capnp.exe"))
{
if (process == null)
Assert.Fail("Unable to start 'where'");
process.WaitForExit();
return process.ExitCode == 0;
}
}
[Given(@"I have a binary code generator request ""(.*)""")]
[Given(@"I have a binary code generator request (.*)")]
public void GivenIHaveABinaryCodeGeneratorRequest(string binaryRequestFileName)
{
_inputStream = LoadResource(binaryRequestFileName);
}
[Given(@"my reference output is ""(.*)""")]
public void GivenMyReferenceOutputIs(string expectedOutputFileName)
{
using (var stream = LoadResource(expectedOutputFileName))
using (var reader = new StreamReader(stream))
{
_referenceOutputContent = reader.ReadToEnd();
}
}
[When(@"I invoke capnpc-csharp")]
public void WhenIInvokeCapnpc_Csharp()
{
using (_inputStream)
{
_result = CapnpCompilation.GenerateFromStream(_inputStream);
}
}
[Then(@"the generated output must match the reference")]
public void ThenTheGeneratedOutputMustMatchTheReference()
{
Assert.IsTrue(_result.IsSuccess, $"Tool invocation failed: {_result.Exception?.Message}");
Assert.AreEqual(_referenceOutputContent, _result.GeneratedFiles.Single().GeneratedContent);
}
[Then(@"the invocation must fail")]
public void ThenTheInvocationMustFail()
{
Assert.IsFalse(_result.IsSuccess, "Tool invocation was supposed to fail, but it didn't");
Assert.IsNotNull(_result.Exception, "Expected an exception");
}
[Given(@"capnp\.exe is installed on my system")]
public void GivenCapnp_ExeIsInstalledOnMySystem()
{
if (!IsCapnpExeInstalled())
{
Assert.Inconclusive("capnp.exe not found. Precondition of this test is not met.");
}
}
[Given(@"I have a schema ""(.*)""")]
public void GivenIHaveASchema(string capnpFileName)
{
_inputSchemaFileName = capnpFileName;
using (var stream = LoadResource(capnpFileName))
using (var reader = new StreamReader(stream))
{
_inputSchema = reader.ReadToEnd();
}
}
[When(@"I try to generate code from that schema")]
public void WhenIWantToGenerateCodeFromThatSchema()
{
string path = Path.Combine(Path.GetTempPath(), _inputSchemaFileName);
File.WriteAllText(path, _inputSchema);
_result = CapnpCompilation.InvokeCapnpAndGenerate(new string[] { path });
}
[Then(@"code generation must succeed")]
public void ThenCodeGenerationMustSucceed()
{
Assert.IsNotNull(_result, "expected generation result");
Assert.IsTrue(_result.IsSuccess, $"Tool invocation failed: {_result.Exception?.Message}");
Assert.IsTrue(_result.GeneratedFiles.Count == 1, "Expected exactly one file");
Assert.IsTrue(_result.GeneratedFiles[0].IsSuccess, $"Code generation failed: {_result.GeneratedFiles[0].Exception?.Message}");
Assert.IsFalse(string.IsNullOrEmpty(_result.GeneratedFiles[0].GeneratedContent), "Expected non-empty generated content");
}
[Given(@"capnp\.exe is not installed on my system")]
public void GivenCapnp_ExeIsNotInstalledOnMySystem()
{
if (IsCapnpExeInstalled())
{
Assert.Inconclusive("capnp.exe found. Precondition of this test is not met.");
}
}
[Then(@"the reason must be bad input")]
public void ThenTheReasonMustBeBadInput()
{
Assert.IsTrue(_result.ErrorCategory == CapnpProcessFailure.BadInput);
}
[Then(@"the error output must contain ""(.*)""")]
public void ThenTheErrorOutputMustContain(string p0)
{
Assert.IsTrue(_result.Messages.Any(m => m.FullMessage.Contains(p0)));
}
[Then(@"the error output must contain multiple messages")]
public void ThenTheErrorOutputMustContainMultipleMessages()
{
Assert.IsTrue(_result.Messages.Count >= 2);
}
}
}

View File

@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard2.0;netcoreapp2.1</TargetFrameworks>
<Configurations>Debug;Release</Configurations>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.2.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Capnp.Net.Runtime\Capnp.Net.Runtime.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,111 @@
using Capnp;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Runtime.CompilerServices;
using System.Text.RegularExpressions;
[assembly: InternalsVisibleTo("CapnpC.CSharp.Generator.Tests")]
namespace CapnpC.CSharp.Generator
{
/// <summary>
/// Provides methods for controlling both the C# code generator backend and the frontend "capnpc"
/// </summary>
public static class CapnpCompilation
{
/// <summary>
/// Generates C# code from given input stream
/// </summary>
/// <param name="input">input stream containing the binary code generation request, which the frontend capnpc emits</param>
/// <returns>generation result</returns>
/// <exception cref="ArgumentNullException">if <paramref name="input"/> is null</exception>
public static GenerationResult GenerateFromStream(Stream input)
{
if (input == null)
throw new ArgumentNullException(nameof(input));
try
{
var segments = Framing.ReadSegments(input);
var dec = DeserializerState.CreateRoot(segments);
var reader = Schema.CodeGeneratorRequest.Reader.Create(dec);
var model = Model.SchemaModel.Create(reader);
var codeGen = new CodeGen.CodeGenerator(model, new CodeGen.GeneratorOptions());
return new GenerationResult(codeGen.Generate());
}
catch (Exception exception)
{
return new GenerationResult(exception);
}
}
/// <summary>
/// Invokes "capnp.exe -o-" with given additional arguments and redirects the output to the C# generator backend.
/// </summary>
/// <param name="arguments">additional command line arguments</param>
/// <param name="workingDirectory">optional working directory</param>
/// <returns>generation result</returns>
/// <exception cref="ArgumentNullException"><paramref name="arguments"/>is null</exception>
public static GenerationResult InvokeCapnpAndGenerate(IEnumerable<string> arguments, string workingDirectory = null)
{
if (arguments == null)
throw new ArgumentNullException(nameof(arguments));
using (var compiler = new Process())
{
var argList = new List<string>();
argList.Add("compile");
argList.Add($"-o-");
argList.AddRange(arguments);
compiler.StartInfo.FileName = "capnp.exe";
compiler.StartInfo.Arguments = string.Join(" ", argList);
compiler.StartInfo.UseShellExecute = false;
compiler.StartInfo.RedirectStandardOutput = true;
compiler.StartInfo.RedirectStandardError = true;
if (!string.IsNullOrWhiteSpace(workingDirectory))
{
compiler.StartInfo.WorkingDirectory = workingDirectory;
}
try
{
compiler.Start();
}
catch (Exception exception)
{
return new GenerationResult(exception)
{
ErrorCategory = CapnpProcessFailure.NotFound
};
}
var result = GenerateFromStream(compiler.StandardOutput.BaseStream);
var messageList = new List<CapnpMessage>();
while (!compiler.StandardError.EndOfStream)
{
messageList.Add(new CapnpMessage(compiler.StandardError.ReadLine()));
}
result.Messages = messageList;
if (!result.IsSuccess)
{
compiler.WaitForExit();
int exitCode = compiler.ExitCode;
if (exitCode == 0)
result.ErrorCategory = CapnpProcessFailure.BadOutput;
else
result.ErrorCategory = CapnpProcessFailure.BadInput;
}
return result;
}
}
}
}

View File

@ -0,0 +1,116 @@
using System.Runtime.CompilerServices;
using System.Text.RegularExpressions;
[assembly: InternalsVisibleTo("CapnpC.CSharp.Generator.Tests")]
namespace CapnpC.CSharp.Generator
{
/// <summary>
/// Represents a capnp.exe output message
/// </summary>
public class CapnpMessage
{
// capnp outputs look like this:
// empty.capnp:1:1: error: File does not declare an ID. I've generated one for you. Add this line to your file: @0xc82955a0c779197d;
// f:\code\invalid.capnp:9:7-8: error: Ordinal @0 originally used here.
// Parsing them is harder than it seems because the colon may be part of the file name (as in the example above).
// And it becomes even worse! NTFS has a rarely used feature called "alternate data streams", identified by a colon:
// f:\code\somefile:stream.capnp:9:7-8: error: Ordinal @0 originally used here.
// What about a name which looks like a line number? (Hint: the 10 denotes the alternate data stream)
// f:\code\somefile:10:9:7-8: error: Ordinal @0 originally used here.
// Watching for the *last* colon as message separator does not work either, either. See first example.
// Strategy: Watch out for the *last* occurence of pattern :[line]:[column]
static readonly Regex LineColumnRegex = new Regex(@":(?<Line>\d+):(?<Column>\d+)(-(?<EndColumn>\d+))?:", RegexOptions.Compiled | RegexOptions.RightToLeft);
/// <summary>
/// Constructs an instance from given message
/// </summary>
/// <param name="fullMessage">output message (one line)</param>
public CapnpMessage(string fullMessage)
{
FullMessage = fullMessage;
var match = LineColumnRegex.Match(fullMessage);
if (match.Success)
{
IsParseSuccess = true;
FileName = fullMessage.Substring(0, match.Index);
var lineMatch = match.Groups["Line"];
if (lineMatch.Success)
{
int.TryParse(lineMatch.Value, out int value);
Line = value;
}
var columnMatch = match.Groups["Column"];
if (columnMatch.Success)
{
int.TryParse(columnMatch.Value, out int value);
Column = value;
}
var endColumnMatch = match.Groups["EndColumn"];
if (endColumnMatch.Success)
{
int.TryParse(endColumnMatch.Value, out int value);
EndColumn = value;
}
int restIndex = match.Index + match.Length;
int bodyIndex = fullMessage.IndexOf(':', restIndex);
if (bodyIndex >= 0)
{
Category = fullMessage.Substring(restIndex, bodyIndex - restIndex).Trim();
MessageText = fullMessage.Substring(bodyIndex + 1).Trim();
}
else
{
// Never observed "in the wild", just in case...
Category = string.Empty;
MessageText = fullMessage.Substring(restIndex).Trim();
}
}
}
/// <summary>
/// The original message
/// </summary>
public string FullMessage { get; }
/// <summary>
/// Whether the message could be decompsed into [filename]:[line]:[column]: [category]: [text]
/// </summary>
public bool IsParseSuccess { get; }
/// <summary>
/// Parsed file name (null iff not IsParseSuccess)
/// </summary>
public string FileName { get; }
/// <summary>
/// Parsed line (0 if not IsParseSuccess)
/// </summary>
public int Line { get; }
/// <summary>
/// Parsed column (0 if not IsParseSuccess)
/// </summary>
public int Column { get; }
/// <summary>
/// Parsed end column (0 if there is none)
/// </summary>
public int EndColumn { get; }
/// <summary>
/// Parsed category (e.g. "error", null iff not IsParseSuccess)
/// </summary>
public string Category { get; }
/// <summary>
/// Parsed message body text (0 if not IsParseSuccess)
/// </summary>
public string MessageText { get; }
}
}

View File

@ -0,0 +1,23 @@
namespace CapnpC.CSharp.Generator
{
/// <summary>
/// Why did invocation of capnpc.exe fail?
/// </summary>
public enum CapnpProcessFailure
{
/// <summary>
/// Because capnpc.exe was not found. It is probably not installed.
/// </summary>
NotFound,
/// <summary>
/// Because it exited with an error. Probably invalid .capnp file input.
/// </summary>
BadInput,
/// <summary>
/// Because it produced an apparently bad code generation request.
/// </summary>
BadOutput
}
}

View File

@ -1,18 +1,17 @@
namespace CapnpC.Generator
namespace CapnpC.CSharp.Generator.CodeGen
{
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using CapnpC.Model;
using CapnpC.CSharp.Generator.Model;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
using static SyntaxHelpers;
class CodeGenerator
internal class CodeGenerator
{
readonly SchemaModel _model;
readonly GenNames _names;
@ -169,14 +168,23 @@
return cu.NormalizeWhitespace().ToFullString();
}
public void Generate()
public IReadOnlyList<FileGenerationResult> Generate()
{
var result = new List<FileGenerationResult>();
foreach (var file in _model.FilesToGenerate)
{
string content = Transform(file);
string path = Path.ChangeExtension(file.Name, ".cs");
File.WriteAllText(path, content);
}
try
{
result.Add(new FileGenerationResult(file.Name, Transform(file)));
}
catch (System.Exception exception)
{
result.Add(new FileGenerationResult(file.Name, exception));
}
}
return result;
}
}
}

View File

@ -1,15 +1,13 @@
using System;
using System.Collections.Generic;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using CapnpC.Model;
using CapnpC.CSharp.Generator.Model;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
using static CapnpC.Generator.SyntaxHelpers;
using static CapnpC.CSharp.Generator.CodeGen.SyntaxHelpers;
namespace CapnpC.Generator
namespace CapnpC.CSharp.Generator.CodeGen
{
class CommonSnippetGen
{

View File

@ -1,15 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using CapnpC.Model;
using Microsoft.CodeAnalysis;
using CapnpC.CSharp.Generator.Model;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
using static CapnpC.Generator.SyntaxHelpers;
using static CapnpC.CSharp.Generator.CodeGen.SyntaxHelpers;
namespace CapnpC.Generator
namespace CapnpC.CSharp.Generator.CodeGen
{
class DomainClassSnippetGen
{

View File

@ -1,13 +1,12 @@
using CapnpC.Model;
using CapnpC.CSharp.Generator.Model;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
namespace CapnpC.Generator
namespace CapnpC.CSharp.Generator.CodeGen
{
enum NameUsage
{

View File

@ -1,8 +1,4 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace CapnpC.Generator
namespace CapnpC.CSharp.Generator.CodeGen
{
class GeneratorOptions
{

View File

@ -1,16 +1,15 @@
using CapnpC.Model;
using CapnpC.CSharp.Generator.Model;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
using static CapnpC.Generator.SyntaxHelpers;
using static CapnpC.CSharp.Generator.CodeGen.SyntaxHelpers;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using System.Threading.Tasks;
using System.Threading;
using Microsoft.CodeAnalysis.CSharp;
namespace CapnpC.Generator
namespace CapnpC.CSharp.Generator.CodeGen
{
class InterfaceSnippetGen
{

View File

@ -2,10 +2,8 @@
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using System;
using System.Collections.Generic;
using System.Text;
namespace CapnpC.Generator
namespace CapnpC.CSharp.Generator.CodeGen
{
class Name
{

View File

@ -1,15 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using CapnpC.Model;
using Microsoft.CodeAnalysis;
using CapnpC.CSharp.Generator.Model;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
using static CapnpC.Generator.SyntaxHelpers;
using static CapnpC.CSharp.Generator.CodeGen.SyntaxHelpers;
namespace CapnpC.Generator
namespace CapnpC.CSharp.Generator.CodeGen
{
class ReaderSnippetGen
{

View File

@ -1,8 +1,6 @@
using Capnp;
using System.Collections.Generic;
using System.Text;
namespace CapnpC.Generator
namespace CapnpC.CSharp.Generator.CodeGen
{
class SerializerStateWorder: SerializerState
{

View File

@ -1,6 +1,6 @@
using System;
namespace CapnpC.Generator
namespace CapnpC.CSharp.Generator.CodeGen
{
class SkeletonWorder : Capnp.Rpc.Skeleton<object>
{

View File

@ -1,13 +1,11 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
namespace CapnpC.Generator
namespace CapnpC.CSharp.Generator.CodeGen
{
static class SyntaxHelpers
{

View File

@ -1,15 +1,12 @@
using System;
using System.Collections.Generic;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using CapnpC.Model;
using Microsoft.CodeAnalysis;
using CapnpC.CSharp.Generator.Model;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
using static CapnpC.Generator.SyntaxHelpers;
using static CapnpC.CSharp.Generator.CodeGen.SyntaxHelpers;
namespace CapnpC.Generator
namespace CapnpC.CSharp.Generator.CodeGen
{
class WriterSnippetGen
{

View File

@ -0,0 +1,52 @@
using System;
namespace CapnpC.CSharp.Generator
{
/// <summary>
/// Represents the generation result of a single .capnp file
/// </summary>
public class FileGenerationResult
{
/// <summary>
/// Constructs an instance in case of successful generation
/// </summary>
/// <param name="capnpFilePath">path to .capnp file</param>
/// <param name="generatedContent">generated C# code</param>
public FileGenerationResult(string capnpFilePath, string generatedContent)
{
CapnpFilePath = capnpFilePath;
GeneratedContent = generatedContent;
}
/// <summary>
/// Constructs an instance in case of unsuccessful generation
/// </summary>
/// <param name="capnpFilePath">path to .capnp file</param>
/// <param name="exception">Exception giving details on the error which prevented generation</param>
public FileGenerationResult(string capnpFilePath, Exception exception)
{
CapnpFilePath = capnpFilePath;
Exception = exception;
}
/// <summary>
/// Path to .capnp file
/// </summary>
public string CapnpFilePath { get; }
/// <summary>
/// Generated C# or null if generation failed
/// </summary>
public string GeneratedContent { get; }
/// <summary>
/// Exception giving details on the error which prevented generation
/// </summary>
public Exception Exception { get; }
/// <summary>
/// true iff generation was successful
/// </summary>
public bool IsSuccess => !string.IsNullOrEmpty(GeneratedContent);
}
}

View File

@ -0,0 +1,54 @@
using System;
using System.Collections.Generic;
namespace CapnpC.CSharp.Generator
{
/// <summary>
/// Represents a .capnp -> code generator result
/// </summary>
public class GenerationResult
{
/// <summary>
/// Constructs an instance in case of at least partially successful generation.
/// </summary>
/// <param name="generatedFiles">Generation result per file to generate</param>
public GenerationResult(IReadOnlyList<FileGenerationResult> generatedFiles)
{
GeneratedFiles = generatedFiles;
}
/// <summary>
/// Constructs an instance in case of total failure.
/// </summary>
/// <param name="exception">Exception with details on error</param>
public GenerationResult(Exception exception)
{
Exception = exception;
}
/// <summary>
/// Generation result per file to generate or null in case of total failure
/// </summary>
public IReadOnlyList<FileGenerationResult> GeneratedFiles { get; }
/// <summary>
/// Exception with details on error or null in case of success
/// </summary>
public Exception Exception { get; }
/// <summary>
/// true iff generation was successful
/// </summary>
public bool IsSuccess => GeneratedFiles != null;
/// <summary>
/// Messages read from standard error. Valid for both failure and success (capnp might spit out some warnings).
/// </summary>
public IReadOnlyList<CapnpMessage> Messages { get; internal set; }
/// <summary>
/// Error classification (if any error)
/// </summary>
public CapnpProcessFailure ErrorCategory { get; internal set; }
}
}

View File

@ -1,6 +1,6 @@
using System.Collections.Generic;
namespace CapnpC.Model
namespace CapnpC.CSharp.Generator.Model
{
abstract class AbstractType
{

View File

@ -1,6 +1,6 @@
using System.Diagnostics;
namespace CapnpC.Model
namespace CapnpC.CSharp.Generator.Model
{
class Annotation : IDefinition
{

View File

@ -1,6 +1,6 @@
using System.Diagnostics;
namespace CapnpC.Model
namespace CapnpC.CSharp.Generator.Model
{
class Constant : IDefinition
{

View File

@ -2,7 +2,7 @@
using System.Collections.Generic;
using System.Linq;
namespace CapnpC.Model
namespace CapnpC.CSharp.Generator.Model
{
class DefinitionManager
{

View File

@ -1,4 +1,4 @@
namespace CapnpC.Model
namespace CapnpC.CSharp.Generator.Model
{
class Enumerant
{

View File

@ -1,4 +1,4 @@
namespace CapnpC.Model
namespace CapnpC.CSharp.Generator.Model
{
class Field
{

View File

@ -1,6 +1,6 @@
using System.Collections.Generic;
namespace CapnpC.Model
namespace CapnpC.CSharp.Generator.Model
{
class GenFile: IDefinition, IHasNestedDefinitions
{

View File

@ -1,4 +1,4 @@
namespace CapnpC.Model
namespace CapnpC.CSharp.Generator.Model
{
class GenericParameter
{

View File

@ -1,5 +1,6 @@
using System.Collections.Generic;
namespace CapnpC.Model
namespace CapnpC.CSharp.Generator.Model
{
static class HasGenericParameters
{

View File

@ -1,5 +1,5 @@

namespace CapnpC.Model
namespace CapnpC.CSharp.Generator.Model
{
interface IDefinition
{

View File

@ -1,6 +1,6 @@
using System.Collections.Generic;
namespace CapnpC.Model
namespace CapnpC.CSharp.Generator.Model
{
interface IHasGenericParameters
{

View File

@ -1,7 +1,7 @@
using System.Collections.Generic;
using System.Linq;
namespace CapnpC.Model
namespace CapnpC.CSharp.Generator.Model
{
interface IHasNestedDefinitions
{

View File

@ -3,7 +3,7 @@ using System;
using System.Collections.Generic;
using System.Text;
namespace CapnpC.Model
namespace CapnpC.CSharp.Generator.Model
{
public class IdentifierRenamer
{

View File

@ -1,6 +1,6 @@
using System;
namespace CapnpC.Model
namespace CapnpC.CSharp.Generator.Model
{
class InvalidSchemaException : Exception
{

View File

@ -1,6 +1,6 @@
using System.Collections.Generic;
namespace CapnpC.Model
namespace CapnpC.CSharp.Generator.Model
{
class Method: IHasGenericParameters
{

View File

@ -4,7 +4,7 @@ using System.Diagnostics;
using System.Linq;
using System.Text;
namespace CapnpC.Model
namespace CapnpC.CSharp.Generator.Model
{
class SchemaModel
{

View File

@ -1,4 +1,4 @@
namespace CapnpC.Model
namespace CapnpC.CSharp.Generator.Model
{
enum SpecialName
{

View File

@ -5,7 +5,7 @@ using System.Diagnostics;
using System.Linq;
using System.Text;
namespace CapnpC.Model
namespace CapnpC.CSharp.Generator.Model
{
class Type: AbstractType
{

View File

@ -1,4 +1,4 @@
namespace CapnpC.Model
namespace CapnpC.CSharp.Generator.Model
{
enum TypeCategory
{

View File

@ -1,7 +1,8 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
namespace CapnpC.Model
namespace CapnpC.CSharp.Generator.Model
{
class TypeDefinition : AbstractType, IDefinition, IHasNestedDefinitions, IHasGenericParameters
{

View File

@ -1,4 +1,4 @@
namespace CapnpC.Model
namespace CapnpC.CSharp.Generator.Model
{
enum TypeTag
{

View File

@ -1,6 +1,6 @@
using System;
namespace CapnpC.Model
namespace CapnpC.CSharp.Generator.Model
{
static class Types
{

View File

@ -3,7 +3,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
namespace CapnpC.Model
namespace CapnpC.CSharp.Generator.Model
{
class Value
{

View File

@ -1,9 +1,7 @@
using Capnp;
using System;
using System.Collections.Generic;
using System.Text;
namespace CapnpC.Schema
namespace CapnpC.CSharp.Generator.Schema
{
namespace Superclass
{

View File

@ -0,0 +1,42 @@
using System;
using System.Collections;
using Microsoft.Build.Framework;
namespace CapnpC.CSharp.MsBuild.Generation.Tests
{
class BuildEngineMock : IBuildEngine
{
public bool ContinueOnError => true;
public int LineNumberOfTaskNode => 0;
public int ColumnNumberOfTaskNode => 0;
public string ProjectFileOfTaskNode => null;
public bool BuildProjectFile(string projectFileName, string[] targetNames, IDictionary globalProperties, IDictionary targetOutputs)
{
return true;
}
public void LogCustomEvent(CustomBuildEventArgs e)
{
Console.WriteLine(e.Message);
}
public void LogErrorEvent(BuildErrorEventArgs e)
{
Console.WriteLine(e.Message);
}
public void LogMessageEvent(BuildMessageEventArgs e)
{
Console.WriteLine(e.Message);
}
public void LogWarningEvent(BuildWarningEventArgs e)
{
Console.WriteLine(e.Message);
}
}
}

View File

@ -0,0 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp2.2</TargetFramework>
<IsPackable>false</IsPackable>
<Configurations>Debug;Release</Configurations>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Build.Utilities.Core" Version="15.8.166" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.2.0" />
<PackageReference Include="MSTest.TestAdapter" Version="1.4.0" />
<PackageReference Include="MSTest.TestFramework" Version="1.4.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\CapnpC.CSharp.Generator.Tests\CapnpC.CSharp.Generator.Tests.csproj" />
<ProjectReference Include="..\CapnpC.CSharp.MsBuild.Generation\CapnpC.CSharp.MsBuild.Generation.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,51 @@
using CapnpC.CSharp.Generator.Tests;
using Microsoft.Build.Framework;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.IO;
namespace CapnpC.CSharp.MsBuild.Generation.Tests
{
[TestClass]
public class GenerateCapnpFileCodeBehindTaskTest
{
string LoadResourceContent(string name)
{
using (var stream = CodeGeneratorSteps.LoadResource("UnitTest1.capnp"))
using (var reader = new StreamReader(stream))
{
return reader.ReadToEnd();
}
}
[TestMethod]
public void ExecutionWithoutParameters()
{
var task = new GenerateCapnpFileCodeBehindTask();
task.BuildEngine = new BuildEngineMock();
task.Execute();
// Should not crash. Should Execute() return true or false if there is no input?
}
[TestMethod]
public void SimpleGeneration()
{
string capnpFile = "UnitTask1.capnp";
string content = LoadResourceContent(capnpFile);
string tmpPath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
Directory.CreateDirectory(tmpPath);
string capnpPath = Path.Combine(tmpPath, capnpFile);
File.WriteAllText(capnpPath, content);
var task = new GenerateCapnpFileCodeBehindTask();
task.BuildEngine = new BuildEngineMock();
task.ProjectPath = Path.Combine(tmpPath, "doesnotneedtoexist.csproj");
task.CapnpFiles = new ITaskItem[1] { new TaskItemMock() { ItemSpec = capnpPath } };
Assert.IsTrue(task.Execute());
Assert.IsNotNull(task.GeneratedFiles);
Assert.AreEqual(1, task.GeneratedFiles.Length);
string csPath = Path.Combine(tmpPath, task.GeneratedFiles[0].ItemSpec);
Assert.AreEqual(capnpPath + ".cs", csPath);
Assert.IsTrue(File.Exists(csPath));
}
}
}

View File

@ -0,0 +1,42 @@
using System.Collections;
using Microsoft.Build.Framework;
namespace CapnpC.CSharp.MsBuild.Generation.Tests
{
class TaskItemMock : ITaskItem
{
public string ItemSpec { get; set; }
public ICollection MetadataNames => null;
public int MetadataCount => 0;
public IDictionary CloneCustomMetadata()
{
return null;
}
public void CopyMetadataTo(ITaskItem destinationItem)
{
}
public string GetMetadata(string metadataName)
{
if (metadataName == "FullPath")
{
return ItemSpec;
}
return string.Empty;
}
public void RemoveMetadata(string metadataName)
{
}
public void SetMetadata(string metadataName, string metadataValue)
{
}
}
}

View File

@ -0,0 +1,4 @@

using System.Runtime.CompilerServices;
using System.Security;

View File

@ -0,0 +1,113 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net471;netcoreapp2.1</TargetFrameworks>
<SignAssembly>false</SignAssembly>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
<NoPackageAnalysis>true</NoPackageAnalysis>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<EmbedUntrackedSources>true</EmbedUntrackedSources>
<AllowedOutputExtensionsInPackageBuildOutputFolder>$(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb</AllowedOutputExtensionsInPackageBuildOutputFolder>
<AssemblyVersion>1.0.0.0</AssemblyVersion>
<FileVersion>1.0.0.0</FileVersion>
<Version>1.0-local$([System.DateTime]::UtcNow.ToString(yyMMddHHmm))</Version>
<NuspecFile>$(MSBuildThisFileDirectory)CapnpC.CSharp.MsBuild.Generation.nuspec</NuspecFile>
<NuspecProperties>version=$(Version);configuration=$(Configuration)</NuspecProperties>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<PackageOutputPath>..\bin\$(Configuration)</PackageOutputPath>
<Configurations>Debug;Release</Configurations>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.VisualStudio.Composition" />
<PackageReference Include="Microsoft.VisualStudio.ProjectSystem" />
<PackageReference Include="Microsoft.VisualStudio.ProjectSystem.SDK" />
<PackageReference Include="Microsoft.VisualStudio.ProjectSystem.SDK.Tools" />
<PackageReference Include="Microsoft.VisualStudio.Threading" />
<PackageReference Include="System.Collections.Immutable" />
</ItemGroup>
<ItemDefinitionGroup>
<PackageReference>
<PrivateAssets>All</PrivateAssets>
</PackageReference>
</ItemDefinitionGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Build.Framework" Version="15.8.166" />
<PackageReference Include="Microsoft.Build.Utilities.Core" Version="15.8.166" />
<PackageReference Update="@(PackageReference)" PrivateAssets="All" />
<PackageReference Update="Microsoft.VisualStudio.Composition" Version="15.8.98" />
<PackageReference Update="Microsoft.VisualStudio.ProjectSystem" Version="15.8.243" />
<PackageReference Update="Microsoft.VisualStudio.ProjectSystem.SDK" Version="15.8.243" />
<PackageReference Update="Microsoft.VisualStudio.ProjectSystem.SDK.Tools" Version="15.8.243" />
<PackageReference Update="Microsoft.VisualStudio.Threading" Version="16.3.52" />
<PackageReference Update="Microsoft.Xaml" Version="4.0.0.1" />
<PackageReference Update="System.Collections.Immutable" Version="1.5.0" />
</ItemGroup>
<ItemGroup>
<Compile Remove="FrameworkDependent\**\*.cs" />
<Compile Include="FrameworkDependent\*.cs" />
<None Include="FrameworkDependent\**\*.cs" />
<Compile Include="FrameworkDependent\FullFramework\**\*.cs" Condition="'$(TargetFramework)' == '$(CapnpcCsharp_FullFramework_Tools_TFM)'" />
<Compile Include="FrameworkDependent\DotNetCore\**\*.cs" Condition="'$(TargetFramework)' == '$(CapnpcCsharp_Core_Tools_TFM)'" />
</ItemGroup>
<ItemGroup>
<Folder Include="FrameworkDependent\DotNetCore\" />
<Folder Include="FrameworkDependent\FullFramework\" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\CapnpC.CSharp.Generator\CapnpC.CSharp.Generator.csproj">
<Private>true</Private>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<Reference Include="Microsoft.Build">
<HintPath>Microsoft.Build</HintPath>
</Reference>
<Reference Include="Microsoft.Build.Framework">
<HintPath>Microsoft.Build.Framework</HintPath>
</Reference>
<Reference Include="Microsoft.Build.Utilities.Core">
<HintPath>Microsoft.Build.Utilities.Core</HintPath>
</Reference>
<Reference Include="System.ComponentModel.Composition" />
</ItemGroup>
<ItemGroup>
<None Update="buildMultiTargeting\CapnpC.CSharp.MsBuild.Generation.props">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="build\CapnpC.CSharp.MsBuild.Generation.props">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="build\CapnpC.CSharp.MsBuild.Generation.targets">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="build\CapnpC.CSharp.MsBuild.Generation.tasks">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="build\CPS\Buildsystem\CpsExtension.DesignTime.targets">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="build\CPS\Buildsystem\Rules\CapnpFileType.xaml">
<Generator>MSBuild:Compile</Generator>
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="build\CPS\Buildsystem\Rules\ProjectItemsSchema.xaml">
<Generator>MSBuild:Compile</Generator>
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
<Exec Command="rmdir /s /q $(SolutionDir)MsBuildGenerationTest\obj" />
</Target>
</Project>

View File

@ -0,0 +1,31 @@
<?xml version="1.0"?>
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata>
<id>CapnpC.CSharp.MsBuild.Generation</id>
<version>$version$</version>
<title>CapnpC.CSharp.MsBuild.Generation</title>
<authors>Christian Köllner and contributors</authors>
<owners>Christian Köllner</owners>
<description>Package to enable the .capnp -> .cs file generation during build time</description>
<summary>Package to enable the .capnp -> .cs file generation during build time</summary>
<language>en-US</language>
<projectUrl>https://github.com/c80k/capnproto-dotnetcore</projectUrl>
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<license type="expression">MIT</license>
<tags>capnproto csharp msbuild</tags>
<copyright>Christian Köllner and contributors</copyright>
<dependencies>
<dependency id="Capnp.Net.Runtime" version="1.0" />
</dependencies>
</metadata>
<files>
<file src="build\**\*" target="build" />
<file src="buildMultiTargeting\**\*" target="buildMultiTargeting" />
<file src="bin\$configuration$\net471\*.dll" target="tasks\net471" />
<file src="bin\$configuration$\netcoreapp2.1\*.dll" target="tasks\netcoreapp2.1" />
<file src="bin\$configuration$\netcoreapp2.1\*.deps.json" target="tasks\netcoreapp2.1" />
<file src="..\Licenses\**\*" target="licenses" />
<file src="..\LICENSE" target="LICENSE" />
</files>
</package>

View File

@ -0,0 +1,99 @@
using CapnpC.CSharp.Generator;
using System;
using System.Collections.Generic;
using System.IO;
namespace CapnpC.CSharp.MsBuild.Generation
{
public class CapnpCodeBehindGenerator : IDisposable
{
public void InitializeProject(string projectPath)
{
}
public CsFileGeneratorResult GenerateCodeBehindFile(CapnpGenJob job)
{
string capnpFile = job.CapnpPath;
// Works around a weird capnp.exe behavior: When the input file is empty, it will spit out an exception dump
// instead of a parse error. But the parse error is nice because it contains a generated ID. We want the parse error!
// Workaround: Generate a temporary file that contains a single line break (such that it is not empty...)
try
{
if (File.Exists(capnpFile) && new FileInfo(capnpFile).Length == 0)
{
string tempFile = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName() + ".capnp");
File.WriteAllText(tempFile, Environment.NewLine);
try
{
var jobCopy = new CapnpGenJob()
{
CapnpPath = tempFile,
WorkingDirectory = job.WorkingDirectory
};
jobCopy.AdditionalArguments.AddRange(job.AdditionalArguments);
return GenerateCodeBehindFile(jobCopy);
}
finally
{
File.Delete(tempFile);
}
}
}
catch
{
}
var args = new List<string>();
args.AddRange(job.AdditionalArguments);
args.Add(capnpFile);
var result = CapnpCompilation.InvokeCapnpAndGenerate(args, job.WorkingDirectory);
if (result.IsSuccess)
{
if (result.GeneratedFiles.Count == 1)
{
return new CsFileGeneratorResult(
result.GeneratedFiles[0],
capnpFile + ".cs",
result.Messages);
}
else
{
return new CsFileGeneratorResult(
"Code generation produced more than one file. This is not supported.",
result.Messages);
}
}
else
{
switch (result.ErrorCategory)
{
case CapnpProcessFailure.NotFound:
return new CsFileGeneratorResult("Unable to find capnp.exe - please install capnproto on your system first.");
case CapnpProcessFailure.BadInput:
return new CsFileGeneratorResult("Invalid schema", result.Messages);
case CapnpProcessFailure.BadOutput:
return new CsFileGeneratorResult(
"Internal error: capnp.exe produced a binary code generation request which was not understood by the backend",
result.Messages);
default:
throw new NotSupportedException("Invalid error category");
}
}
}
public void Dispose()
{
}
}
}

View File

@ -0,0 +1,88 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
namespace CapnpC.CSharp.MsBuild.Generation
{
public class CapnpFileCodeBehindGenerator : ICapnpcCsharpGenerator
{
public CapnpFileCodeBehindGenerator(TaskLoggingHelper log)
{
Log = log ?? throw new ArgumentNullException(nameof(log));
}
public TaskLoggingHelper Log { get; }
public IEnumerable<string> GenerateFilesForProject(
string projectPath,
List<CapnpGenJob> capnpFiles,
string projectFolder)
{
using (var capnpCodeBehindGenerator = new CapnpCodeBehindGenerator())
{
capnpCodeBehindGenerator.InitializeProject(projectPath);
var codeBehindWriter = new CodeBehindWriter(null);
if (capnpFiles == null)
{
yield break;
}
foreach (var genJob in capnpFiles)
{
Log.LogMessage(MessageImportance.Normal, "Generate {0}, working dir = {1}, options = {2}",
genJob.CapnpPath,
genJob.WorkingDirectory,
string.Join(" ", genJob.AdditionalArguments));
var generatorResult = capnpCodeBehindGenerator.GenerateCodeBehindFile(genJob);
if (!generatorResult.Success)
{
if (!string.IsNullOrEmpty(generatorResult.Error))
{
Log.LogError("{0}", generatorResult.Error);
}
if (generatorResult.Messages != null)
{
foreach (var message in generatorResult.Messages)
{
if (message.IsParseSuccess)
{
Log.LogError(
subcategory: null,
errorCode: null,
helpKeyword: null,
file: genJob.CapnpPath,
lineNumber: message.Line,
columnNumber: message.Column,
endLineNumber: message.Line,
endColumnNumber: message.EndColumn == 0 ? message.Column : message.EndColumn,
"{0}",
message.MessageText);
}
else
{
Log.LogError("{0}", message.FullMessage);
}
}
}
continue;
}
var resultedFile = codeBehindWriter.WriteCodeBehindFile(generatorResult.Filename, generatorResult);
yield return FileSystemHelper.GetRelativePath(resultedFile, projectFolder);
}
}
}
}
}

View File

@ -0,0 +1,11 @@
using System.Collections.Generic;
namespace CapnpC.CSharp.MsBuild.Generation
{
public class CapnpGenJob
{
public string CapnpPath { get; set; }
public string WorkingDirectory { get; set; }
public List<string> AdditionalArguments { get; } = new List<string>();
}
}

View File

@ -0,0 +1,38 @@
using System;
using System.IO;
using Microsoft.Build.Utilities;
namespace CapnpC.CSharp.MsBuild.Generation
{
public class CodeBehindWriter
{
public CodeBehindWriter(TaskLoggingHelper log)
{
Log = log;
}
public TaskLoggingHelper Log { get; }
public string WriteCodeBehindFile(string outputPath, CsFileGeneratorResult testFileGeneratorResult)
{
string directoryPath = Path.GetDirectoryName(outputPath) ?? throw new InvalidOperationException();
Log?.LogWithNameTag(Log.LogMessage, directoryPath);
Log?.LogWithNameTag(Log.LogMessage, $"Writing data to {outputPath}; path = {directoryPath}; generatedFilename = {testFileGeneratorResult.Filename}");
if (File.Exists(outputPath))
{
if (!FileSystemHelper.FileCompareContent(outputPath, testFileGeneratorResult.GeneratedCode))
{
File.WriteAllText(outputPath, testFileGeneratorResult.GeneratedCode);
}
}
else
{
File.WriteAllText(outputPath, testFileGeneratorResult.GeneratedCode);
}
return outputPath;
}
}
}

View File

@ -0,0 +1,51 @@
using CapnpC.CSharp.Generator;
using System;
using System.Collections.Generic;
using System.Linq;
namespace CapnpC.CSharp.MsBuild.Generation
{
public class CsFileGeneratorResult
{
public CsFileGeneratorResult(FileGenerationResult generatorResult, string fileName, IReadOnlyList<CapnpMessage> messages)
{
if (generatorResult == null)
{
throw new ArgumentNullException(nameof(generatorResult));
}
Filename = fileName ?? throw new ArgumentNullException(nameof(fileName));
Error = generatorResult.Exception?.Message;
GeneratedCode = generatorResult.GeneratedContent;
Messages = messages;
}
public CsFileGeneratorResult(string error)
{
Error = error;
}
public CsFileGeneratorResult(string error, IReadOnlyList<CapnpMessage> messages)
{
Error = error;
Messages = messages;
}
/// <summary>
/// The error, if any.
/// </summary>
public string Error { get; }
/// <summary>
/// The generated code.
/// </summary>
public string GeneratedCode { get; }
public IReadOnlyList<CapnpMessage> Messages { get; }
public bool Success => Error == null;
public string Filename { get; }
}
}

View File

@ -0,0 +1,112 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Text;
namespace CapnpC.CSharp.MsBuild.Generation
{
public static class FileSystemHelper
{
public static void CopyFileToFolder(string filePath, string folderName)
{
File.Copy(filePath, Path.Combine(folderName, Path.GetFileName(filePath)));
}
public static string GetRelativePath(string path, string basePath)
{
path = Path.GetFullPath(path);
basePath = Path.GetFullPath(basePath);
if (String.Equals(path, basePath, StringComparison.OrdinalIgnoreCase))
return "."; // the "this folder"
if (path.StartsWith(basePath + Path.DirectorySeparatorChar, StringComparison.OrdinalIgnoreCase))
return path.Substring(basePath.Length + 1);
//handle different drives
string pathRoot = Path.GetPathRoot(path);
if (!String.Equals(pathRoot, Path.GetPathRoot(basePath), StringComparison.OrdinalIgnoreCase))
return path;
//handle ".." pathes
string[] pathParts = path.Substring(pathRoot.Length).Split(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
string[] basePathParts = basePath.Substring(pathRoot.Length).Split(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
int commonFolderCount = 0;
while (commonFolderCount < pathParts.Length && commonFolderCount < basePathParts.Length &&
String.Equals(pathParts[commonFolderCount], basePathParts[commonFolderCount], StringComparison.OrdinalIgnoreCase))
commonFolderCount++;
StringBuilder result = new StringBuilder();
for (int i = 0; i < basePathParts.Length - commonFolderCount; i++)
{
result.Append("..");
result.Append(Path.DirectorySeparatorChar);
}
if (pathParts.Length - commonFolderCount == 0)
return result.ToString().TrimEnd(Path.DirectorySeparatorChar);
result.Append(String.Join(Path.DirectorySeparatorChar.ToString(), pathParts, commonFolderCount, pathParts.Length - commonFolderCount));
return result.ToString();
}
// This method accepts two strings the represent two files to
// compare. A return value of true indicates that the contents of the files
// are the same. A return value of any other value indicates that the
// files are not the same.
public static bool FileCompare(string filePath1, string filePath2)
{
int file1byte;
int file2byte;
// Determine if the same file was referenced two times.
if (String.Equals(filePath1, filePath2, StringComparison.CurrentCultureIgnoreCase))
{
// Return true to indicate that the files are the same.
return true;
}
// Open the two files.
using (FileStream fs1 = new FileStream(filePath1, FileMode.Open, FileAccess.Read))
{
using (FileStream fs2 = new FileStream(filePath2, FileMode.Open, FileAccess.Read))
{
// Check the file sizes. If they are not the same, the files
// are not the same.
if (fs1.Length != fs2.Length)
{
// Return false to indicate files are different
return false;
}
// Read and compare a byte from each file until either a
// non-matching set of bytes is found or until the end of
// file1 is reached.
do
{
// Read one byte from each file.
file1byte = fs1.ReadByte();
file2byte = fs2.ReadByte();
} while ((file1byte == file2byte) && (file1byte != -1));
}
}
// Return the success of the comparison. "file1byte" is
// equal to "file2byte" at this point only if the files are
// the same.
return ((file1byte - file2byte) == 0);
}
// This method accepts two strings the represent two files to
// compare. A return value of true indicates that the contents of the files
// are the same. A return value of any other value indicates that the
// files are not the same.
public static bool FileCompareContent(string filePath1, string fileContent)
{
var currentFileContent = File.ReadAllText(filePath1);
return string.CompareOrdinal(currentFileContent, fileContent) == 0;
}
}
}

View File

@ -0,0 +1,136 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Resources;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
namespace CapnpC.CSharp.MsBuild.Generation
{
public class GenerateCapnpFileCodeBehindTask : Task
{
public GenerateCapnpFileCodeBehindTask()
{
CodeBehindGenerator = new CapnpFileCodeBehindGenerator(Log);
}
public ICapnpcCsharpGenerator CodeBehindGenerator { get; set; }
[Required]
public string ProjectPath { get; set; }
public string ProjectFolder => Path.GetDirectoryName(ProjectPath);
public ITaskItem[] CapnpFiles { get; set; }
[Output]
public ITaskItem[] GeneratedFiles { get; private set; }
static CapnpGenJob ToGenJob(ITaskItem item)
{
var job = new CapnpGenJob()
{
CapnpPath = item.GetMetadata("FullPath"),
WorkingDirectory = item.GetMetadata("WorkingDirectory")
};
string importPaths = item.GetMetadata("ImportPaths");
if (!string.IsNullOrWhiteSpace(importPaths))
{
job.AdditionalArguments.AddRange(importPaths.Split(new char[] { ';' },
StringSplitOptions.RemoveEmptyEntries).Select(p => $"-I\"{p.TrimEnd('\\')}\""));
}
string sourcePrefix = item.GetMetadata("SourcePrefix");
if (!string.IsNullOrWhiteSpace(sourcePrefix))
{
job.AdditionalArguments.Add(sourcePrefix);
}
string verbose = item.GetMetadata("Verbose");
if ("true".Equals(verbose, StringComparison.OrdinalIgnoreCase))
{
job.AdditionalArguments.Add("--verbose");
}
return job;
}
public override bool Execute()
{
try
{
try
{
var currentProcess = Process.GetCurrentProcess();
Log.LogWithNameTag(Log.LogMessage, $"process: {currentProcess.ProcessName}, pid: {currentProcess.Id}, CD: {Environment.CurrentDirectory}");
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
{
Log.LogWithNameTag(Log.LogMessage, " " + assembly.FullName);
}
}
catch (Exception e)
{
Log.LogWithNameTag(Log.LogMessage, $"Error when dumping process info: {e}");
}
AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;
var generator = CodeBehindGenerator ?? new CapnpFileCodeBehindGenerator(Log);
Log.LogWithNameTag(Log.LogMessage, "Starting GenerateCapnpFileCodeBehind");
var capnpFiles = CapnpFiles?.Select(ToGenJob).ToList() ?? new List<CapnpGenJob>();
var generatedFiles = generator.GenerateFilesForProject(
ProjectPath,
capnpFiles,
ProjectFolder);
GeneratedFiles = generatedFiles.Select(file => new TaskItem { ItemSpec = file }).ToArray();
return !Log.HasLoggedErrors;
}
catch (Exception e)
{
if (e.InnerException != null)
{
if (e.InnerException is FileLoadException fle)
{
Log?.LogWithNameTag(Log.LogError, $"FileLoadException Filename: {fle.FileName}");
Log?.LogWithNameTag(Log.LogError, $"FileLoadException FusionLog: {fle.FusionLog}");
Log?.LogWithNameTag(Log.LogError, $"FileLoadException Message: {fle.Message}");
}
Log?.LogWithNameTag(Log.LogError, e.InnerException.ToString());
}
Log?.LogWithNameTag(Log.LogError, e.ToString());
return false;
}
finally
{
AppDomain.CurrentDomain.AssemblyResolve -= CurrentDomain_AssemblyResolve;
}
}
private System.Reflection.Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
Log.LogWithNameTag(Log.LogMessage, args.Name);
return null;
}
}
}

View File

@ -0,0 +1,22 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Build.Utilities;
namespace CapnpC.CSharp.MsBuild.Generation
{
public static class LogExtensions
{
public static void LogWithNameTag(
this TaskLoggingHelper loggingHelper,
Action<string, object[]> loggingMethod,
string message,
params object[] messageArgs)
{
string fullMessage = $"[Cap'n Proto] {message}";
loggingMethod?.Invoke(fullMessage, messageArgs);
}
}
}

View File

@ -0,0 +1,9 @@
using System.Collections.Generic;
namespace CapnpC.CSharp.MsBuild.Generation
{
public interface ICapnpcCsharpGenerator
{
IEnumerable<string> GenerateFilesForProject(string projectPath, List<CapnpGenJob> jobs, string projectFolder);
}
}

View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8" ?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0">
<PropertyGroup>
<CpsExtensionSchemaDirectory Condition="'$(CpsExtensionSchemaDirectory)' == ''">$(MSBuildThisFileDirectory)Rules\</CpsExtensionSchemaDirectory>
</PropertyGroup>
<ItemGroup>
<PropertyPageSchema Include="$(CpsExtensionSchemaDirectory)\ProjectItemsSchema.xaml;"/>
<PropertyPageSchema Include="$(CpsExtensionSchemaDirectory)\CapnpFileType.xaml;">
<Context>File;BrowseObject</Context>
</PropertyPageSchema>
</ItemGroup>
</Project>

View File

@ -0,0 +1,46 @@
<?xml version="1.0" encoding="utf-8"?>
<!--Copyright, Microsoft Corporation, All rights reserved.-->
<Rule
Name="Capnp"
DisplayName="Cap'n Proto"
PageTemplate="tool"
Description="Cap'n Proto schema file"
xmlns="http://schemas.microsoft.com/build/2009/properties">
<Rule.DataSource>
<DataSource Persistence="ProjectFile" HasConfigurationCondition="True" ItemType="CapnpFiles" />
</Rule.DataSource>
<StringProperty Name="Identity" DisplayName="File Name" ReadOnly="true" Category="Misc">
<StringProperty.DataSource>
<DataSource Persistence="Intrinsic" ItemType="CapnpFiles" PersistedName="Identity" />
</StringProperty.DataSource>
</StringProperty>
<StringProperty Name="FullPath" DisplayName="Full Path" ReadOnly="true" Category="Misc">
<StringProperty.DataSource>
<DataSource Persistence="Intrinsic" ItemType="CapnpFiles" PersistedName="FullPath" />
</StringProperty.DataSource>
</StringProperty>
<StringProperty Name="DependentUpon" Visible="false" />
<StringProperty Name="Link" Visible="false" />
<!--<StringProperty Name="Generator" Visible="true" DisplayName="Custom Tool"/>-->
<!--<BoolProperty Name="Exclude" DisplayName="Exclude from build" Category="Misc" Visible="True"
Description="Whether to skip code generation for this item"/>-->
<StringProperty Name="WorkingDirectory" DisplayName="Working Directory" ReadOnly="false" Category="Misc" Visible="True"
Subtype="Folder" Description="Working directory for capnp"/>
<StringListProperty Name="ImportPaths" DisplayName="Import Paths" Category="Misc" Visible="True" Subtype="Folder"
Description="List of directories searched for non-relative imports (ones that start with a '/')"/>
<StringProperty Name="SourcePrefix" DisplayName="Source Prefix" Switch="src-prefix" SwitchPrefix="--" Category="Misc" Visible="True"
Description="If a file specified for compilation starts with the specified prefix, remove the prefix for the purpose of deciding the names of output files."/>
<BoolProperty Name="Verbose" DisplayName="Verbose" Switch="verbose" SwitchPrefix="--" Category="Misc" Visible="True"
Description="Log informational messages to stderr; useful for debugging."/>
</Rule>

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<!--Copyright, Microsoft Corporation, All rights reserved.-->
<ProjectSchemaDefinitions
xmlns="http://schemas.microsoft.com/build/2009/properties">
<ContentType
Name="CapnpFile"
DisplayName="CapnpFileType source file"
ItemType="CapnpFiles">
<NameValuePair Name="DependentExtensions" Value=".capnp.cs" />
</ContentType>
<ItemType Name="CapnpFiles" DisplayName="Cap'n Proto schema file"/>
<FileExtension Name=".capnp" ContentType="CapnpFile" />
</ProjectSchemaDefinitions>

View File

@ -0,0 +1,78 @@
<Project TreatAsLocalProperty="TaskFolder;TaskAssembly">
<PropertyGroup>
<CapnpcCsharp_CpsExtensionDesignTimeTargetsPath Condition="'$(CapnpcCsharp_CpsExtensionDesignTimeTargetsPath)' == ''">$(MSBuildThisFileDirectory)CPS\Buildsystem\CpsExtension.DesignTime.targets</CapnpcCsharp_CpsExtensionDesignTimeTargetsPath>
</PropertyGroup>
<Import Project="$(CapnpcCsharp_CpsExtensionDesignTimeTargetsPath)" Condition="'$(DesignTimeBuild)' == 'true' " />
<PropertyGroup>
<CapnpcCsharp_UseHostCompilerIfAvailable Condition="'$(CapnpcCsharp_UseHostCompilerIfAvailable)'==''">false</CapnpcCsharp_UseHostCompilerIfAvailable>
<UseHostCompilerIfAvailable>$(CapnpcCsharp_UseHostCompilerIfAvailable)</UseHostCompilerIfAvailable>
</PropertyGroup>
<PropertyGroup>
<OverwriteReadOnlyFiles Condition="'$(OverwriteReadOnlyFiles)'==''">false</OverwriteReadOnlyFiles>
<ForceGeneration Condition="'$(ForceGeneration)'==''">false</ForceGeneration>
<ShowTrace Condition="'$(ShowTrace)'==''">false</ShowTrace>
<VerboseOutput Condition="'$(VerboseOutput)'==''">true</VerboseOutput>
<CapnpcCsharp_DebugMSBuildTask Condition="'$(CapnpcCsharp_DebugMSBuildTask)' == ''">false</CapnpcCsharp_DebugMSBuildTask>
<_CapnpcCsharpPropsImported Condition="'$(_CapnpcCsharpPropsImported)'==''">true</_CapnpcCsharpPropsImported>
</PropertyGroup>
<!--
property group for feature flags
-->
<PropertyGroup>
<!--
feature flag to enable experimental support for cleaning up generated code-behind files during rebuild and clean scenarios
-->
<CapnpcCsharp_DeleteCodeBehindFilesOnCleanRebuild Condition="'$(CapnpcCsharp_DeleteCodeBehindFilesOnCleanRebuild)'==''">false</CapnpcCsharp_DeleteCodeBehindFilesOnCleanRebuild>
<!--
net.sdk support: feature flag to enable experimental support for net.sdk project system
-->
<CapnpcCsharp_EnableDefaultCompileItems Condition="'$(CapnpcCsharp_EnableDefaultCompileItems)'==''">true</CapnpcCsharp_EnableDefaultCompileItems>
<CapnpcCsharp_EnableWarnForFeatureCodeBehindFilesWithoutCorrespondingCapnpFile Condition="'$(CapnpcCsharp_EnableWarnForFeatureCodeBehindFilesWithoutCorrespondingCapnpFile)'==''">$(CapnpcCsharp_EnableDefaultCompileItems)</CapnpcCsharp_EnableWarnForFeatureCodeBehindFilesWithoutCorrespondingCapnpFile>
<DefaultItemExcludes>$(DefaultItemExcludes);**/*.capnp</DefaultItemExcludes>
</PropertyGroup>
<ItemGroup>
<CapnpFiles Include="**\*.capnp" >
<CodeBehindFile>%(RelativeDir)%(Filename).capnp.cs</CodeBehindFile>
<Visible>$(UsingMicrosoftNETSdk)</Visible>
<WorkingDirectory>$(ProjectDir)</WorkingDirectory>
</CapnpFiles>
<!-- obsolete codebehind files, scenarios:
- after rename operation
- after deletion of a feature file
- after pulling latest changes from version control with above changes
-->
<CapnpCsharpObsoleteCodeBehindFiles Include="**\*.capnp.cs" Exclude="@(CapnpFiles->'%(CodeBehindFile)')" />
<!-- Support for Visual Studio Incremental Build
https://github.com/techtalk/SpecFlow/issues/1319
-->
<UpToDateCheckInput Include="@(CapnpFiles)" />
<UpToDateCheckBuild Include="@(CapnpFiles->'%(CodeBehindFile)')" Original="@(CapnpFiles)" />
<CustomAdditionalCompileInputs Include="@(CapnpFiles->'%(CodeBehindFile)')" />
</ItemGroup>
<PropertyGroup>
<_CapnpcCsharp_TaskFolder Condition=" '$(MSBuildRuntimeType)' == 'Core' And '$(_CapnpcCsharp_TaskFolder)' == ''">netcoreapp2.1</_CapnpcCsharp_TaskFolder>
<_CapnpcCsharp_TaskFolder Condition=" '$(MSBuildRuntimeType)' != 'Core' And '$(_CapnpcCsharp_TaskFolder)' == ''">net471</_CapnpcCsharp_TaskFolder>
<_CapnpcCsharp_TaskAssembly Condition=" '$(_CapnpcCsharp_TaskAssembly)' == '' ">..\tasks\$(_CapnpcCsharp_TaskFolder)\CapnpC.CSharp.MsBuild.Generation.dll</_CapnpcCsharp_TaskAssembly>
</PropertyGroup>
<Import Project="CapnpC.CSharp.MsBuild.Generation.tasks"/>
</Project>

View File

@ -0,0 +1,133 @@
<Project>
<Import Project="CapnpC.CSharp.MsBuild.Generation.props" Condition="'$(_CapnpcCsharpPropsImported)'==''"/>
<PropertyGroup Condition="'$(BuildServerMode)' == ''">
<BuildServerMode Condition="'$(BuildingInsideVisualStudio)'=='true'">false</BuildServerMode>
<BuildServerMode Condition="'$(BuildingInsideVisualStudio)'!='true'">true</BuildServerMode>
<!--
net.sdk experimental support:
- currently we only want to support either classic project system or netsdk project system.
- currently we don't want to support globbing with classic project system => ensure globbing only get enabled with 'UsingMicrosoftNETSdk'
- currently we are supporting $(EnableDefaultCompileItems) for disabling globbing support for codebehind files
-->
<_CapnpcCsharp_EnableDefaultCompileItems Condition="'$(CapnpcCsharp_EnableDefaultCompileItems)' == '' And '$(UsingMicrosoftNETSdk)' == 'true'">true</_CapnpcCsharp_EnableDefaultCompileItems>
<_CapnpcCsharp_EnableDefaultCompileItems Condition="'$(CapnpcCsharp_EnableDefaultCompileItems)' == 'true' And '$(UsingMicrosoftNETSdk)' == 'true'">true</_CapnpcCsharp_EnableDefaultCompileItems>
</PropertyGroup>
<PropertyGroup>
<BuildDependsOn>
BeforeUpdateCapnpFilesInProject;
UpdateCapnpFilesInProject;
IncludeCodeBehindFilesInProject;
AfterUpdateCapnpFilesInProject;
$(BuildDependsOn)
</BuildDependsOn>
<CleanDependsOn>
CleanCapnpFilesInProject;
$(CleanDependsOn)
</CleanDependsOn>
<RebuildDependsOn>
SwitchToForceGenerate;
$(RebuildDependsOn)
</RebuildDependsOn>
</PropertyGroup>
<!--
net.sdk support: update default compile items to show generated files as nested items
-->
<ItemGroup Condition="'$(_CapnpcCsharp_EnableDefaultCompileItems)' == 'true' and '$(EnableDefaultItems)' == 'true' ">
<Compile Update="@(CapnpFiles->'%(CodeBehindFile)')"
DependentUpon="%(Filename)"
AutoGen="true"
DesignTime="true"
Visible="true"
Condition="'$(EnableDefaultCompileItems)' == 'true'" />
</ItemGroup>
<Target Name="WarnForCapnpCsharpCodeBehindFilesWithoutCorrespondingCapnpFile" AfterTargets="CoreCompile"
Condition="'$(CapnpcCsharp_EnableWarnForFeatureCodeBehindFilesWithoutCorrespondingCapnpFile)' == 'true'">
<Warning Text="For codebehind file '@(CapnpCsharpObsoleteCodeBehindFiles)', no capnp file was found." File="@(CapnpCsharpObsoleteCodeBehindFiles)" Condition="'@(CapnpCsharpObsoleteCodeBehindFiles)' != ''" />
</Target>
<Target Name="SwitchToForceGenerate">
<PropertyGroup>
<ForceGeneration>true</ForceGeneration>
</PropertyGroup>
</Target>
<Target Name="UpdateCapnpFilesInProject"
DependsOnTargets="BeforeUpdateCapnpFilesInProject">
<Message Text="CapnpFiles: @(CapnpFiles)" Importance="high" Condition="'$(VerboseOutput)' == 'true'" />
<GenerateCapnpFileCodeBehindTask
ProjectPath="$(MSBuildProjectFullPath)"
CapnpFiles="@(CapnpFiles)" >
<Output TaskParameter="GeneratedFiles" ItemName="CapnpcCsharpGeneratedFiles" />
</GenerateCapnpFileCodeBehindTask>
<Message Text="CapnpcCsharpGeneratedFiles: %(CapnpcCsharpGeneratedFiles.Identity)" Importance="high" Condition="'$(VerboseOutput)' == 'true'" />
<!--
net.sdk support: globbing does not support including files which are dynamically generated inside targets, we have to manually update compile items
-->
<ItemGroup Condition="'$(_CapnpcCsharp_EnableDefaultCompileItems)' == 'true' and '$(EnableDefaultItems)' == 'true' and '$(EnableDefaultCompileItems)' == 'true'">
<!-- if this is the first time generation of codebehind files, we have to manually add them as compile items -->
<Compile Include="@(CapnpFiles->'%(CodeBehindFile)')"
Exclude="@(Compile)"/>
<!--
eather if codebehind files are added manually to compile item group or are added by net.sdk globbing support,
ensure they are nested under feature files like in previous specflow versions
currently, we cannot use itemgroup update attribute inside a target because of some bugs in MSBuild (all items will be updated)
- https://github.com/Microsoft/msbuild/issues/1618
- https://github.com/Microsoft/msbuild/issues/2835
- https://github.com/Microsoft/msbuild/issues/1124
-->
<Compile DependentUpon="@(CapnpFiles)"
AutoGen="true"
DesignTime="true"
Visible="true"
Condition="'%(Compile.Identity)' == '@(CapnpFiles->'%(CodeBehindFile)')'" />
<!-- remove files which got obsolete, typically after rename operation, or getting changes from source control -->
<Compile Remove="@(CapnpCsharpObsoleteCodeBehindFiles)" />
</ItemGroup>
</Target>
<Target Name="BeforeUpdateCapnpFilesInProject">
</Target>
<Target Name="IncludeCodeBehindFilesInProject" DependsOnTargets="UpdateCapnpFilesInProject">
<ItemGroup Condition="'$(UsingMicrosoftNETSdk)' != 'true'">
<Compile Include="@(CapnpcCsharpGeneratedFiles)" Exclude="@(Compile)" />
</ItemGroup>
</Target>
<Target Name="AfterUpdateCapnpFilesInProject" DependsOnTargets="IncludeCodeBehindFilesInProject">
<!-- include any generated SpecFlow files in the compilation of the project if not included yet -->
</Target>
<Target Name="CleanCapnpFilesInProject" Condition="'$(CapnpcCsharp_DeleteCodeBehindFilesOnCleanRebuild)' == 'true'">
<!-- remove known codebehind files for existing capnp files -->
<Delete Files="%(CapnpFiles.CodeBehindFile)" ContinueOnError="true" />
<!-- remove obsolete codebehind files, scenarios:
- after rename operation
- after deletion of a capnp file
- after pulling latest changes from version control with above changes
-->
<Delete Files="@(CapnpCsharpObsoleteCodeBehindFiles)" ContinueOnError="true" />
</Target>
</Project>

Some files were not shown because too many files have changed in this diff Show More