Incremental build in .NET Core & Visual Studio - asp.net-core

This toy project doesn't have working incremental build:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.2</TargetFramework>
<Timestamp>$([System.DateTime]::Now.ToString("yyyy-MM-dd\THHmmss"))</Timestamp>
</PropertyGroup>
<ItemGroup>
<MyInputs Include="myinput.txt" />
<MyOutput Include="myoutput.txt" />
</ItemGroup>
<Target
Name="test_BeforeTargets_BeforeBuild"
BeforeTargets="BeforeBuild"
Inputs="#(MyInputs)"
Outputs="#(MyOutput)"
>
<Message Text="test_BeforeTargets_BeforeBuild" Importance="high" />
<WriteLinestoFile File="myoutput.txt" Lines="$(Timestamp)" Overwrite="true" />
</Target>
</Project>
There are 3 build outcomes that I've noticed in Visual Studio:
When myinput.txt and the .csproj files are modified I get my desired outcome:
1>Target "test_BeforeTargets_BeforeBuild" in file "C:\Users\dan\source\repos\TestMSBuild\TestMSBuild.csproj":
1> Building target "test_BeforeTargets_BeforeBuild" completely.
1> Input file "myinput.txt" is newer than output file "myoutput.txt".
1> Task "Message"
1> Task Parameter:Text=test_BeforeTargets_BeforeBuild
1> Task Parameter:Importance=high
1> test_BeforeTargets_BeforeBuild
1> Done executing task "Message".
1> Task "WriteLinestoFile"
1> Task Parameter:File=myoutput.txt
1> Task Parameter:Lines=2019-02-21T163909
1> Task Parameter:Overwrite=True
1> Done executing task "WriteLinestoFile".
1>Done building target "test_BeforeTargets_BeforeBuild" in project "TestMSBuild.csproj".
When myinput.txt is not modified, but the .csproj is. I get the expected skipping of the target:
1>Target "test_BeforeTargets_BeforeBuild" in file "C:\Users\dan\source\repos\TestMSBuild\TestMSBuild.csproj":
1> Skipping target "test_BeforeTargets_BeforeBuild" because all output files are up-to-date with respect to the input files.
1> Input files: myinput.txt
1> Output files: myoutput.txt
1>Done building target "test_BeforeTargets_BeforeBuild" in project "TestMSBuild.csproj".
If I build twice without modifying anything, then modify only myinput.txt, I get a one-liner output, no matter how many times I try to build:
========== Build: 0 succeeded, 0 failed, 1 up-to-date, 0 skipped ==========
I expected that when I modify myinput.txt only, the target will not be skipped. Currently, this is only true if I also modify the .csproj file at the same time.
The following variations of BeforeTargets & AfterTargets were tried but to no avail:
BeforeTargets="BeforeBuild" (as above)
BeforeTargets="Build"
AfterTargets="BeforeBuild"
AfterTargets="Build"
I've only tried .NET-Core (Microsoft.NET.Sdk) and ASP.NET-Core (Microsoft.NET.Sdk.Razor & Microsoft.NET.Sdk.Web) projects but both have the same result.
Question:
How can I get this toy project working?

While the MSBuild-based up-to-date check works as expected, there is also an additional up-to-date check performed Visual Studio to determine if it should call MSBuild or not. Since it doesn't know about your input file, it determines that there is no need to run MSBuild on that project.
This can be changed by telling VS about it using the UpToDateCheckInput and UpToDateCheckOutput items that the project system uses for this check:
<ItemGroup>
<UpToDateCheckInput Include="#(MyInputs)" />
<UpToDateCheckOutput Include="#(MyOutputs)" />
</ItemGroup>

Related

MSBuild not finding the DLL

