Initial skeleton for MSBuild integration

This commit is contained in:
Christian Köllner 2019-09-04 22:29:23 +02:00
parent dbe6c29f8b
commit bf34494ae1
23 changed files with 1050 additions and 0 deletions

View File

@ -13,6 +13,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Capnp.Net.Runtime.Tests.Cor
EndProject 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.tests", "capnpc-csharp.tests\capnpc-csharp.tests.csproj", "{B77AC567-E232-4072-85C3-8689566BF3D4}"
EndProject 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
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
@ -39,6 +41,10 @@ Global
{B77AC567-E232-4072-85C3-8689566BF3D4}.Debug|Any CPU.Build.0 = Debug|Any CPU {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.ActiveCfg = Release|Any CPU
{B77AC567-E232-4072-85C3-8689566BF3D4}.Release|Any CPU.Build.0 = 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
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE

View File

@ -0,0 +1,4 @@

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

View File

@ -0,0 +1,79 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard2.0;netcoreapp2.1</TargetFrameworks>
<SignAssembly>false</SignAssembly>
<NuspecFile>$(MSBuildThisFileDirectory)Capnpc.Csharp.MsBuild.Generation.nuspec</NuspecFile>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
<NoPackageAnalysis>true</NoPackageAnalysis>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<EmbedUntrackedSources>true</EmbedUntrackedSources>
<AllowedOutputExtensionsInPackageBuildOutputFolder>$(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb</AllowedOutputExtensionsInPackageBuildOutputFolder>
</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="15.8.132" />
<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>
<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="build\CPS\Buildsystem\Rules\FeatureFileType.xaml">
<Generator>MSBuild:Compile</Generator>
</None>
<None Update="build\CPS\Buildsystem\Rules\ProjectItemsSchema.xaml">
<Generator>MSBuild:Compile</Generator>
</None>
</ItemGroup>
</Project>

View File

@ -0,0 +1,30 @@
<?xml version="1.0"?>
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata>
<id>Capnpc.Csharp.Generation</id>
<version>1.0.0</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>
</dependencies>
</metadata>
<files>
<file src="build\**\*" target="build" />
<file src="buildMultiTargeting\**\*" target="buildMultiTargeting" />
<file src="bin\Release\netstandard2.0\*.dll" target="tasks\netstandard2.0" />
<file src="bin\Release\netcoreapp2.1\*.dll" target="tasks\netcoreapp2.1" />
<file src="bin\Release\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,49 @@
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, string featureFile, TestFileGeneratorResult testFileGeneratorResult) //todo needs unit tests
{
//if (string.IsNullOrEmpty(testFileGeneratorResult.Filename))
//{
// Log?.LogWithNameTag(Log.LogError, $"{featureFile} has no generated filename");
// return null;
//}
//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.GeneratedTestCode))
// {
// File.WriteAllText(outputPath, testFileGeneratorResult.GeneratedTestCode);
// }
//}
//else
//{
// if (!Directory.Exists(directoryPath))
// {
// Directory.CreateDirectory(directoryPath);
// }
// File.WriteAllText(outputPath, testFileGeneratorResult.GeneratedTestCode);
//}
return outputPath;
}
}
}

View File

@ -0,0 +1,39 @@
using System;
using System.Collections.Generic;
using System.IO;
namespace Capnpc.Csharp.MsBuild.Generation
{
public class FeatureCodeBehindGenerator : IDisposable
{
//private SpecFlowProject _specFlowProject;
//private ITestGenerator _testGenerator;
public void InitializeProject(string projectPath, string rootNamespace, IEnumerable<string> generatorPlugins)
{
//_specFlowProject = MsBuildProjectReader.LoadSpecFlowProjectFromMsBuild(Path.GetFullPath(projectPath), rootNamespace);
//var projectSettings = _specFlowProject.ProjectSettings;
//var testGeneratorFactory = new TestGeneratorFactory();
//_testGenerator = testGeneratorFactory.CreateGenerator(projectSettings, generatorPlugins);
}
public TestFileGeneratorResult GenerateCodeBehindFile(string featureFile)
{
//var featureFileInput = new FeatureFileInput(featureFile);
//var generatedFeatureFileName = Path.GetFileName(_testGenerator.GetTestFullPath(featureFileInput));
//var testGeneratorResult = _testGenerator.GenerateTestFile(featureFileInput, new GenerationSettings());
return new TestFileGeneratorResult(null, null);
}
public void Dispose()
{
//_testGenerator?.Dispose();
}
}
}

View File

