6

I am building a project to be installed as a NuGet package and I want to set the properties of a SpecFlow feature file (because it is latest SpecFlow and should not produce code-behind files for the features.)

To achieve the effect of selecting a file, opening it's Properties pane and setting a couple of values, I have set my project structure like this:

\MyProject
  \build
    MyProject.targets
    \Framework <the folder containing the file I want to affect>
      FrameworkTests.feature <the file I want to affect>
  \Framework
    FrameworkTests.feature <the original location of the file I want to affect>

My .nuspec like this:

<?xml version="1.0"?>
<package >
  <metadata minClientVersion="2.5">
    <id>$id$</id>
    <version>$version$</version>
    ...
  </metadata>
  <files>
    <file src="build\MyProject.targets" target="build\MyProject.targets" />
    <file src="build\FrameworkTests\FrameworkTests.feature" target="build\Framework\FrameworkTests.feature" />
  </files>
</package>

My .targets file like this:

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <ItemGroup>
    <None Include="$(MSBuildThisFileDirectory)FrameworkTests\FrameworkTests.feature">
      <Link>FrameworkTests.feature</Link>
      <CopyToOutputDirectory>Copy if newer</CopyToOutputDirectory>
      <CustomToolNamespace></CustomToolNamespace>
    </None>
  </ItemGroup>
</Project>

I am not seeing the FrameworkTests.feature file get copied to the project when the package is installed. What do I need to change?

Matt W
  • 11,753
  • 25
  • 118
  • 215
  • 1
    `I am not seeing the FrameworkTests.feature file get copied to the project` -Do you mean the file FrameworkTests.feature not added to your project, you could not see it in the solution explorer? – Leo Liu Apr 12 '18 at 08:22
  • Yes. I can only see that get added to the solution explorer if I use `target="content\Framework\FrameworkTests.feature"` – Matt W Apr 12 '18 at 08:50

1 Answers1

20

I am not seeing the FrameworkTests.feature file get copied to the project when the package is installed. What do I need to change?

This is the default behavior of the nuget folder conventions. If you set the file in the content folder, nuget will copy those contents to the project root, then you will see them in the solution explorer. If you set files in the build folder, nuget will automatically inserted them into the project file or project.lock.json, like following script in the project file:

<Import Project="..\packages\MyProject.1.0.0\build\MyProject.targets" Condition="Exists('..\packages\MyProject.1.0.0\build\MyProject.targets')" />

So, this is the reason why you could not see the file FrameworkTests.feature in the solution explorer unless you change the folder to content.

You can refer to the document Creating the .nuspec file for more details:

NuGet folder conventions

Besides, if you change the folder to content, the .targets would not work. Because when you change the folder to content, in the .targetsfile, you need to change the path from:

<None Include="$(MSBuildThisFileDirectory)FrameworkTests\FrameworkTests.feature">

To:

<None Include="$(MSBuildThisFileDirectory)..\content\FrameworkTests\FrameworkTests.feature">

But MSBuild could not parse the path ..\content. To resolve this issue, we need to change the .targets file to:

<None Include="$(ProjectDir)FrameworkTests.feature">

Alternatively, you can change property of files with Install.ps1 file, set it into the nuget package.

See this thread for more details.

Update:

I have also applied the changes you've described, but still cannot get the CopyToOutputDirectory file property to be updated.

Found it. There are two points you need update and one point should to know.

First point:

I found following script in your .nuspec:

  <files>
    <file src="build\MyProject.targets" target="build\MyProject.targets" />
    <file src="build\FrameworkTests\FrameworkTests.feature" target="build\Framework\FrameworkTests.feature" />
  </files>

Note: You set your target folder for build\Framework but in your .targets file, you including it with FrameworkTests;

<None Include="$(MSBuildThisFileDirectory)FrameworkTests\FrameworkTests.feature">

So, when change it to content folder, your .nuspec file should be:

  <files>
    <file src="build\MyProject.targets" target="build\MyProject.targets" />
    <file src="content\FrameworkTests\FrameworkTests.feature" target="content\FrameworkTests\FrameworkTests.feature" />
  </files>

And the .targets file should be:

<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <ItemGroup>
    <None Include="$(ProjectDir)FrameworkTests\FrameworkTests.feature">
      <Link>FrameworkTests.feature</Link>
      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
      <CustomToolNamespace></CustomToolNamespace>
    </None>
  </ItemGroup>
</Project>

Second point need update:

The value of CopyToOutputDirectory in the .targets file should be PreserveNewest Not Copy if newer.

The point you need to know:

When you use this method to change the property of the FrameworkTests.feature, the property of this file would not changed in the solution explorer, however, MSBuild will apply this change when you build the project. That because <Import Project="....\MyProject.targets" Condition="Exists('...')" /> will be parsed when you compile the project.

