TFService MSBUILD and T4 Code Generation - code-generation

We are using a T4 file to manage our assembly versioning;
Basically we are following these procedure to regenerate our Assembly versionings on each build.
This is done by having the .csproj use the Modeling SDK for VS 2013 in order to customize T4 regeneration configuration with MSBUILD.
This works great! Unfortunately team-build for Team Foundation Service does not appear to support T4 code generation at build time as the instances of VS 12 on the build server do not have the TextTemplating.targets in the default install location; assumedly the Modeling/VS SDKs are not installed on the build server (from the TFService build failure logs):
The imported project "C:\Program Files (x86)\MSBuild\12.0\Microsoft\VisualStudio\v12.0\TextTemplating\Microsoft.TextTemplating.targets" was not found. Confirm that the path in the declaration is correct, and that the file exists on disk.
Are there plans to install the Microsoft-released VS 2013 addons (VS SDK, Modeling SDK, etc) to the build server image?
Is there a work around I can implement so that when I commit my code to Team Foundation Service via Git, the build won't break?
How can I configure TFService's Hosted Build Controller to transform my T4 files during its builds?

I ended up creating a different solution to the task of having the build server auto-version our assemblies. Instead of using T4 to dynamically create an AssemblyInfo.cs file to share via item link to other projects within the solution (and therefore have to deal with the annoyance of figuring out how to keep the regeneration of the file current for this purpose), I avoided T4 altogether. It was too much work integrating with the 3rd party remote build machine, managing the addons and SDKs that allow for T4 manipulation etc. There is a much simpler solution involving MSBuild Community Tasks that I found from this SO question. The solution is elegantly simple compared to T4! Here's how it shakes down:
Each time this project, Application.Versioning.csproj, is built, MSBUILD tasks defined in the 3rd-party, Nuget-installed 'MSBuildCommunity Tasks' library
dynamically generate [AssemblyFileVersion], [AssemblyInformationalVersion], [AssemblyVersion] based off of the current UTC date time and input them into a new file,
AutoVersion.cs, that resides in this project's 'Properties' directory (alongside the AssemblyInfo.cs file). The AssemblyInfo.cs has these assembly attributes
forever culled to avoid build errors of multiply-defined assembly attributes). After generation of AutoVersion.cs (which happens just before build), the compiler
integrates the aforementioned assembly versioning attributes into the built assembly. This versioning reflects the UTC time at which they were built (see below).
Every other .csproj in the .sln subscribes to this dynamic, build-time assembly versioning creating a file link to the generated AutoVersion.cs file.
These assemblies must also have their AssemblyInfo.cs' pruned. The assembly versioning for all subscribing .csprojs in the .sln is done each time the MyApplication.Versioning.csproj
is built (or rebuilt). Here's the .csproj for the versioning project:
<!-- START DYNAMIC ASSEMBLY VERSIONING WORK-->
<!--MSBuild Community Tasks path as installed via the nuget package 'Install-Package MSBuildTasks'-->
<PropertyGroup>
<MSBuildCommunityTasksPath>$(MSBuildThisFileDirectory)..\.build</MSBuildCommunityTasksPath>
<My-PropertiesDir>Properties</My-PropertiesDir>
</PropertyGroup>
<PropertyGroup>
<!--this should only be incremented (starting at zero) for MAJOR application releases this should never be reset only incremented!-->
<ManualMajorVersion>0</ManualMajorVersion>
<!--3 digits maximum should only be manually incremented (starting at zero) for feature releases-->
<!--!this should be reset to '0' at the start of each caldenar Year-->
<ManualMinorVersion>0</ManualMinorVersion>
</PropertyGroup>
<!--Import MSBuild Community Tasks library installed from Nuget -->
<!--This library contains defined MSBUILD tasks useful for dynamically generating Assembly information via MSBUILD https://github.com/loresoft/msbuildtasks-->
<Import Project="$(MSBuildCommunityTasksPath)\MSBuild.Community.Tasks.Targets" />
<Target Name="BeforeBuild">
<Time Format="yy.MM.dd.HHmm" Kind="Utc">
<Output TaskParameter="FormattedTime" PropertyName="My-VersionNumber" />
</Time>
<Message Text="Auto versioning from UTC time: $(My-VersionNumber) ...">
</Message>
<Time Format="yy" Kind="Utc">
<Output TaskParameter="FormattedTime" PropertyName="Year" />
</Time>
<Time Format="MM" Kind="Utc">
<Output TaskParameter="FormattedTime" PropertyName="Month" />
</Time>
<Time Format="dd" Kind="Utc">
<Output TaskParameter="FormattedTime" PropertyName="Day" />
</Time>
<Time Format="HH" Kind="Utc">
<Output TaskParameter="FormattedTime" PropertyName="Hour" />
</Time>
<Time Format="mm" Kind="Utc">
<Output TaskParameter="FormattedTime" PropertyName="Minute" />
</Time>
<ItemGroup>
<My-AssemblyInfo Include="$(My-PropertiesDir)\AutoVersion.cs" />
<Compile Include="#(My-AssemblyInfo)" />
</ItemGroup>
<MakeDir Directories="$(My-PropertiesDir)" />
<PropertyGroup>
<GeneratedAssemblyVersion>$(ManualMajorVersion).$(Year)$(ManualMinorVersion).$(Month)$(Day).$(Hour)$(Minute)</GeneratedAssemblyVersion>
</PropertyGroup>
<AssemblyInfo OutputFile="#(My-AssemblyInfo)" CodeLanguage="CS" AssemblyFileVersion="$(GeneratedAssemblyVersion)" AssemblyInformationalVersion="$(GeneratedAssemblyVersion)" AssemblyVersion="$(GeneratedAssemblyVersion)" Condition="$(GeneratedAssemblyVersion) != '' " />
</Target>
<!-- END DYNAMIC ASSEMBLY VERSIONING WORK-->
as well as unit test to verify for yourself:
/// <summary> A test to validate the configured, auto-generated assembly versioning is working as expected </summary>
[Test]
public void AssemblyVersioningTest()
{
DirectoryInfo currentDirectory = new DirectoryInfo(Directory.GetCurrentDirectory());
DirectoryInfo versioningDir = new DirectoryInfo(currentDirectory.FullName + "\\" + VERSIONING_DYNAMIC_FILE_DIRECTORY);
// verify versioning directory located/loaded/exists
Assert.IsTrue(versioningDir.Exists);
// locate the VERSIONING_DYNAMIC_FILE file within the VERSIONING_DYNAMIC_FILE_DIRECTORY directory
string dynamicFilePath = versioningDir.FullName + "\\" + VERSIONING_DYNAMIC_FILE;
// get the FileInfo for the file that is used to dynamically generate assembly versioning
FileInfo dynamicVersioningFileInfo = new FileInfo(dynamicFilePath);
Assert.IsTrue(dynamicVersioningFileInfo.Exists);
// get the two digit creation Dates/Times for the assembly's file as strings
// since that's what the versioning reflects
DateTime dynamicVersioningFileLastWriteTime = dynamicVersioningFileInfo.LastWriteTime;
#region Created VERSIONING_DYNAMIC_FILE
string dynamicVersioningFileLastWriteTimeYear = dynamicVersioningFileLastWriteTime.ToUniversalTime().ToString("yy");
string dynamicVersioningFileLastWriteTimeMonth = dynamicVersioningFileLastWriteTime.ToUniversalTime().ToString("MM");
string dynamicVersioningFileLastWriteTimeDay = dynamicVersioningFileLastWriteTime.ToUniversalTime().ToString("dd");
string dynamicVersioningFileLastWriteTimeHour = dynamicVersioningFileLastWriteTime.ToUniversalTime().ToString("HH");
string dynamicVersioningFileLastWriteTimeMinute = dynamicVersioningFileLastWriteTime.ToUniversalTime().ToString("mm");
#endregion Created VERSIONING_DYNAMIC_FILE
// get *this* assembly from the .sln using reflection since this assembly consumes/is dependent upon the versioning functionality we
// are testing
Assembly theAssemblyExecutingThisTest = Assembly.GetExecutingAssembly();
// get this assembly's version
// we will investigate this to compare it to a reverse-engineering of what we would
// expect it to be based
AssemblyName testingAssemblyName = theAssemblyExecutingThisTest.GetName();
Version testingAssemblyVersion = testingAssemblyName.Version;
#region Generated Assembly Versioning
// get the first two digits of the assemblyVersion.MinorVersion - these represent the year
string testingAssemblyVersionMinorYear = testingAssemblyVersion.Minor.ToString().Substring(0, 2);
// the rest of the digits represent the manually-configured version and can be 1-3 chars long
string testingAssemblyVersionMinorManual = GetMinorManualFromVersionString(testingAssemblyVersion.Minor.ToString());
string testingAssemblyVersionBuildMonth = testingAssemblyVersion.Build.ToString("D4").Substring(0, 2);
string testingAssemblyVersionBuildDay = testingAssemblyVersion.Build.ToString("D4").Substring(2, 2);
string testingAssemblyVersionRevisionHour = testingAssemblyVersion.Revision.ToString("D4").Substring(0, 2);
string testingAssemblyVersionRevisionMinute = testingAssemblyVersion.Revision.ToString("D4").Substring(2, 2);
#endregion Generated Assembly Versioning
// verify the assembly's minor version: last two digits match of assembly file creation year
// (pad minorversion 4 spaces in case released minor version is empty)
Assert.AreEqual(dynamicVersioningFileLastWriteTimeYear,
testingAssemblyVersionMinorYear,
"Assembly's minor version: last two digits do not match assembly file last write time year");
// verify the assembly's minor version is comprised of two digit 'released minor version' + two digit year of assembly file creation date
Assert.AreEqual(dynamicVersioningFileLastWriteTimeYear + testingAssemblyVersionMinorManual,
testingAssemblyVersionMinorYear + testingAssemblyVersionMinorManual,
"Assembly's minor version: not comprised of two digit year of assembly file last write time date + dynamically-sized 'minor manual version' + ");
// verify the Assembly's build version is comprised of two digit month + two digit day of assembly file creation date
Assert.AreEqual(dynamicVersioningFileLastWriteTimeMonth + dynamicVersioningFileLastWriteTimeDay,
testingAssemblyVersionBuildMonth + testingAssemblyVersionBuildDay,
"Assembly's build version: not comprised of two digit month + two digit day of assembly file last write time date");
// verify the Assembly's revision version is comprised two digit hour + two digit minute of assembly file creation date
Assert.AreEqual(dynamicVersioningFileLastWriteTimeHour + dynamicVersioningFileLastWriteTimeMinute,
testingAssemblyVersionRevisionHour + testingAssemblyVersionRevisionMinute,
"Assembly's revision version: comprised two digit hour + two digit minute of assembly file last write time date");
}