@ -0,0 +1,78 @@
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 class FeatureFileCodeBehindGenerator : ICapnpCsharpGenerator
{
private readonly FilePathGenerator _filePathGenerator;
public FeatureFileCodeBehindGenerator(TaskLoggingHelper log)
{
Log = log ?? throw new ArgumentNullException(nameof(log));
_filePathGenerator = new FilePathGenerator();
}
public TaskLoggingHelper Log { get; }
public IEnumerable<string> GenerateFilesForProject(
string projectPath,
string rootNamespace,
List<string> CapnpFiles,
List<string> generatorPlugins,
string projectFolder,
string outputPath)
{
using (var featureCodeBehindGenerator = new FeatureCodeBehindGenerator())
{
featureCodeBehindGenerator.InitializeProject(projectPath, rootNamespace, generatorPlugins);
var codeBehindWriter = new CodeBehindWriter(null);
if (CapnpFiles == null)
{
yield break;
}
foreach (var featureFile in CapnpFiles)
{
var featureFileItemSpec = featureFile;
var generatorResult = featureCodeBehindGenerator.GenerateCodeBehindFile(featureFileItemSpec);
if (!generatorResult.Success)
{
foreach (var error in generatorResult.Errors)
{
//Log.LogError(
// null,
// null,
// null,
// featureFile,
// error.Line,
// error.LinePosition,
// 0,
// 0,
// error.Message);
}
continue;
}
var targetFilePath = _filePathGenerator.GenerateFilePath(
projectFolder,
outputPath,
featureFile,
generatorResult.Filename);
var resultedFile = codeBehindWriter.WriteCodeBehindFile(targetFilePath, featureFile, generatorResult);
yield return FileSystemHelper.GetRelativePath(resultedFile, projectFolder);
}
}
}
}
}

View File

@ -0,0 +1,31 @@
using System;
using System.IO;
namespace Capnpc.Csharp.MsBuild.Generation
{
public class FilePathGenerator
{
public string GenerateFilePath(string projectFolder, string relativeOutputPath, string featureFileName, string generatedCodeBehindFileName)
{
if (projectFolder is null)
{
throw new ArgumentNullException(nameof(projectFolder));
}
if (featureFileName is null)
{
throw new ArgumentNullException(nameof(featureFileName));
}
if (generatedCodeBehindFileName is null)
{
throw new ArgumentNullException(nameof(generatedCodeBehindFileName));
}
string featureFileFullPath = Path.GetFullPath(Path.Combine(projectFolder, relativeOutputPath ?? "", featureFileName));
string featureFileDirPath = Path.GetDirectoryName(featureFileFullPath);
return Path.Combine(featureFileDirPath, generatedCodeBehindFileName);
}
}
}

View File

@ -0,0 +1,204 @@
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);
}
public static void CopyDirectory(string sourcePath, string destPath, bool cleanDestination = true)
{
if (cleanDestination)
EnsureEmptyFolder(destPath);
else
{
if (!Directory.Exists(destPath))
{
Directory.CreateDirectory(destPath);
}
}
foreach (string file in Directory.GetFiles(sourcePath))
{
var fileName = Path.GetFileName(file);
Debug.Assert(fileName != null);
string dest = Path.Combine(destPath, fileName);
File.Copy(file, dest, true);
File.SetAttributes(dest, File.GetAttributes(dest) & (~FileAttributes.ReadOnly));
}
foreach (string folder in Directory.GetDirectories(sourcePath))
{
var folderName = Path.GetFileName(folder);
Debug.Assert(folderName != null);
string dest = Path.Combine(destPath, folderName);
CopyDirectory(folder, dest);
}
}
public static void EnsureEmptyFolder(string folderName)
{
folderName = Path.GetFullPath(folderName);
if (!Directory.Exists(folderName))
{
Directory.CreateDirectory(folderName);
return;
}
DeleteFolderContent(folderName);
}
public static void EnsureFolderOfFile(string filePath)
{
string directory = Path.GetDirectoryName(filePath);
if (directory != null && !Directory.Exists(directory))
Directory.CreateDirectory(directory);
}
private static void Retry(int number, Action action)
{
try
{
action();
}
catch (UnauthorizedAccessException)
{
var i = number - 1;
if (i == 0)
throw;
Retry(i, action);
}
}
public static void DeleteFolderContent(string folderName)
{
foreach (string file in Directory.GetFiles(folderName))
{
try
{
Retry(5, () => File.Delete(file));
}
catch (Exception ex)
{
throw new Exception("Unable to delete file: " + file, ex);
}
}
foreach (string folder in Directory.GetDirectories(folderName))
{
try
{
Retry(5, () => Directory.Delete(folder, true));
}
catch (Exception ex)
{
throw new Exception("Unable to delete folder: " + folder, ex);
}
}
}
public static void DeleteFolder(string path)
{
if (!Directory.Exists(path))
return;
DeleteFolderContent(path);
Retry(5, () => Directory.Delete(path, true));
}
}
}

View File

