Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Change project reader from MSBuild to XML #837

Merged
merged 10 commits into from Apr 21, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -12,15 +12,18 @@ public class ProjectPlatformSettings
/// <summary>
/// The version of the <see cref="Language"/>. Optinal, defaults to C# 3.0.
/// </summary>
[Obsolete("Not used anymore, will be removed with SpecFlow 3")]
public Version LanguageVersion { get; set; }

/// <summary>
/// Specifies the target platform of the project. Optional, defaults to .NET 3.5.
/// </summary>
[Obsolete("Not used anymore, will be removed with SpecFlow 3")]
public string Platform { get; set; }
/// <summary>
/// The version of the <see cref="Platform"/>. Optional, defaults to .NET 3.5.
/// </summary>
[Obsolete("Not used anymore, will be removed with SpecFlow 3")]
public Version PlatformVersion { get; set; }

public ProjectPlatformSettings()
Expand Down

This file was deleted.

227 changes: 194 additions & 33 deletions TechTalk.SpecFlow.Generator/Project/MsBuildProjectReader.cs
@@ -1,79 +1,240 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Xml;
using Microsoft.Build.Evaluation;
using System.Xml.Linq;
using TechTalk.SpecFlow.Generator.Configuration;
using TechTalk.SpecFlow.Generator.Interfaces;