I emit a console app with Mono.Cecil and I want to integrate MSBuild into the build process. But then when I run dotnet build on my custom project file, MSBuild throws an error saying Expected file "obj\Debug\net5.0\refint\test.dll" does not exist. It's trying to find the generated assembly inside the refint folder. When the assembly gets generated inside obj\Debug\net5.0\test.dll as it should. Is there a way I can change the path where MSBuild looks for the output assembly? Everything on the side of the IL generator works, I even get a runnable exe inside the build folder. Here's my project file:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<OutputType>Exe</OutputType>
<DefaultLanguageSourceExtension>.ao</DefaultLanguageSourceExtension>
<OutputPath>C:\Users\filip\source\alto\samples\test\obj\Debug\net5.0\</OutputPath>
</PropertyGroup>
<Target Name="CreateManifestResourceNames" />
<Target Name="CoreCompile" DependsOnTargets="$(CoreCompileDependsOn)">
<Exec Command="dotnet run --project "$(MSBuildThisFileDirectory)\..\..\src\aoc\aoc.csproj" -- #(Compile->'$(MSBuildThisFileDirectory)', ' ') /o "#(IntermediateAssembly)" #(ReferencePath->' /r "%(Identity)"', ' ')"
WorkingDirectory="$(MSBuildProjectDirectory)" />
</Target>
</Project>
Thank you in advance.
I didn't read the whole error message.
C:\Program Files\dotnet\sdk\6.0.100-preview.7.21379.14\Microsoft.Common.CurrentVersion.targets(4527,5): error : Expected file "obj\Debug\net5.0\refint\test.dll" does not exist.
It actually points me to a file where the error was thrown. This is where:
<!-- Copy the reference assembly build product (.dll or .exe). -->
<CopyRefAssembly
SourcePath="#(IntermediateRefAssembly)"
DestinationPath="$(TargetRefPath)"
Condition="'$(ProduceReferenceAssembly)' == 'true' and '$(CopyBuildOutputToOutputDirectory)' == 'true' and '$(SkipCopyBuildProduct)' != 'true'"
>
<Output TaskParameter="DestinationPath" ItemName="ReferenceAssembly"/>
<Output TaskParameter="DestinationPath" ItemName="FileWrites"/>
</CopyRefAssembly>
It's trying to make a reference assembly when I don't need one. So I just set the ProduceReferenceAssembly property to false, since I don't need one.

Referencing macros on msbuild (12.0) command line property assignment

I am curious, is it possible to reference a macro on a command line property assignment for MSBuild?
E.g:
msbuild.exe MySolution.sln /p:CustomBeforeMicrosoftCSharpTargets="$(SolutionDir)\custom.targets"
Would this also work when specified as "MSBuildArguments" from an "Edit Build Definition"/"Queue New Build" from Visual Studio connected to TFS?
E.g:
/p:CustomBeforeMicrosoftCSharpTargets="$(SolutionDir)\custom.targets"
Because it doesn't appear to be importing these targets for me. But the targets file is definitely there, alongside the solution, in the build workspace.
I don't want to have to specify an absolute path. Not sure how working with relative paths is meant to work here, can't find any advice on the internet, and debugging it is quite difficult, as it is called on a build agent using a workflow. The workflow logging is definitely reporting it is calling MSBuild with these arguments, but nowhere in the verbose logging output can I see it is making reference to the CustomBeforeMicrosoftCSharpTargets target, or calling it.
EDIT
I wrote a little test build project buildme.proj to further my understanding.
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<SetMe>NotInTheSandbox</SetMe>
</PropertyGroup>
<PropertyGroup>
<SomeMacroValue>c:\Sandbox\BuildTest</SomeMacroValue>
</PropertyGroup>
<PropertyGroup>
<AlreadySet>$(SomeMacroValue)\InTheSandbox</AlreadySet>
</PropertyGroup>
<Target Name="Build">
<Message Text="I am building!" />
<Message Text="Some macro value: $(SomeMacroValue)" />
<Message Text="$(SetMe)" />
<Message Text="$(AlreadySet)" />
</Target>
</Project>
When I execute with the command:
msbuild buildme.proj /p:SetMe="$(SomeMacroValue)\StillNotInSandbox"
I get the following output:
Microsoft (R) Build Engine version 12.0.31101.0
[Microsoft .NET Framework, version 4.0.30319.42000]
Copyright (C) Microsoft Corporation. All rights reserved.
Build started 10/12/2015 22:12:08.
Project "C:\Sandbox\BuildTest\buildme.proj" on node 1 (default targets).
Build:
I am building!
Some macro value: c:\Sandbox\BuildTest
$(SomeMacroValue)\StillNotInSandbox
c:\Sandbox\BuildTest\InTheSandbox
Done Building Project "C:\Sandbox\BuildTest\buildme.proj" (default targets).
Build succeeded.
0 Warning(s)
0 Error(s)
Time Elapsed 00:00:00.02
So clearly, it is not behaving how I expected: The macro identifier appears in the output message text.
Is there a solution to this?
A "macro" like $(SolutionDir) exists only in VisualStudio and VS passes the value to MSBuild.
Instead MSBuild makes Environment variables available as properties, so a batch file like this
set SomeMacroValue=foo
msbuild buildme.proj /p:SetMe="$(SomeMacroValue)\StillNotInSandbox"
is probably what you are looking for.
And you can set environment variables per-user or per-machine (Control Panel\All Control Panel Items\System Advanced System Settings, Environment variables).