@ -0,0 +1,112 @@
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 FeatureFileCodeBehindGenerator(Log);
}
public ICapnpCsharpGenerator CodeBehindGenerator { get; set; }
[Required]
public string ProjectPath { get; set; }
public string RootNamespace { get; set; }
public string ProjectFolder => Path.GetDirectoryName(ProjectPath);
public string OutputPath { get; set; }
public ITaskItem[] CapnpFiles { get; set; }
public ITaskItem[] GeneratorPlugins { get; set; }
[Output]
public ITaskItem[] GeneratedFiles { get; private set; }
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 FeatureFileCodeBehindGenerator(Log);
Log.LogWithNameTag(Log.LogMessage, "Starting GenerateFeatureFileCodeBehind");
var generatorPlugins = GeneratorPlugins?.Select(gp => gp.ItemSpec).ToList() ?? new List<string>();
var capnpFiles = CapnpFiles?.Select(i => i.ItemSpec).ToList() ?? new List<string>();
var generatedFiles = generator.GenerateFilesForProject(
ProjectPath,
RootNamespace,
capnpFiles,
generatorPlugins,
ProjectFolder,
OutputPath);
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 = $"[SpecFlow] {message}";
loggingMethod?.Invoke(fullMessage, messageArgs);
}
}
}

View File

@ -0,0 +1,9 @@
using System.Collections.Generic;
namespace Capnpc.Csharp.MsBuild.Generation
{
public interface ICapnpCsharpGenerator
{
IEnumerable<string> GenerateFilesForProject(string projectPath, string rootNamespace, List<string> CapnpFiles, List<string> generatorPlugins, string projectFolder, string outputPath);
}
}

View File

@ -0,0 +1,42 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace Capnpc.Csharp.MsBuild.Generation
{
public class TestFileGeneratorResult
{
public TestFileGeneratorResult(TestGeneratorResult generatorResult, string fileName)
{
if (generatorResult == null)
{
throw new ArgumentNullException(nameof(generatorResult));
}
Filename = fileName ?? throw new ArgumentNullException(nameof(fileName));
Errors = generatorResult.Errors;
IsUpToDate = generatorResult.IsUpToDate;
GeneratedTestCode = generatorResult.GeneratedTestCode;
}
/// <summary>
/// The errors, if any.
/// </summary>
public IEnumerable<TestGenerationError> Errors { get; }
/// <summary>
/// The generated file was up-to-date.
/// </summary>
public bool IsUpToDate { get; }
/// <summary>
/// The generated test code.
/// </summary>
public string GeneratedTestCode { get; }
public bool Success => Errors == null || !Errors.Any();
public string Filename { get; }
}
}

View File

@ -0,0 +1,6 @@
namespace Capnpc.Csharp.MsBuild.Generation
{
public class TestGenerationError
{
}
}

View File

@ -0,0 +1,11 @@
using System.Collections.Generic;
namespace Capnpc.Csharp.MsBuild.Generation
{
public class TestGeneratorResult
{
public IEnumerable<TestGenerationError> Errors { get; internal set; }
public bool IsUpToDate { get; internal set; }
public string GeneratedTestCode { get; internal set; }
}
}

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)\FeatureFileType.xaml;">
<Context>File;BrowseObject</Context>
</PropertyPageSchema>
</ItemGroup>
</Project>

View File

@ -0,0 +1,34 @@
<?xml version="1.0" encoding="utf-8"?>
<!--Copyright, Microsoft Corporation, All rights reserved.-->
<Rule
Name="Feature"
DisplayName="Feature"
PageTemplate="tool"
Description="SpecFlow Feature 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>
<!--<BoolProperty Name="Visible" Visible="true" />-->
<StringProperty Name="DependentUpon" Visible="false" />
<StringProperty Name="Link" Visible="false" />
<StringProperty Name="Generator" Visible="true" DisplayName="Custom Tool"/>
</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="FeatureFile"
DisplayName="FeatureFileType source file"
ItemType="CapnpFiles">
<NameValuePair Name="DependentExtensions" Value=".feature.cs" />
</ContentType>
<ItemType Name="CapnpFiles" DisplayName="Feature file"/>
<FileExtension Name=".feature" ContentType="FeatureFile" />
</ProjectSchemaDefinitions>

View File