namespace TechTalk.SpecFlow.Generator.Project
{
public class MsBuildProjectReader : ISpecFlowProjectReader
{
private readonly IGeneratorConfigurationProvider configurationLoader;
private readonly IGeneratorConfigurationProvider _configurationLoader;

public MsBuildProjectReader(IGeneratorConfigurationProvider configurationLoader)
{
this.configurationLoader = configurationLoader;
}

public static SpecFlowProject LoadSpecFlowProjectFromMsBuild(string projectFilePath)
{
return new MsBuildProjectReader(new GeneratorConfigurationProvider()).ReadSpecFlowProject(projectFilePath);
_configurationLoader = configurationLoader;
}

public SpecFlowProject ReadSpecFlowProject(string projectFilePath)
{
Microsoft.Build.Evaluation.Project project = ProjectCollection.GlobalProjectCollection.LoadProject(projectFilePath);
var projectFolder = Path.GetDirectoryName(projectFilePath);

string projectFolder = Path.GetDirectoryName(projectFilePath);
using (var filestream = new FileStream(projectFilePath, FileMode.Open))
{
var xDocument = XDocument.Load(filestream);

var specFlowProject = new SpecFlowProject();
specFlowProject.ProjectSettings.ProjectFolder = projectFolder;
specFlowProject.ProjectSettings.ProjectName = Path.GetFileNameWithoutExtension(projectFilePath);
specFlowProject.ProjectSettings.AssemblyName = project.AllEvaluatedProperties.First(x=>x.Name=="AssemblyName").EvaluatedValue;
specFlowProject.ProjectSettings.DefaultNamespace =project.AllEvaluatedProperties.First(x=>x.Name=="RootNamespace").EvaluatedValue;
var newProjectSystem = IsNewProjectSystem(xDocument);

var specFlowProject = new SpecFlowProject();
specFlowProject = LoadProjectSettings(specFlowProject, xDocument, projectFolder, projectFilePath, newProjectSystem);
specFlowProject = LoadFeatureFiles(specFlowProject, xDocument, projectFolder, newProjectSystem);
specFlowProject = LoadAppConfig(specFlowProject, xDocument, projectFolder, newProjectSystem);

return specFlowProject;
}
}

specFlowProject.ProjectSettings.ProjectPlatformSettings.Language = GetLanguage(project);
private SpecFlowProject LoadProjectSettings(SpecFlowProject specFlowProject, XDocument xDocument, string projectFolder, string projectFilePath,
bool newProjectSystem)
{
var projectSettings = specFlowProject.ProjectSettings;

foreach (ProjectItem item in project.FeatureFiles())
projectSettings.ProjectFolder = projectFolder;
projectSettings.ProjectName = Path.GetFileNameWithoutExtension(projectFilePath);
projectSettings.DefaultNamespace = GetMsBuildProperty(xDocument, newProjectSystem, "RootNamespace");
projectSettings.AssemblyName = GetMsBuildProperty(xDocument, newProjectSystem, "AssemblyName");

if (newProjectSystem)
{
var featureFile = new FeatureFileInput(item.EvaluatedInclude);
var ns = item.GetMetadataValue("CustomToolNamespace");
if (!String.IsNullOrEmpty(ns))
featureFile.CustomNamespace = ns;
specFlowProject.FeatureFiles.Add(featureFile);

if (string.IsNullOrWhiteSpace(projectSettings.AssemblyName))
{
projectSettings.AssemblyName = projectSettings.ProjectName;
}

if (string.IsNullOrWhiteSpace(projectSettings.DefaultNamespace))
{
projectSettings.DefaultNamespace = projectSettings.ProjectName;
}
}

ProjectItem appConfigItem = project.ApplicationConfigurationFile();
if (appConfigItem != null)
projectSettings.ProjectPlatformSettings.Language = GetLanguage(xDocument, newProjectSystem);

return specFlowProject;
}

private string GetMsBuildProperty(XDocument xDocument, bool newProjectSystem, string propertyName)
{
return xDocument.Descendants(GetNameWithNamespace(propertyName, newProjectSystem)).SingleOrDefault()?.Value;
}

private SpecFlowProject LoadAppConfig(SpecFlowProject specFlowProject, XDocument xDocument, string projectFolder, bool newProjectSystem)
{
var appConfigFile = GetAppConfigFile(xDocument, newProjectSystem, projectFolder);
if (!string.IsNullOrWhiteSpace(appConfigFile))
{
var configFilePath = Path.Combine(projectFolder, appConfigItem.EvaluatedInclude);
var configFilePath = Path.Combine(projectFolder, appConfigFile);
var configFileContent = File.ReadAllText(configFilePath);
var configurationHolder = GetConfigurationHolderFromFileContent(configFileContent);
specFlowProject.ProjectSettings.ConfigurationHolder = configurationHolder;
specFlowProject.Configuration = configurationLoader.LoadConfiguration(configurationHolder);
specFlowProject.Configuration = _configurationLoader.LoadConfiguration(configurationHolder);
}

return specFlowProject;
}

private string GetLanguage(Microsoft.Build.Evaluation.Project project)
private SpecFlowProject LoadFeatureFiles(SpecFlowProject specFlowProject, XDocument xDocument, string projectFolder, bool newProjectSystem)
{
if (project.Imports.Any(i => i.ImportingElement.Project.Contains("Microsoft.VisualBasic.targets")))
return GenerationTargetLanguage.VB;
foreach (var item in GetFeatureFiles(xDocument, newProjectSystem, projectFolder))
{
specFlowProject.FeatureFiles.Add(item);
}

return specFlowProject;
}

private bool IsNewProjectSystem(XDocument xDocument)
{
return xDocument.Root.Attribute("Sdk") != null;
}

public static SpecFlowProject LoadSpecFlowProjectFromMsBuild(string projectFilePath)
{
return new MsBuildProjectReader(new GeneratorConfigurationProvider()).ReadSpecFlowProject(projectFilePath);
}

private string GetAppConfigFile(XDocument xDocument, bool newProjectSystem, string projectFolder)
{
var nodesWhereFeatureFilesCouldBe = GetNotCompileableNodes(xDocument, newProjectSystem);

foreach (var xElement in nodesWhereFeatureFilesCouldBe)
{
var include = xElement.Attribute("Include")?.Value;
if (string.IsNullOrWhiteSpace(include))
{
continue;
}

if (Path.GetFileName(include).Equals("app.config", StringComparison.InvariantCultureIgnoreCase))
{
return include;
}
}

if (project.Imports.Any(i => i.ImportingElement.Project.Contains("Microsoft.CSharp.targets")))
if (newProjectSystem)
{
var appConfigFilePath = Path.Combine(projectFolder, "app.config");

if (File.Exists(appConfigFilePath))
{
return appConfigFilePath;
}
}

return null;
}

private IEnumerable<FeatureFileInput> GetFeatureFiles(XDocument xDocument, bool newProjectSystem, string projectFolder)
{
var nodesWhereFeatureFilesCouldBe = GetNotCompileableNodes(xDocument, newProjectSystem);

var result = new List<FeatureFileInput>();

foreach (var xElement in nodesWhereFeatureFilesCouldBe)
{
var include = xElement.Attribute("Include")?.Value;
var update = xElement.Attribute("Update")?.Value;

if (string.IsNullOrWhiteSpace(include) && string.IsNullOrWhiteSpace(update))
{
continue;
}

var fileName = string.IsNullOrWhiteSpace(update) ? include : update;

if (IsAFeatureFile(fileName))
{
var featureFile = new FeatureFileInput(fileName);

var customNamespace = xElement.Descendants(GetNameWithNamespace("CustomToolNamespace", newProjectSystem)).SingleOrDefault();
if (customNamespace != null)
{
featureFile.CustomNamespace = customNamespace.Value;
}

result.Add(featureFile);
}
}

if (newProjectSystem)
{
var allFilesInProjectFolder = Directory.EnumerateFiles(projectFolder, "*", SearchOption.AllDirectories);

foreach (var file in allFilesInProjectFolder)
{
if (IsAFeatureFile(Path.GetFileName(file)))
{
if (result.Any(i => file.EndsWith(i.ProjectRelativePath)))
{
continue;
}

var realtivePath = file.Replace(projectFolder, "").Trim('\\');

result.Add(new FeatureFileInput(realtivePath));
}
}
}

return result;
}

private bool IsAFeatureFile(string fileName)
{
return fileName.EndsWith(".feature", StringComparison.InvariantCultureIgnoreCase) ||
fileName.EndsWith(".feature.xlsx", StringComparison.InvariantCultureIgnoreCase);
}

private IEnumerable<XElement> GetNotCompileableNodes(XDocument xDocument, bool newProjectSystem)
{
var contentNodes = xDocument.Descendants(GetNameWithNamespace("Content", newProjectSystem));
var noneNodes = xDocument.Descendants(GetNameWithNamespace("None", newProjectSystem));

var nodesWhereFeatureFilesCouldBe = contentNodes.Union(noneNodes);
return nodesWhereFeatureFilesCouldBe;
}

private XName GetNameWithNamespace(string localName, bool newProjectSystem)
{
if (newProjectSystem)
{
return XName.Get(localName);
}

return XName.Get(localName, "http://schemas.microsoft.com/developer/msbuild/2003");
}

private string GetLanguage(XDocument project, bool newProjectSystem)
{
var imports = project.Descendants(GetNameWithNamespace("Import", newProjectSystem)).ToList();

if (imports.Any(n => n.Attribute("Project")?.Value.Contains("Microsoft.CSharp.targets") ?? false))
{
return GenerationTargetLanguage.CSharp;
}

if (imports.Any(n => n.Attribute("Project")?.Value.Contains("Microsoft.VisualBasic.targets") ?? false))
{
return GenerationTargetLanguage.VB;
}

return GenerationTargetLanguage.CSharp;
}

private static SpecFlowConfigurationHolder GetConfigurationHolderFromFileContent(string configFileContent)
private SpecFlowConfigurationHolder GetConfigurationHolderFromFileContent(string configFileContent)
{
try
{
Expand Down
Expand Up @@ -40,7 +40,6 @@
<HintPath>..\packages\Gherkin.4.0.0\lib\net45\Gherkin.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.Build" />
<Reference Include="System" />
<Reference Include="System.configuration" />
<Reference Include="System.Core">
Expand Down Expand Up @@ -73,7 +72,6 @@
<Compile Include="Plugins\IGeneratorPlugin.cs" />
<Compile Include="Plugins\IGeneratorPluginLoader.cs" />
<Compile Include="Project\ISpecFlowProjectReader.cs" />
<Compile Include="Project\MsBuildProjectFileExtensions.cs" />
<Compile Include="Project\MsBuildProjectReader.cs" />
<Compile Include="Configuration\RuntimeConfigurationReader.cs" />
<Compile Include="Configuration\InProcGeneratorInfoProvider.cs" />
Expand Down
2 changes: 1 addition & 1 deletion TechTalk.SpecFlow.Tools/MsBuild/TaskBase.cs
Expand Up @@ -10,7 +10,7 @@

namespace TechTalk.SpecFlow.Tools.MsBuild
{
public abstract class TaskBase : Task
public abstract class TaskBase : AppDomainIsolatedTask
{
public bool ShowTrace { get; set;}

Expand Down
@@ -0,0 +1,8 @@
using System;

namespace NetStandardLib
{
public class Class1
{
}
}
@@ -0,0 +1,11 @@
Feature: SocialLogins
In order to avoid silly mistakes
As a math idiot
I want to be told the sum of two numbers

@mytag
Scenario: Add two numbers
Given I have entered 50 into the calculator
And I have entered 70 into the calculator
When I press add
Then the result should be 120 on the screen
@@ -0,0 +1,11 @@
Feature: CreateWorkflowDefinition
In order to avoid silly mistakes
As a math idiot
I want to be told the sum of two numbers

@mytag
Scenario: Add two numbers
Given I have entered 50 into the calculator
And I have entered 70 into the calculator
When I press add
Then the result should be 120 on the screen