Post-Build task failing in VS 2013

In Visual Studio 2012 I was running the following Post-Build task to compile and combine my LESS files:
$(MSBuildBinPath)\msbuild.exe "$(ProjectDir)MSBuildSettingsLess.xml"
With my MSBuildSettingsLess.xml looking like:
<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/MsBuild/2003">
<Target Name="CompileDotlessCss">
<ItemGroup>
<Binaries Include="*.dll;*.exe"/>
</ItemGroup>
<!-- Compile dotLess CSS into minified full CSS -->
<Exec Command="$(SolutionDir)..\Tools\dotless.compiler.exe $(ProjectDir)..\GMHC\Content\less\responsive.less $(ProjectDir)..\GMHC\Content\bootstrap-responsive.less.css"/>
<Exec Command="$(SolutionDir)..\Tools\dotless.compiler.exe $(ProjectDir)..\GMHC\Content\less\bootstrap.less $(ProjectDir)..\GMHC\Content\bootstrap.less.css"/>
</Target>
</Project>
In Visual Studio 2013, that fails with the following error:
The command "C:\Program Files (x86)\MSBuild\12.0\bin\msbuild.exe "C:\Users\Administrator\Documents\Projects\MedicalMissions\GMHC\MSBuildSettingsLess.xml exited with code 9009.
The output windows shows the following:
1> 'C:\Program' is not recognized as an internal or external command,
1> operable program or batch file.
1> C:\Program Files (x86)\MSBuild\12.0\bin\Microsoft.Common.CurrentVersion.targets(4429,5): error MSB3073: The command "C:\Program Files (x86)\MSBuild\12.0\bin\msbuild.exe "C:\Users\Administrator\Documents\Projects\MedicalMissions\GMHC\MSBuildSettingsLess.xml"
Any idea why it is failing or how to get to the bottom of it?
The answer was actually pretty simple, I just had to surround the call in quotes. So the new Post-Build task is:
"$(MSBuildBinPath)\msbuild.exe" "$(ProjectDir)MSBuildSettingsLess.xml"
Looks like it is backward compatible with VS 2012 as well.
You may need quotes in your msbuild defintion (.proj) file.
Generic example:
<Exec Command=""c:\Folder With Spaces\myfile.exe""/>
The same problem occurred for me but i am using space in folder. The solution is working when i remove the space.
Do:
D:\MyDetailsMuthuvelF\Project_Document\
Don't:
D:\My Details Muthuvel F\Project_Document\

Running XUnit tests with TeamCity using MSBuild