We solved a similar issue by including the V&M SDK files (Microsoft.TextTemplating.Build.Tasks.dll, Microsoft.TextTemplating.targets, Microsoft.VisualStudio.TextTemplating.Sdk.Host.1X.0.dll) in an external_libraries folder in source control.
IMO this is the One True Way to manage 3rd party SDKs in large projects: installation on every individual dev machine and build server is too brittle to be practical.

Related

NuGet: Two packages with targets files lead to incorrectly copied DLLs

I have created two NuGet packages that contain Native Libraries. The first one has two subfolders (amd64 and x86) the second one includes the DLL flat under the build directory. The NativeLibraries of the the first package are supposed to be copied into subfolders of the OutputPath. The only DLL in the second package should be copied flat under the OutputPath. I used the following stackoverflow entry as a guide for creating the package: https://stackoverflow.com/a/30316946/4496150
The first NuGet package folder structure looks like this (amd64 and x86 subfolder under build):
build
amd64
DLL1.dll
DLL2.dll
x86
DLL1.dll
DLL2.dll
packagename.targets
First targets file:
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<NativeLibs Include="$(MSBuildThisFileDirectory)**\*.dll" />
<None Include="#(NativeLibs)">
<Link>%(RecursiveDir)%(FileName)%(Extension)</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>
Please notice $(MSBuildThisFileDirectory)** as NativeLibs Include and %(RecursiveDir) as part of Link attribute.
The second NuGet package structure looks like this (no subfolders under build):
build
DLL1.dll
packagename.targets
Second targets file:
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<NativeLibs Include="$(MSBuildThisFileDirectory)\*.dll" />
<None Include="#(NativeLibs)">
<Link>%(FileName)%(Extension)</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>
Please notice $(MSBuildThisFileDirectory) (without **) as NativeLibs Include and missing %(RecursiveDir) in Link attribute.
If you reference both packages, the DLLs from the first package are additionally copied flat into the output folder, which is not desired. I suspect this is because the second include step also reads the DLLs from the first package, but then does not use %(RecursiveDir).
A workaround is to also include in the second package both.
and NativeLibs Include="$(MSBuildThisFileDirectory)***.dll" /> exactly as in the first package.
However, I would prefer to understand why the second package ensures that the DLLs from the first are copied flat.
If I read https://learn.microsoft.com/de-de/visualstudio/msbuild/msbuild-reserved-and-well-known-properties?view=vs-2019 correctly, $(MSBuildThisFileDirectory) points to the folder in my NuGet cache where the targets file of the NuGet package is located. So actually everything would be correct. But still (probably) wrong DLLs are copied.
Edit
I added the following snippet in both targets files to get some output:
<Target Name="OutputPackage1NativeLibs" BeforeTargets="Build">
<Message Text="Package1 %(NativeLibs.Identity)" Importance="high"/>
</Target>
For the seconds targets file I changed the target name to OutputPackage2NativeLibs and started the text Output with Packag2.
When I clean my NuGet Package Cache and rebuild the solution everything is fine. But after the third or forth rebuild operation the DLLs of the first package are copied flat under the Output path and I get the following output:
Package1 C:\Users\USERNAME\.nuget\packages\PACKAGENAME1\1.2.3.4\build\amd64\DLL1.dll
Package1 C:\Users\USERNAME\.nuget\packages\PACKAGENAME1\1.2.3.4\build\amd64\DLL2.dll
Package1 C:\Users\USERNAME\.nuget\packages\PACKAGENAME1\1.2.3.4\build\x86\DLL1.dll
Package1 C:\Users\USERNAME\.nuget\packages\PACKAGENAME1\1.2.3.4\build\x86\DLL2.dll
Package1 C:\Users\USERNAME\.nuget\packages\PACKAGENAME2\1.0.0.0\build\DLL1.dll
Package2 C:\Users\USERNAME\.nuget\packages\PACKAGENAME1\1.2.3.4\build\amd64\DLL1.dll
Package2 C:\Users\USERNAME\.nuget\packages\PACKAGENAME1\1.2.3.4\build\amd64\DLL2.dll
Package2 C:\Users\USERNAME\.nuget\packages\PACKAGENAME1\1.2.3.4\build\x86\DLL1.dll
Package2 C:\Users\USERNAME\.nuget\packages\PACKAGENAME1\1.2.3.4\build\x86\DLL2.dll
Package2 C:\Users\USERNAME\.nuget\packages\PACKAGENAME2\1.0.0.0\build\DLL1.dll
So NativeLibs are added from the other NuGet package apparently after the third or fourth rebuild.
I think you are using Packages.config nuget package format with non-sdk net framework projects. If so, that could be explained.
For packages.config nuget management format, it imports the target files directly under csproj file. In my side, I created two nuget packages called flat 1.0.0 and faltt 1.0.0 nuget packages.
You can check my csproj file:
If so, the first import targets file flat.targets file is the same as your first nuget package's targets file:
It includes the x86 and amd64 folder files into output folder\x86 and output folder\amd64 folder, that is right as we excepted.
However, under pakckages.config, since the two targets file are in the same csproj file, they can access each other(they have the same life cycle in the same CSProj), when msbuild reads faltt.targets file after flat.targets file, also you did not change NativeLibs item for the second targets file, itself has the file:
C:\Users\xxx\source\repos\flat\packages\flat.1.0.0\build\amd64\Dll1.dll
C:\Users\xxx\source\repos\flat\packages\flat.1.0.0\build\amd64\Dll2.dll
C:\Users\xxx\source\repos\flat\packages\flat.1.0.0\build\x86\Dll1.dll
C:\Users\xxx\source\repos\flat\packages\flat.1.0.0\build\x86\Dll1.dll
When reads the flatt.targets file, it also includes C:\Users\xxx\source\repos\flat\packages\flatt.1.0.0\build\x86\Dll1.dll,
So it has five files under NativeLibs item.
And then it executes <Link>%(FileName)%(Extension)</Link>, the first nuget package's dlls will be executed under the second nuget packages's node, output to the output root folder.
Because of the special nature of Packages.config import targets files, they are interlaced.
You should note that
Under PackageReference nuget management format, the import targets files are stored under obj\xxx.csproj.nuget.g.targets file, like this:
<ImportGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
<Import Project="$(NuGetPackageRoot)flatt\1.0.0\build\flatt.targets" Condition="Exists('$(NuGetPackageRoot)flatt\1.0.0\build\flatt.targets')" />
<Import Project="$(NuGetPackageRoot)flat\1.0.0\build\flat.targets" Condition="Exists('$(NuGetPackageRoot)flat\1.0.0\build\flat.targets')" />
</ImportGroup>
Since the content is not in csproj file and the particularity of the file xxx.csproj.nuget.g.targets, it will read the targets files in each nuget package separately. Each file is a separate life cycle, so it does not Will affect each other. This is also one of the advantages of the latest PackagesReference nuget management format.
Sorry for not telling you at the beginning that I was using packagesReference and I didn't notice packages.config.
So if you want to get the right behavior to separate them, try the two approaches:
1) do not change the two nuget packages, right-click on the packages.config file of the main project which installs the two nuget packages-->click Migrate packages.config to PackageReference option
After that, click Rebuild option to get that.
2) modify the nuget packages--> change the NativeLibs item's name to another for the second nuget package targets file:
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<NativeLibs1 Include="$(MSBuildThisFileDirectory)*.dll" />
<None Include="#(NativeLibs1)">
<Link>%(FileName)%(Extension)</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>
When re-pack your project, uninstall them first, delete all cache files under C:\Users\xxx\.nuget\packages. And then re-install the packages again.

