0
votes
<?xml version="1.0" encoding="utf-8" ?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <Target Name="MainFrameworkRegenerateFeatures" BeforeTargets="BeforeBuild">
    <Message Importance="high" Text="Regenerating Feature Files" />
    <Exec Command="$(ProjectDir)..\packages\SpecFlow.3.0.1\tools\SpecFlow.exe generateAll $(ProjectPath) /force /verbose"
          Condition="Exists('$(ProjectDir)..\packages\SpecFlow.3.0.1\tools\SpecFlow.exe')" />
  </Target>
  <PropertyGroup>
    <BuildDependsOn>
      $(BuildDependsOn);
      MainFrameworkRegenerateFeatures;
    </BuildDependsOn>
  </PropertyGroup>
</Project>

I have the above targets file, which gets bundled into the build directory of a nuget package. When the package is installed, I can see it makes the appropriate entries in the csproj. Note - the above target file has taken many forms as I try to debug this. I originally had it without the property group, but then put the property group in after hearing that this approach may be a work around for other issues etc.

<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
    <PropertyGroup>
      <ErrorText>This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them.  For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
    </PropertyGroup>
    <Error Condition="!Exists('$(SolutionDir)\.nuget\NuGet.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(SolutionDir)\.nuget\NuGet.targets'))" />
    <Error Condition="!Exists('..\packages\MainFramework.0.15.0\build\MainFramework.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\MainFramework.0.15.0\build\MainFramework.targets'))" />
  </Target>
  <PropertyGroup>
    <PreBuildEvent>
    </PreBuildEvent>
  </PropertyGroup>
  <Import Project="..\packages\MainFramework.0.15.0\build\MainFramework.targets" Condition="Exists('..\packages\MainFramework.0.15.0\build\MainFramework.targets')" />

So this is the default installation from nuget.exe when it installs the package into a csproj.

When I run a build via Visual Studio - the target fires correctly. I see output saying 'Regenerating Feature Files' and then it tells me what feature files are regenerated.

However, when I build via a custom build tool I developed that uses the MSBuild API - it skips the MainFrameworkRegenerateFeatures altogether.

My build tool accomplishes the build via:

                ProjectCollection pc = new ProjectCollection();
                Dictionary<string, string> globalProperty = new Dictionary<string, string>();
                globalProperty.Add("nodeReuse", "false");
                BuildParameters bp = new BuildParameters(pc);
                bp.Loggers = new List<Microsoft.Build.Framework.ILogger>()
                {
                    new FileLogger() {Parameters = @"logfile=buildresult.txt", Verbosity = LoggerVerbosity.Diagnostic}
                };
                BuildRequestData buildRequest = new BuildRequestData(projectFilePath, globalProperty, "12.0",
                    new string[] { "Clean", "Build" }, null);
                BuildResult buildResult = BuildManager.DefaultBuildManager.Build(bp, buildRequest);
                BuildManager.DefaultBuildManager.Dispose();

                pc = null;
                bp = null;
                buildRequest = null;

The build succeeds, but it never executes the Target file - no feature files get regenerated.

I have a highly verbose log file that tells me the following:

Target "EnsureNuGetPackageBuildImports" in file "C:\ETRBuilder\BuildRunner\ETRun_9302082.1742918\CP.SolutionDir\CP.ProjectDir\IE.Project.csproj": Task "Error" skipped, due to false condition; (!Exists('$(SolutionDir).nuget\NuGet.targets')) was evaluated as (!Exists('C:\ETRBuilder\BuildRunner\ETRun_9302082.1742918\CP.SolutionDir\.nuget\NuGet.targets')). Task "Error" skipped, due to false condition; (!Exists('..\packages\MainFramework.0.15.0\build\MainFramework.targets')) was evaluated as (!Exists('..\packages\MainFramework.0.15.0\build\MainFramework.targets')). Done building target "EnsureNuGetPackageBuildImports" in project "IE.Project.csproj".

Why would this work in visual studio, but fail in MSBuild?

Any ideas on how to solve this?

EDIT 1: When running MSBuild.exe against the solution file manually, It does execute the target and regenerates feature files into test classes. It seems that it fails to do so when it is done via the API and only when it is on this particular machine.

GenerateTargetFrameworkMonikerAttribute: Skipping target "GenerateTargetFrameworkMonikerAttribute" because all output files are up-to-date with respect to the input files. MainFrameworkRegenerateFeatures: Regenerating Feature Files
C:\ETRBuilder\BuildRunner\ETRun_9372271.4822016\ClientName\ProjectFolder..\packages\SpecFlow.2.0.1\tools\SpecFlow.exe generateAll C:\ETRBuilder\BuildRunner\ETRun_9372271.4822016\ClientName\ProjectFolder\Client.csproj /force /verbose Processing project: ClientProject etc....
Features\Legacy\SmokeTest\Core\Core.feature -> test updated

I am going to try and take out the MSBuild version attribute from the API call, in case it is causing it.

I tried this as a work around - but when its executed via a Service application, it still skips the targets!! However if I execute it myself manually via a command line - it works fine and regenerates the feature files... WTF is going on??

public static bool BuildWithMSBuild(string solution)
        {
            string command = solution;
            ProcessStartInfo psi = new ProcessStartInfo(PathToMSBuild());
            psi.Arguments = command;
            psi.UseShellExecute = false;
            psi.RedirectStandardOutput = true;
            Process p = new Process();
            p.StartInfo = psi;
            p.Start();
            var v = p.StandardOutput.ReadToEnd();
            Console.Write(v);
            p.WaitForExit(60000);
            File.WriteAllText("buildresult.txt",v);
            if (v.Contains("Build succeeded"))
            {
                return true;
            }
            return false;
        }

Right now, I cannot get this to work when executed via a Windows Service. It always skips the custom target.

I am having to integrate the Regeneration step into my build task via Process.Start on the specflow.exe.

1
$(SolutionDir) property is defined only while build from IDE. Try define it in your build tool. - UserName
$(SolutionDir) is not being used by this target however? What would I define it as - it has to be a dynamic var mapping to the project directory. The message being reported doesnt sound like an error message- it says it is evaluating the statement to false - which means that !Exists == false would be the same as saying 'yes the target file does exist' - Baaleos
Have you check if the MainFramework.targets exits in the packages folder on the build server after you restore the nuget package. And Have you tried build the custom build tool with MSBuild cli on the build server? - Leo Liu-MSFT
I can confirm that the targets file exists on the build server. It seems like something environmental is causing it. The only difference I can think of between me running the tool through visual studio and locally via cmd prompt is that on the build server, it is running as the System account as a Windows service. The weird thing is that the messages in the logging suggests it is finding the targets file - because it is saying !Exists == false. It would only be a negative if it was Exists == false - right? Also - no error text is appearing, suggesting it is not failing to find the targets file - Baaleos
@Baaleos, yes, you are right. If MainFramework.targets not exists on the server, the import will throw an error can not find MainFramework.targets. Have you try to build it with MSBuild cli on the build server directly without TFS/VSTS... If it works fine with MSBuild, it should be work fine with TFS/VSTS..., BTW, what is the version of MSBuild you are using on the build server. - Leo Liu-MSFT

1 Answers

0
votes

Are you also programatically doing a nuget restore too?

Since it looks like this is evaluating to false:

Exists('$(SolutionDir).nuget\NuGet.targets')