I am trying to get TeamCity to run XUnit tests as part of the build process. So I created a separate file - MyProject.msbuild - living in the same folder as the .sln file, which looks like this:
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<UsingTask AssemblyFile="$(MSBuildProjectDirectory)\..\bin\xunit.net\xunit.runner.msbuild.dll" TaskName="Xunit.Runner.MSBuild.xunit"
/>
<Target Name="Build">
<MSBuild Projects="MyProject.sln" Targets="Build" Properties="Configuration=Release">
<xunit Assembly="MyProject.Utility.Tests\bin\Release\MyProject.Utility.Tests.dll" />
</MSBuild>
</Target>
</Project>
However, no matter what I do, VS2010 hates me having the element inside the element. If I run MSBuild on the file, it tells me a little bit more:
P:\MyProject\src>c:\Windows\Microsoft.NET\Framework64\v4.0.30319\MSBuild.exe MyProject.msbuild /tv:4.0 /v:d
Microsoft (R) Build Engine Version 4.0.30319.1
[Microsoft .NET Framework, Version 4.0.30319.225]
Copyright (C) Microsoft Corporation 2007. All rights reserved.
Build started 08.11.2011 21:08:46.
Project "P:\MyProject\src\MyProject.msbuild" on node 1 (default targets).
Building with tools version "4.0".
P:\MyProject\src\MyProject.msbuild(8,9): error MSB4067: The element <xunit> beneath element <MSBuild> is unrecognized.
Done Building Project "P:\MyProject\src\MyProject.msbuild" (default targets) -- FAILED.
Build FAILED.
"P:\MyProject\src\MyProject.msbuild" (default target) (1) ->
P:\MyProject\src\MyProject.msbuild(8,9): error MSB4067: The element <xunit> beneath element <MSBuild> is unrecognized.
0 Warning(s)
1 Error(s)
Time Elapsed 00:00:00.01
So my current guess is that it doesn't successfully load the xunit.runner.msbuild.dll somehow - or I have done something else strange.
However, I would think that if it couldn't load xunit.runner.msbuild.dll, it would tell me about it. I made sure the file is not blocked (by unpacking the xunit distribution with 7zip).
Any ideas what I can do to get MSBuild to swallow my build file and run the tests?
You don't want to nest the calls, try this:
<Target Name="Build">
<MSBuild Projects="MyProject.sln" Targets="Build" Properties="Configuration=Release">
</MSBuild>
<xunit Assembly="MyProject.Utility.Tests\bin\Release\MyProject.Utility.Tests.dll" />
</Target>
The items within a target are executed in sequence.

How can you conditionally run an MSBuild task only when your project outputs have been built?

I want to run an MSBuild Task (which signs an executable/dll) but only when the output exe/dll has changed. If none of the source files have changed causing a recompile of the exe/dll then I don't want the task to run.
Despite spending several hours trying different things out I cannot work out how to get my target task to only run if the project has been compiled where the output files have changed (in other words the CoreCompile target was not skipped I think).
You can just do this:
<PropertyGroup>
<TargetsTriggeredByCompilation>DoStuffWithNewlyCompiledAssembly</TargetsTriggeredByCompilation>
</PropertyGroup>
This works because someone smart at Microsoft added the following line at the end of the CoreCompile target in Microsoft.[CSharp|VisualBasic][.Core].targets (the file name depends on the language and MSBuild/Visual Studio version).
<CallTarget Targets="$(TargetsTriggeredByCompilation)" Condition="'$(TargetsTriggeredByCompilation)' != ''"/>
So if you specify a target name in the TargetsTriggeredByCompilation property, your target will run if CoreCompile runs-- and your target will not run if CoreCompile is skipped (e.g. because the output assembly is already up-to-date with respect to the code).
Should be the same as this answer, using the TargetOutputs parameter::
<MSBuild Projects="File.sln" >
<Output TaskParameter="TargetOutputs" ItemName="AssembliesBuiltByChildProjects" />
</MSBuild>
<Message Text="Assemblies built: #(AssembliesBuiltByChildProjects)" /> <!-- just for debug -->
<CallTarget Targets="SignExe" Condition="'#(AssembliesBuiltByChildProjects)'!=''" />