Upload NuGet package, how to set the license?

I'm trying to upload a new NuGet package on the website.
https://www.nuget.org/packages/manage/upload
On the first attemp I received an error that says I need to specify the license.
I don't want but... ok.
So, my first attempt with a specified license failed. I read the linked documentation and I tried multiple times.
I tried with a <license> property and also including a license.txt file in the package.
These are my try with the .nuspec file and the error I receive:
<license type="MIT"/>
=> Error: Unsupported license type 'MIT'.
(I know it is wrong but I found this "example" here: https://github.com/NuGet/Home/wiki/Packaging-License-within-the-nupkg)
<license type="expression">MIT</license>
=> Error: To provide a better experience for older clients when a license expression is specified, must be set to 'https://licenses.nuget.org/MIT'.
<license type="expression">BSD-2-Clause OR MIT</license>
=> Error: To provide a better experience for older clients when a license expression is specified, must be set to 'https://licenses.nuget.org/BSD-2-Clause%20OR%20MIT'.
<license type="expression">https://licenses.nuget.org/MIT</license>
=> Error: Invalid license metadata: The license expression 'https://licenses.nuget.org/MIT' contains invalid characters.
I also tried with my license.txt without success.
What I'm doing exactly is: modify the mylibrary.nuspec file contained in a generated mylibrary.nupkg adding the <license> field.
Why? Because this VS studio project generatse the package using nuget.exe mylibrary.csproj file, and it does not contains the license.
I updated nuget.exe to the latest 5.1 version, but I don't want to modify the VS solution or project.
Any idea ?
Edit the .csproj file. Add the following:
Using License file:
<PropertyGroup>
<PackageLicenseFile>LICENSE.txt</PackageLicenseFile>
</PropertyGroup>
<ItemGroup>
<None Include="licenses\LICENSE.txt" Pack="true"
PackagePath="LICENSE.txt"/>
</ItemGroup>
Using PackageLicenseExpression:
<PropertyGroup>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
</PropertyGroup>
For more details, see
In my case I had to set two properties:
PackageLicenseUrl - https://licenses.nuget.org/MIT
PackageLicenseExpression - MIT

specflow plugin with nunit and TFS build

I am in the middle of trying to get specflow UI tests working with TFS build and I am very very close.
I wanted to be able to run the same test on a number of browsers, and so after some research I found this:
http://www.baseclass.ch/blog/Lists/Beitraege/Post.aspx?ID=4&mobile=0
And it works like a dream for local test runs. the next step was to get this to call a selenium grid during a TFS 2010 build.
After getting the nunit community build task into my build template, I am now stuck with an error I can't seem to resolve. The build reports the following error:
Error: The system cannot find the file specified. Stack Trace: at System.Diagnostics.Process.StartWithCreateProcess(ProcessStartInfo startInfo) at System.Diagnostics.Process.Start() at TfsBuildExtensions.Activities.CodeQuality.NUnit.RunProcess(String fullPath, String workingDirectory, String arguments) in d:\Projects\CodePlex\teambuild2010contrib\CustomActivities\Legacy\VS2010\Source\Activities\CodeQuality\NUnit\NUnit.cs:line 339 at TfsBuildExtensions.Activities.CodeQuality.NUnit.PublishMSTestResults(String resultTrxFile, String collectionUrl, String buildNumber, String teamProject, String platform, String flavor) in d:\Projects\CodePlex\teambuild2010contrib\CustomActivities\Legacy\VS2010\Source\Activities\CodeQuality\NUnit\NUnit.cs:line 394 at TfsBuildExtensions.Activities.CodeQuality.NUnit.PublishTestResultsToTFS(ActivityContext context, String folder) in d:\Projects\CodePlex\teambuild2010contrib\CustomActivities\Legacy\VS2010\Source\Activities\CodeQuality\NUnit\NUnit.cs:line 387 at TfsBuildExtensions.Activities.CodeQuality.NUnit.InternalExecute() in d:\Projects\CodePlex\teambuild2010contrib\CustomActivities\Legacy\VS2010\Source\Activities\CodeQuality\NUnit\NUnit.cs:line 299 at TfsBuildExtensions.Activities.BaseCodeActivity.Execute(CodeActivityContext context) in d:\Projects\CodePlex\teambuild2010contrib\CustomActivities\Legacy\VS2010\Source\Common\BaseCodeActivity.cs:line 67.
however, this seems to be masking a deeper error as to me it simply says that nunit can't load a custom assembly. So I had a look through the binaries folder on the build server for the nunit logs, and sure enough in my test run .xml file I find another error:
System.Configuration.ConfigurationErrorsException : The type 'OpenQA.Selenium.Remote.RemoteWebDriver, Baseclass.Contrib.SpecFlow.Selenium.NUnit.SpecFlowPlugin' could not be found. It may require assembly qualification, e.g. "MyType, MyAssembly".
at Autofac.Configuration.ConfigurationRegistrar.LoadType(String typeName, Assembly defaultAssembly)
at Autofac.Configuration.ConfigurationRegistrar.RegisterConfiguredComponents(ContainerBuilder builder, SectionHandler configurationSection)
at Autofac.Configuration.ConfigurationRegistrar.RegisterConfigurationSection(ContainerBuilder builder, SectionHandler configurationSection)
at Autofac.Configuration.Core.ConfigurationModule.Load(ContainerBuilder builder)
at Autofac.Module.Configure(IComponentRegistry componentRegistry)
at Autofac.ContainerBuilder.Build(IComponentRegistry componentRegistry, Boolean excludeDefaultModules)
at Autofac.ContainerBuilder.Build(ContainerBuildOptions options)
at RegistrationForm.Tests.Acceptance.Features.UserRegistrationFeature.FeatureSetup() in c:\Builds\1\Testing\RegistrationForm - Nightly - Main\Sources\Testing\RegistrationForm\Main\RegistrationForm.Tests.Acceptance\Features\UserRegistration.feature.cs:line 0
From this I started to look at the path to the custom assembly. This is specified in the app.config file for the project (which is transformed during the build. I also checked to ensure the file is being transformed, and in the binaries directory on the build server it is). the relevant section of the file is this.
<specFlow>
<stepAssemblies>
<stepAssembly assembly="SpecFlow.Assist.Dynamic" />
<stepAssembly assembly="Baseclass.Contrib.SpecFlow.Selenium.NUnit.Bindings" />
</stepAssemblies>
<unitTestProvider name="SeleniumNUnit" />
<plugins>
<add name="Baseclass.Contrib.SpecFlow.Selenium.NUnit" path="..\packages\Baseclass.Contrib.SpecFlow.Selenium.NUnit.1.2.0\tools" />
</plugins>
Which works locally. I figured I just need to change the path in the transformed file. At first I assumed the working directory was the binaries directory so in the transform file I have this:
<specFlow>
<plugins xdt:Transform="Replace">
<add name="Baseclass.Contrib.SpecFlow.Selenium.NUnit" path="." />
</plugins>
The dll is in the same dir (binaries) so I tried ".", "", ".\" - none of these worked. So after thinking a little more and reading the errors more carefully I thought I needed to look in the Sources folder on the build server. The Dll is in the packages folder (nuget package restore), and so the path should be the same as I use locally. this didn't work either. So what about a path relative to the ".feature" file throwing the error? this simply needed an extra "..\" - still no luck.
So i am at a bit of a loss, I feel I have tried all the paths i can think of, but my knowledge of specflow plugins and TFS build is letting me down. can anyone give me any pointers?
But the initial error is basically saying the TFSs nunit support cant find a file when it calls CreateProcess, and as you say it works for local test runs, so is it as simple as NUnit isn't installed on the machine that is running the test for you?
I got there in the end!
So somewhere along the lines of setting this up I endded up with something like this in my app.config:
<component
name="IE"
type="Baseclass.Contrib.SpecFlow.Selenium.NUnit.RemoteWebDriver, Baseclass.Contrib.SpecFlow.Selenium.NUnit.SpecFlowPlugin"
service="OpenQA.Selenium.Remote.RemoteWebDriver, WebDriver"
instance-scope="per-dependency">
<parameters>
<parameter name="browser" value="InternetExplorer" />
<parameter name="url" value="http://192.168.1.3:4444/wd/hub" />
</parameters>
</component>
The problem (as it says in the error) is that it could not find OpenQA.Selenium.Remote.RemoteWebDriver. I belive I saw this in one of the examples I found on the bassclass page. After changing it to OpenQA.Selenium.IWebDriver everything worked fine, and nUnit was able to run my specFlow tests on my TFS 2010 build server. I did hit another snag in that when the build tried to parse the results, it tried to use MSTest 11 rather than 10 (prob because I was using VS 2012). However, this TFS machine was just a proof of concept and was a windows 2008 32bit edition running on Virtual PC on windows 7. (I could therfor not run 64bit, and so could not install VS2012 on my build server). I resolved this by creating a new environment variable for MStest 11 and pointing it at MSTest 10. The entire process now works perfectly.

CodeAssassin.ConfigTransform for of arbitrarily named config files

NuGet packages such as CodeAssassin.ConfigTransform tranform web.*.config or app.*.config to web.*.config.transformed or app.*.config.transformed upon a VS build.
However, what if you have config files of form {arbitrary-name}.config ?
For example, MyAssembly.dll.config and its transform rulesets MyAssembly.dll.debug.config & MyAssembly.dll.release.config
CodeAssassin.ConfigTransform does not appear to work for these file patterns.
If you look at the target source code it looks quite simple to modify it to allow any .config file to be transformed. Actually I think that transforming any XML file should be possible.
I will fork that repository tomorrow for and experiment with this.
Disclaimer: In this example I modified CodeAssassin.ConfigTransform.targets directly. But you should create a separate .targets file and reference that in your .csproj.
Add a ConnectionString.config (as an example) and then add the transforms.
Add this to the .targets file (your config name just has to match the regex expression - (?i)^ConnectionString\. in this case):
<Target Name="TransformAllConnectionStringConfigTransformFiles"
Condition="'$(WebProjectOutputDir)'!=''"
BeforeTargets="Compile">
<ItemGroup>
<ConnectionStringConfigTransformFile Include="#(None);#(Content)" Condition="'$([System.Text.RegularExpressions.Regex]::IsMatch(%(Filename),"(?i)^ConnectionString\."))' == true and '%(Extension)'=='.config'" />
</ItemGroup>
<TransformXml Source="ConnectionString.config" Destination="%(ConnectionStringConfigTransformFile.Identity).transformed" Transform="#(ConnectionStringConfigTransformFile)"
Condition="'#(ConnectionStringConfigTransformFile)'!=''" />
<CreateItem Include="%(ConnectionStringConfigTransformFile.Identity).transformed"
AdditionalMetadata="CopyToOutputDirectory=Always">
<Output TaskParameter="Include" ItemName="Content"/>
</CreateItem>
</Target>
Build, and your .transformed files are created.

Script task in NANT now complains about SortedDictionary

I've been trying to upgrade Nant from 0.86-beta1 to 0.92 in my build server, which runs Cruise Control.Net. Now (seemingly since the time I restarted the server), a <script> task fails. Even reverting to the old Nant didn't help. Here's the task:
<script language="C#">
<code>
<![CDATA[
public static void ScriptMain(Project project)
{
System.Collections.Generic.SortedDictionary<string, string> sortedByKey = new System.Collections.Generic.SortedDictionary<string, string>();
foreach(DictionaryEntry de in project.Properties)
{
sortedByKey.Add(de.Key.ToString(), de.Value.ToString());
}
NAnt.Core.Tasks.EchoTask echo = new NAnt.Core.Tasks.EchoTask();
echo.Project = project;
foreach(System.Collections.Generic.KeyValuePair<string, string> kvp in sortedByKey)
{
// Omit the many boring nant.tasks properties
if(kvp.Key.StartsWith("nant.tasks"))
continue;
echo.Message = String.Format("{0}: {1}", kvp.Key, kvp.Value);
echo.Execute();
}
}
]]>
</code>
</script>
This is a script I copied from somewhere to echo the values of all defined properties as a debugging aid. Nant complains that SortedDictionary cannot be found in the namespace System.Collections.Generic, though that's where it should be, both in .Net 2.0 (which Nant 0.86-beta1 uses), and in .Net 4.0 (for Nant 0.92). It's in the assembly System.DLL, so it seems like Nant should certainly be able to find it.
To do the Nant upgrade, I just installed the new verion in a folder next to the prior version, and changed the PATH variable. For some reason, restarting just the CC.Net service was not enough for it to start using the new version, but a restarting the computer did the trick. But then I started getting this problem. Switching the PATH back (and another restart) didn't fix it.
That sounds weird to me.
Did you try to put the reference and namespace part to the parameters of the <script> task? Only a guess...
<script language="C#" >
<references>
<include name="System.dll" />
</references>
<imports>
<import namespace="System.Collections.Generic" />
</imports>
<code>
<!-- ... -->
</code>
</script>
Long shot: is it possible you also have .NET 1.1 installed and it somehow is using that one?
Just to double check, can you replace the faulting script with a script that compiles and have that script just print out the version of the .NET framework used?
Edit actually I found another question in Stackoverflow with a similar problem. Apparently, NAnt won't reference System.dll by default. Have a look at that question for more details.