So, you can check the output for CopyToOutputDirectory file property after build the project(After build your project, the file FrameworkTests.feature would be copied to the output folder.)

Update2:

A lot of comments circulating that PowerShell scripts can't be run on build servers because the install command doesn't run them; Only VisualStudio will

Not sure of all the reasons why PowerShell scripts can't be run on build servers, but the vast majority are because PowerShell's default security level.

Try typing this in the PowerShell:

set-executionpolicy remotesigned

In the Visual Studio 2015, you need to install the extension PowerShell Tools for Visual Studio 2015 and the Windows Management Framework 4.0. After install them, the install.ps1 would works fine. Following is my .nuspec file and install.ps1 script.

.nuspec file:

  <files>
    <file src="content\FrameworkTests\FrameworkTests.feature" target="content\FrameworkTests\FrameworkTests.feature" />
    <file src="scripts\Install.ps1" target="tools\Install.ps1" />
  </files>

Note: Do not forget remove the MyProject.targets in the .nuspec file.

install.ps`1 script:

param($installPath, $toolsPath, $package, $project)

function MarkDirectoryAsCopyToOutputRecursive($item)
{
    $item.ProjectItems | ForEach-Object { MarkFileASCopyToOutputDirectory($_) }
}

function MarkFileASCopyToOutputDirectory($item)
{
    Try
    {
        Write-Host Try set $item.Name
        $item.Properties.Item("CopyToOutputDirectory").Value = 2
    }
    Catch
    {
        Write-Host RecurseOn $item.Name
        MarkDirectoryAsCopyToOutputRecursive($item)
    }
}

#Now mark everything in the a directory as "Copy to newer"
MarkDirectoryAsCopyToOutputRecursive($project.ProjectItems.Item("FrameworkTests"))

Then, after install the nuget package, the property of the file FrameworkTests.feature would be changed to copy if newer:

enter image description here

Hope this helps.

jeuxjeux20
  • 391
  • 1
  • 6
  • 20
Leo Liu
  • 71,098
  • 10
  • 114
  • 135
  • I have already tried adding a `content` folder and placing files into it but they did not get copied into the target project when the nuget package was installed. I am packing by providing the name of the .csproj - does that imply a difference? I have also applied the changes you've described, but still cannot get the `CopyToOutputDirectory` file property to be updated. – Matt W Apr 12 '18 at 11:16
  • 1
    @MattW, there should be have something I did not explain clearly, I will update my answer later, and will let you know it. – Leo Liu Apr 12 '18 at 11:55
  • 1
    @MattW, after create a sample with your `.nuspec` and `.targets` files, I found the reason why it not work for you, there are two points you need update and one point should to know. Please check my Update answer for details. And I have test it, it works fine on my side, if it still not work for you, please let me know. – Leo Liu Apr 12 '18 at 13:04
  • Thanks - at a quick glance I see `PreserveNewest` is a change I've already made. Taking a further look... – Matt W Apr 12 '18 at 13:05
  • Ok, so I've applied everything you've said. Thank you for the help. One problem remains... You'll notice the files I'm trying to apply the property changes to are `.feature` files - these are used with SpecFlow/xUnit and when changing those properties in Visual Studio, the SpecFlow plugin is removing the code-behind `.cs` files which back the `.feature` files. As the NuGet is currently, this is not happening and when I build the project (after installing the NuGet package) and try to run the SpecFlow/xUnit tests the build complains because it thinks the auto-generated `.cs` files are there. – Matt W Apr 12 '18 at 13:21
  • Another gotcha I'm seeing is that the `.feature` files are being copied into the target project's `bin\Debug\FrameworkTests\` folder, which means the Test Explorer shows 3 copies of each `.feature` file, rather than the one in the project folder. How can I prevent the `bin\Debug` item being created when the package installs? – Matt W Apr 12 '18 at 13:24
  • 1
    @MattW, Since you are limited by the SpecFlow/xUnit, you could use the another way which I mentioned in the answer. Using Install.ps1 file, in this case, when you complete the installation nuget package, nuget would change the property.https://stackoverflow.com/questions/8474253/nuget-how-can-i-change-property-of-files-with-install-ps1-file – Leo Liu Apr 12 '18 at 13:32
  • A lot of comments circulating that PowerShell scripts can't be run on build servers because the install command doesn't run them; Only VisualStudio will. :( – Matt W Apr 12 '18 at 13:54
  • 1
    @MattW, Not sure of all the reasons why PowerShell scripts can't be run on build servers, but the vast majority are because PowerShell's default security level. You could check my update2 answer for details, it works fine when I install that nuget package in VS2015. – Leo Liu Apr 12 '18 at 16:13
  • Thank you! You are amazing! No words! Truly exceptional - thank you so much! – Matt W Apr 13 '18 at 04:58
  • Hey, I just want to say this helped me a ton. Thank you! – bruestle2 Apr 20 '21 at 18:44