@ -0,0 +1,77 @@
<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>
<_SpecFlowPropsImported Condition="'$(_SpecFlowPropsImported)'==''">true</_SpecFlowPropsImported>
</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_EnableWarnForFeatureCodeBehindFilesWithoutCorrespondingFeatureFile Condition="'$(CapnpcCsharp_EnableWarnForFeatureCodeBehindFilesWithoutCorrespondingFeatureFile)'==''">$(CapnpcCsharp_EnableDefaultCompileItems)</CapnpcCsharp_EnableWarnForFeatureCodeBehindFilesWithoutCorrespondingFeatureFile>
<DefaultItemExcludes>$(DefaultItemExcludes);**/*.feature</DefaultItemExcludes>
</PropertyGroup>
<ItemGroup>
<CapnpFiles Include="**\*.capnp" >
<CodeBehindFile>%(RelativeDir)%(Filename).capnp(DefaultLanguageSourceExtension)</CodeBehindFile>
<Visible>$(UsingMicrosoftNETSdk)</Visible>
</CapnpFiles>
<!-- obsolete codebehind files, scenarios:
- after rename operation
- after deletion of a feature file
- after pulling latest changes from version control with above changes
-->
<SpecFlowObsoleteCodeBehindFiles Include="**\*.capnp(DefaultLanguageSourceExtension)" 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.0</_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,145 @@
<Project>
<Import Project="Capnpc.Csharp.MsBuild.Generation.props" Condition="'$(_SpecFlowPropsImported)'==''"/>
<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="WarnForFeatureCodeBehindFilesWithoutCorrespondingFeatureFile" AfterTargets="CoreCompile"
Condition="'$(CapnpcCsharp_EnableWarnForFeatureCodeBehindFilesWithoutCorrespondingFeatureFile)' == 'true'">
<Warning Text="For codebehind file '@(SpecFlowObsoleteCodeBehindFiles)', no feature file was found." File="@(SpecFlowObsoleteCodeBehindFiles)" Condition="'@(SpecFlowObsoleteCodeBehindFiles)' != ''" />
</Target>
<Target Name="SwitchToForceGenerate">
<PropertyGroup>
<ForceGeneration>true</ForceGeneration>
</PropertyGroup>
</Target>
<Target Name="UpdateCapnpFilesInProject"
DependsOnTargets="BeforeUpdateCapnpFilesInProject">
<Message Text="CapnpFiles: @(CapnpFiles)" Importance="high" Condition="'$(VerboseOutput)' == 'true'" />
<Error
Text="CapnpCsharp codebehind generation is not compatible with MSBuild codebehind generation. The custom tool must be removed from the file."
File="@(None)"
Condition="%(None.Extension) == '.feature' AND %(None.Generator) == 'SpecFlowSingleFileGenerator'"/>
<!-- <PropertyGroup>
<SpecFlowCodeBehindOutputPath Condition="'$(SpecFlowCodeBehindOutputPath)' == ''">Features.Generated</SpecFlowCodeBehindOutputPath>
</PropertyGroup> -->
<GenerateFeatureFileCodeBehindTask
ProjectPath="$(MSBuildProjectFullPath)"
OutputPath="$(SpecFlowCodeBehindOutputPath)"
CapnpFiles="@(CapnpFiles)"
RootNamespace="$(RootNamespace)"
GeneratorPlugins="@(SpecFlowGeneratorPlugins)" >
<Output TaskParameter="GeneratedFiles" ItemName="CapnpCsharpGeneratedFiles" />
</GenerateFeatureFileCodeBehindTask>
<Message Text="CapnpCsharpGeneratedFiles: %(CapnpCsharpGeneratedFiles.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="@(SpecFlowObsoleteCodeBehindFiles)" />
</ItemGroup>
</Target>
<Target Name="BeforeUpdateCapnpFilesInProject">
</Target>
<Target Name="IncludeCodeBehindFilesInProject" DependsOnTargets="UpdateCapnpFilesInProject">
<ItemGroup Condition="'$(UsingMicrosoftNETSdk)' != 'true'">
<Compile Include="@(CapnpCsharpGeneratedFiles)" 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 feature files -->
<Delete Files="%(CapnpFiles.CodeBehindFile)" ContinueOnError="true" />
<!-- remove obsolete codebehind files, scenarios:
- after rename operation
- after deletion of a feature file
- after pulling latest changes from version control with above changes
-->
<Delete Files="@(SpecFlowObsoleteCodeBehindFiles)" ContinueOnError="true" />
</Target>
</Project>

View File

@ -0,0 +1,3 @@
<Project>
<UsingTask TaskName="Capnpc.Csharp.MsBuild.Generation.GenerateFeatureFileCodeBehindTask" AssemblyFile="$(_CapnpcCsharp_TaskAssembly)" />
</Project>

View File

@ -0,0 +1,5 @@
<Project TreatAsLocalProperty="TaskFolder;TaskAssembly">
<Import Project="..\build\Capnpc.Csharp.MsBuild.Generation.props"/>
</Project>

View File

@ -0,0 +1,31 @@
SpecFlow Licence (New BSD License)
Copyright (c) 2009, TechTalk
Disclaimer:
* The initial codebase of Specflow was written by TechTalk employees.
No 3rd party code was included.
* No code of customer projects was used to create this project.
* TechTalk had the full rights to publish the initial codebase.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the SpecFlow project nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL TECHTALK OR CONTRIBUTORS BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.