From 8a83666e52bb1d8dc13ba10a65794b3e7322fb35 Mon Sep 17 00:00:00 2001 From: Gaspar Nagy Date: Tue, 24 Nov 2009 17:01:44 +0100 Subject: [PATCH] =?UTF-8?q?=EF=BB=BFfirst=20version=20of=20generator=20too?= =?UTF-8?q?l=20+=20msbuild=20task?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Generator/BatchGenerator.cs | 97 ++++++++++++ .../BatchTestGenerator.cs | 0 .../Configuration/MsBuildProjectReader.cs | 9 +- Generator/TechTalk.SpecFlow.Generator.csproj | 1 + TechTalk.SpecFlow.sln | 12 +- .../MsBuild/BatchTestGeneratorTask.cs | 0 Tools/MsBuild/GeneratorTask.cs | 65 ++++++++ Tools/MsBuild/GeneratorTaskBase.cs | 148 ++++++++++++++++++ Tools/MsBuild/TaskBase.cs | 118 ++++++++++++++ {GeneratorTools => Tools}/Program.cs | 0 .../Properties/AssemblyInfo.cs | 0 .../TechTalk.SpecFlow.Tools.csproj | 0 Tools/TechTalk.SpecFlow.targets | 65 ++++++++ Tools/TechTalk.SpecFlow.tasks | 14 ++ 14 files changed, 521 insertions(+), 8 deletions(-) create mode 100644 Generator/BatchGenerator.cs rename {GeneratorTools/BatchTestGeneration => Generator}/BatchTestGenerator.cs (100%) rename {GeneratorTools => Tools}/MsBuild/BatchTestGeneratorTask.cs (100%) create mode 100644 Tools/MsBuild/GeneratorTask.cs create mode 100644 Tools/MsBuild/GeneratorTaskBase.cs create mode 100644 Tools/MsBuild/TaskBase.cs rename {GeneratorTools => Tools}/Program.cs (100%) rename {GeneratorTools => Tools}/Properties/AssemblyInfo.cs (100%) rename GeneratorTools/TechTalk.SpecFlow.GeneratorTools.csproj => Tools/TechTalk.SpecFlow.Tools.csproj (100%) create mode 100644 Tools/TechTalk.SpecFlow.targets create mode 100644 Tools/TechTalk.SpecFlow.tasks diff --git a/Generator/BatchGenerator.cs b/Generator/BatchGenerator.cs new file mode 100644 index 000000000..f08dcfcc0 --- /dev/null +++ b/Generator/BatchGenerator.cs @@ -0,0 +1,97 @@ +using System; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Text; +using TechTalk.SpecFlow.Generator.Configuration; + +namespace TechTalk.SpecFlow.Generator +{ + public class BatchGenerator + { + private readonly TextWriter traceWriter; + private readonly bool verboseOutput; + + public BatchGenerator(TextWriter traceWriter, bool verboseOutput) + { + this.traceWriter = traceWriter; + this.verboseOutput = verboseOutput; + } + + public void ProcessProject(SpecFlowProject specFlowProject, bool forceGenerate) + { + if (verboseOutput) + traceWriter.WriteLine("Processing project: " + specFlowProject); + + SpecFlowGenerator generator = new SpecFlowGenerator(specFlowProject); + + foreach (var featureFile in specFlowProject.FeatureFiles) + { + string featureFileFullPath = featureFile.GetFullPath(specFlowProject); + + string codeFileFullPath = featureFileFullPath + ".cs"; + + bool needsProcessing = true; + + if (!forceGenerate && IsUpToDate(generator, featureFileFullPath, codeFileFullPath)) + { + needsProcessing = false; + } + + if (verboseOutput || needsProcessing) + { + traceWriter.WriteLine("Processing file: {0}", featureFileFullPath); + if (!needsProcessing) + traceWriter.WriteLine(" up-to-date"); + } + + if (needsProcessing) + { + GenerateFile(generator, featureFile, codeFileFullPath); + } + } + } + + protected virtual void GenerateFile(SpecFlowGenerator generator, SpecFlowFeatureFile featureFile, string codeFileFullPath) + { + using (var writer = GetWriter(codeFileFullPath)) + { + generator.GenerateCSharpTestFile(featureFile, writer); + } + } + + protected virtual StreamWriter GetWriter(string codeFileFullPath) + { + return new StreamWriter(codeFileFullPath, false, Encoding.UTF8); + } + + private static bool IsUpToDate(SpecFlowGenerator generator, string featureFileFullPath, string codeFileFullPath) + { + // check existance of the target file + if (!File.Exists(codeFileFullPath)) + return false; + + // check modification time of the target file + try + { + var featureFileModificationTime = File.GetLastWriteTime(featureFileFullPath); + var codeFileModificationTime = File.GetLastWriteTime(codeFileFullPath); + + if (featureFileModificationTime > codeFileModificationTime) + return false; + } + catch (Exception ex) + { + Debug.WriteLine(ex); + return false; + } + + // check tools version + var codeFileVersion = generator.GetGeneratedFileSpecFlowVersion(codeFileFullPath); + if (codeFileVersion == null || codeFileVersion != generator.GetCurrentSpecFlowVersion()) + return false; + + return true; + } + } +} \ No newline at end of file diff --git a/GeneratorTools/BatchTestGeneration/BatchTestGenerator.cs b/Generator/BatchTestGenerator.cs similarity index 100% rename from GeneratorTools/BatchTestGeneration/BatchTestGenerator.cs rename to Generator/BatchTestGenerator.cs diff --git a/Generator/Configuration/MsBuildProjectReader.cs b/Generator/Configuration/MsBuildProjectReader.cs index 5227ee828..cd4093810 100644 --- a/Generator/Configuration/MsBuildProjectReader.cs +++ b/Generator/Configuration/MsBuildProjectReader.cs @@ -11,8 +11,13 @@ public static class MsBuildProjectReader public static SpecFlowProject LoadSpecFlowProjectFromMsBuild(string projectFile) { projectFile = Path.GetFullPath(projectFile); - Project project = new Project(); - project.Load(projectFile, ProjectLoadSettings.IgnoreMissingImports); + + Project project = Engine.GlobalEngine.GetLoadedProject(projectFile); + if (project == null) + { + project = new Project(); + project.Load(projectFile, ProjectLoadSettings.IgnoreMissingImports); + } string projectFolder = Path.GetDirectoryName(projectFile); diff --git a/Generator/TechTalk.SpecFlow.Generator.csproj b/Generator/TechTalk.SpecFlow.Generator.csproj index fd61232ff..0c9364756 100644 --- a/Generator/TechTalk.SpecFlow.Generator.csproj +++ b/Generator/TechTalk.SpecFlow.Generator.csproj @@ -58,6 +58,7 @@ VersionInfo.cs + diff --git a/TechTalk.SpecFlow.sln b/TechTalk.SpecFlow.sln index 01a360048..84db78353 100644 --- a/TechTalk.SpecFlow.sln +++ b/TechTalk.SpecFlow.sln @@ -32,10 +32,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DevenvSetupCustomAction", " EndProject Project("{54435603-DBB4-11D2-8724-00A0C9A8B90C}") = "SpecFlowSetup", "Installer\SpecFlowSetup\SpecFlowSetup.vdproj", "{F6740296-282C-4A0F-941E-A8FD1B1DAC2D}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TechTalk.SpecFlow.GeneratorTools", "GeneratorTools\TechTalk.SpecFlow.GeneratorTools.csproj", "{87BE7FE6-C3DE-4409-ABF6-FA5B60AF3DE1}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TechTalk.SpecFlow.Generator", "Generator\TechTalk.SpecFlow.Generator.csproj", "{453D8014-B6CD-4E86-80A8-D59F59092334}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TechTalk.SpecFlow.Tools", "Tools\TechTalk.SpecFlow.Tools.csproj", "{87BE7FE6-C3DE-4409-ABF6-FA5B60AF3DE1}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -73,14 +73,14 @@ Global {F6740296-282C-4A0F-941E-A8FD1B1DAC2D}.Debug|Any CPU.ActiveCfg = Debug {F6740296-282C-4A0F-941E-A8FD1B1DAC2D}.Release|Any CPU.ActiveCfg = Release {F6740296-282C-4A0F-941E-A8FD1B1DAC2D}.Release|Any CPU.Build.0 = Release - {87BE7FE6-C3DE-4409-ABF6-FA5B60AF3DE1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {87BE7FE6-C3DE-4409-ABF6-FA5B60AF3DE1}.Debug|Any CPU.Build.0 = Debug|Any CPU - {87BE7FE6-C3DE-4409-ABF6-FA5B60AF3DE1}.Release|Any CPU.ActiveCfg = Release|Any CPU - {87BE7FE6-C3DE-4409-ABF6-FA5B60AF3DE1}.Release|Any CPU.Build.0 = Release|Any CPU {453D8014-B6CD-4E86-80A8-D59F59092334}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {453D8014-B6CD-4E86-80A8-D59F59092334}.Debug|Any CPU.Build.0 = Debug|Any CPU {453D8014-B6CD-4E86-80A8-D59F59092334}.Release|Any CPU.ActiveCfg = Release|Any CPU {453D8014-B6CD-4E86-80A8-D59F59092334}.Release|Any CPU.Build.0 = Release|Any CPU + {87BE7FE6-C3DE-4409-ABF6-FA5B60AF3DE1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {87BE7FE6-C3DE-4409-ABF6-FA5B60AF3DE1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {87BE7FE6-C3DE-4409-ABF6-FA5B60AF3DE1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {87BE7FE6-C3DE-4409-ABF6-FA5B60AF3DE1}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/GeneratorTools/MsBuild/BatchTestGeneratorTask.cs b/Tools/MsBuild/BatchTestGeneratorTask.cs similarity index 100% rename from GeneratorTools/MsBuild/BatchTestGeneratorTask.cs rename to Tools/MsBuild/BatchTestGeneratorTask.cs diff --git a/Tools/MsBuild/GeneratorTask.cs b/Tools/MsBuild/GeneratorTask.cs new file mode 100644 index 000000000..b85e23374 --- /dev/null +++ b/Tools/MsBuild/GeneratorTask.cs @@ -0,0 +1,65 @@ +using System; +using System.CodeDom.Compiler; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using Microsoft.Build.Framework; +using TechTalk.SpecFlow.Generator; +using TechTalk.SpecFlow.Generator.Configuration; + +namespace TechTalk.SpecFlow.Tools.MsBuild +{ + internal class MsBuildBatchGenerator : BatchGenerator + { + private readonly GeneratorTaskBase task; + + public MsBuildBatchGenerator(TextWriter traceWriter, bool verboseOutput, GeneratorTaskBase task) : base(traceWriter, verboseOutput) + { + this.task = task; + } + + private GeneratorTaskBase.OutputFile outputFile = null; + + protected override StreamWriter GetWriter(string codeFileFullPath) + { + outputFile = task.PrepareOutputFile(codeFileFullPath); + + return base.GetWriter(outputFile.FilePathForWriting); + } + + protected override void GenerateFile(SpecFlowGenerator generator, SpecFlowFeatureFile featureFile, string codeFileFullPath) + { + try + { + base.GenerateFile(generator, featureFile, codeFileFullPath); + outputFile.Done(task.Errors); + } + catch(Exception ex) + { + task.RecordException(ex); + } + finally + { + outputFile = null; + } + } + } + + public class GeneratorTask : GeneratorTaskBase + { + public bool VerboseOutput { get; set; } + public bool ForceGeneration { get; set; } + + [Required] + public string ProjectPath { get; set; } + + protected override void DoExecute() + { + SpecFlowProject specFlowProject = MsBuildProjectReader.LoadSpecFlowProjectFromMsBuild(ProjectPath); + + BatchGenerator batchGenerator = new MsBuildBatchGenerator(GetMessageWriter(), VerboseOutput, this); + batchGenerator.ProcessProject(specFlowProject, ForceGeneration); + } + } +} \ No newline at end of file diff --git a/Tools/MsBuild/GeneratorTaskBase.cs b/Tools/MsBuild/GeneratorTaskBase.cs new file mode 100644 index 000000000..4895ca679 --- /dev/null +++ b/Tools/MsBuild/GeneratorTaskBase.cs @@ -0,0 +1,148 @@ +using System; +using System.CodeDom.Compiler; +using System.Diagnostics; +using System.IO; +using System.Linq; + +namespace TechTalk.SpecFlow.Tools.MsBuild +{ + public abstract class GeneratorTaskBase : TaskBase + { + public bool BuildServerMode { get; set; } + public bool OverwriteReadOnlyFiles { get; set; } + + public class OutputFile + { + public string FilePath { get; private set; } + + public virtual string FilePathForWriting + { + get { return FilePath; } + } + + public OutputFile(string filePath) + { + FilePath = filePath; + } + + public virtual void Done(CompilerErrorCollection result) + { + // nop; + } + } + + public class VerifyDifferenceOutputFile : OutputFile + { + public string TempFilePath { get; private set; } + + public override string FilePathForWriting + { + get { return TempFilePath; } + } + + public VerifyDifferenceOutputFile(string filePath) : base(filePath) + { + TempFilePath = Path.Combine(Path.GetTempPath(), "tmp" + Path.GetFileName(filePath)); + } + + public override void Done(CompilerErrorCollection result) + { + Compare(TempFilePath, FilePath, result); + } + + private void Compare(string path1, string path2, CompilerErrorCollection result) + { + if (FileCompare(path1, path2)) + return; + + string message = String.Format("Error during file generation. The target file '{0}' is read-only, but different from the transformation result. This problem can be a sign of an inconsistent source code package. Compile and check-in the current version of the file from the development environment or remove the read-only flag from the generation result. To compile a solution that contains messaging project on a build server, you can also exclude the messaging project from the build-server solution or set the msbuild project parameter to 'true' in the messaging project file.", Path.GetFullPath(path2)); + result.Add(new CompilerError(String.Empty, 0, 0, null, message)); + } + } + + public OutputFile PrepareOutputFile(string outputFilePath) + { + if (OverwriteReadOnlyFiles) + { + RemoveReadOnly(outputFilePath); + } + + bool isReadOnly = IsReadOnly(outputFilePath); + if (isReadOnly && BuildServerMode) + return new VerifyDifferenceOutputFile(outputFilePath); + + return new OutputFile(outputFilePath); + } + + // 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. + private 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); + } + + private bool IsReadOnly(string path) + { + try + { + FileInfo fileInfo = new FileInfo(path); + if (!fileInfo.Exists) + return false; + return fileInfo.IsReadOnly; + + } + catch (Exception ex) + { + Debug.WriteLine(ex, "IsReadOnly"); + + // if there is an exception, we let the generation to discover the real problem + return false; + } + } + + private void RemoveReadOnly(string path) + { + FileInfo fileInfo = new FileInfo(path); + if (fileInfo.Exists && fileInfo.IsReadOnly) + fileInfo.IsReadOnly = false; + } + } +} \ No newline at end of file diff --git a/Tools/MsBuild/TaskBase.cs b/Tools/MsBuild/TaskBase.cs new file mode 100644 index 000000000..2486b5995 --- /dev/null +++ b/Tools/MsBuild/TaskBase.cs @@ -0,0 +1,118 @@ +using System; +using System.CodeDom.Compiler; +using System.IO; +using System.Linq; +using System.Text; +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; + +namespace TechTalk.SpecFlow.Tools.MsBuild +{ + public abstract class TaskBase : Task + { + public bool ShowTrace { get; set;} + + protected internal CompilerErrorCollection Errors { get; private set; } + + public override bool Execute() + { + Errors = new CompilerErrorCollection(); + try + { + DoExecute(); + } + catch (Exception ex) + { + RecordException(ex); + } + + + // handle errors + if (Errors.Count > 0) + { + bool hasErrors = false; + foreach (CompilerError error in Errors) + { + if (error.IsWarning) + OutputWarning(error.ToString(), error.ErrorText, error.FileName, error.Line, error.Column); + else + { + OutputError(error.ToString(), error.ErrorText, error.FileName, error.Line, error.Column); + hasErrors = true; + } + } + + return !hasErrors; + } + + return true; + } + + public void RecordException(Exception ex) + { + string message = ex.Message; + if (ShowTrace) + message += Environment.NewLine + ex; + Errors.Add(new CompilerError(String.Empty, 0, 0, null, message)); + } + + protected void OutputError(string outString, string message, string fileName, int lineNumber, int columnNumber) + { + message = message.TrimEnd('\n', '\r'); + + Log.LogError(null, null, null, fileName, lineNumber, columnNumber, 0, 0, message); + } + + protected void OutputWarning(string outString, string message, string fileName, int lineNumber, int columnNumber) + { + message = message.TrimEnd('\n', '\r'); + + Log.LogWarning(null, null, null, fileName, lineNumber, columnNumber, 0, 0, message); + } + + protected void OutputInformation(MessageImportance importance, string message, params object[] messageArgs) + { + message = message.TrimEnd('\n', '\r'); + + Log.LogMessage(importance, message, messageArgs); + } + + private class MessageTextWriter : TextWriter + { + private TaskBase task; + MessageImportance importance = MessageImportance.Normal; + + public MessageTextWriter(TaskBase task) + { + this.task = task; + } + + public override Encoding Encoding + { + get { return Encoding.Unicode; } + } + + public override void Write(char value) + { + Write(value.ToString()); + } + + public override void Write(char[] buffer, int index, int count) + { + Write(new string(buffer, index, count)); + } + + public override void Write(string value) + { + task.OutputInformation(importance, value); + } + } + + protected TextWriter GetMessageWriter() + { + return new MessageTextWriter(this); + } + + protected abstract void DoExecute(); + } +} \ No newline at end of file diff --git a/GeneratorTools/Program.cs b/Tools/Program.cs similarity index 100% rename from GeneratorTools/Program.cs rename to Tools/Program.cs diff --git a/GeneratorTools/Properties/AssemblyInfo.cs b/Tools/Properties/AssemblyInfo.cs similarity index 100% rename from GeneratorTools/Properties/AssemblyInfo.cs rename to Tools/Properties/AssemblyInfo.cs diff --git a/GeneratorTools/TechTalk.SpecFlow.GeneratorTools.csproj b/Tools/TechTalk.SpecFlow.Tools.csproj similarity index 100% rename from GeneratorTools/TechTalk.SpecFlow.GeneratorTools.csproj rename to Tools/TechTalk.SpecFlow.Tools.csproj diff --git a/Tools/TechTalk.SpecFlow.targets b/Tools/TechTalk.SpecFlow.targets new file mode 100644 index 000000000..22e63ce30 --- /dev/null +++ b/Tools/TechTalk.SpecFlow.targets @@ -0,0 +1,65 @@ + + + + + + + specflow.exe + + + + + + + + + + false + + false + false + false + + + + false + true + + + + + <__FeatureFiles Include="@(Content)" Condition=" '%(Content.Extension)' == '.feature' " /> + + + + + + + + + + + + + + + + diff --git a/Tools/TechTalk.SpecFlow.tasks b/Tools/TechTalk.SpecFlow.tasks new file mode 100644 index 000000000..35b939eea --- /dev/null +++ b/Tools/TechTalk.SpecFlow.tasks @@ -0,0 +1,14 @@ + + + + specflow.exe + + + + + + + +