Target BeforeBuild doesn't work in csproj - c#

I'm trying to use the target event "BeforeBuild" in .csproj (vs2017), but it's not working. Someone would know what is wrong:
<Project DefaultTargets="BeforeBuild" Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp1.1</TargetFramework>
</PropertyGroup>
<Target Name="BeforeBuild">
<Message Text="Test123"></Message>
</Target>
</Project>
The expected result is a message: Test123 on output.
[]s

BeforeBuild dosen't working in csproj
That because Before/AfterTarget in csproj gets overridden by SDKs target file.
if you're using the new Sdk attribute on the Project element, it's not possible to put a target definition after the default .targets import. This can lead to targets that people put in their project files unexpectedly not running, with no indication why unless you examine the log file and see the message that the target has been overridden.
dsplaisted have filed Microsoft/msbuild#1680 for this issue. As a workaround, you can do the following:
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp1.1</TargetFramework>
<PreBuildEvent />
</PropertyGroup>
<Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk" />
<Import Project="Sdk.targets" Sdk="Microsoft.NET.Sdk" />
<Target Name="BeforeBuild">
<Message Text="Test123"></Message>
</Target>
Or:
<Target Name="test" BeforeTargets="Build">
<Message Text="Test123" />
</Target>

From the official docs:
Warning
Be sure to use different names than the predefined targets listed in
the table in the previous section (for example, we named the custom
build target here CustomAfterBuild, not AfterBuild), since those
predefined targets are overridden by the SDK import which also defines
them. You don't see the import of the target file that overrides those
targets, but it is implicitly added to the end of the project file
when you use the Sdk attribute method of referencing an SDK.

Related

How to append version to assembly output file name in .NET Standard 2.0 Library Project

I am using VS2022 17.2.3.
In a .NET Standard 2.0 Library Project I wish to change the output assembly file name.
In Project -> Properties -> Assembly Name, I see that it is set to $(MSBuildProjectName).
I want to change it to $(MSBuildProjectName)$(AssemblyVersion), but $(AssemblyVersion) is not working.
I want to ask if anyone can point to right thing.
This is due to MSBuild's Property Evaluation Order.
Setting AssemblyVersion in the csproj file before setting AssemblyName works fine:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<AssemblyVersion>1.0.0.0</AssemblyVersion>
<AssemblyName>$(MSBuildProjectName)$(AssemblyVersion)</AssemblyName>
</PropertyGroup>
</Project>
1>CSharpScratchpad -> C:\Projects\CSharpScratchpad\bin\Debug\net6.0\CSharpScratchpad1.0.0.0.dll
In the comments, you also stated you want to use a wildcard AssemblyVersion, like 1.0.*.
I don't know a good way to access the expanded, final form of the version MSBuild generates internally, so I can only offer a slightly ugly post build copy retrieving the version from the built assembly:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<AssemblyVersion>1.0.*</AssemblyVersion>
<Deterministic>false</Deterministic>
</PropertyGroup>
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
<GetAssemblyIdentity AssemblyFiles="$(TargetPath)">
<Output TaskParameter="Assemblies" ItemName="AssemblyIdentity"/>
</GetAssemblyIdentity>
<Exec Command='COPY "$(TargetPath)" "$(TargetDir)$(TargetName)%(AssemblyIdentity.Version)$(TargetExt)" /Y' />
</Target>
</Project>
In a follow-up comment, you wanted to only append the major.minor version to the file name.
You can create a System.Version instance of the assembly version, and call its ToString(int fieldCount) method so that it only returns the first 2 segments. This would be the new post build target - I've stored the result in a MajorMinor property for sanity readability, but you can bash it all into one line if you prefer:
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
<GetAssemblyIdentity AssemblyFiles="$(TargetPath)">
<Output TaskParameter="Assemblies" ItemName="AssemblyIdentity"/>
</GetAssemblyIdentity>
<PropertyGroup>
<MajorMinor>$([System.Version]::new("%(AssemblyIdentity.Version)").ToString(2))</MajorMinor>
</PropertyGroup>
<Exec Command='COPY "$(TargetPath)" "$(TargetDir)$(TargetName)$(MajorMinor)$(TargetExt)" /Y' />
</Target>
I suggest being able to explain this to your collegues or yourself in a few months. And maybe someone else knows a less convoluted solution for this.

How to nest a Target within another Target in the .csproj file?

Long story short, I need to kill the VBCSCompiler.exe on a Azure pipeline Ubuntu agent after the nuget restore task is completed. On windows2019 agent i dont need to do that but on ubuntu i am running into an issue:
/home/vsts/work/1/s/packages/Microsoft.CodeDom.Providers.DotNetCompilerPlatform.1.0.8/build/net45/Microsoft.CodeDom.Providers.DotNetCompilerPlatform.props(17,5):
warning MSB3021: Unable to copy file "/home/vsts/work/1/s/packages/Microsoft.Net.Compilers.2.4.0/build/../tools/csc.exe" to "/bin/roslyn/csc.exe". Access to the path '/bin/roslyn' is denied. [/home/vsts/work/1/s/Bobby.ProjectA/Bobby.ProjectA.csproj]
so according to Levi in this post here, I need to add a <Target Name="CheckIfShouldKillVBCSCompiler"> lines to the .csproj file. i added them like this:
...
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target> -->
<Target Name="CheckIfShouldKillVBCSCompiler">
<PropertyGroup>
<ShouldKillVBCSCompiler>true</ShouldKillVBCSCompiler>
</PropertyGroup>
</Target>
</Project>
But that didnt do anything to unlock the /bin/roslyn path.
I am thinking that this has to be added in the BeforeBuild target lines (e.g. nest them), so i attempted this:
<Target Name="BeforeBuild">
<Target Name="CheckIfShouldKillVBCSCompiler">
<PropertyGroup>
<ShouldKillVBCSCompiler>true</ShouldKillVBCSCompiler>
</PropertyGroup>
</Target>
</Target>
But i ended up with error:
error MSB4067: The element <PropertyGroup> beneath element <Target> is unrecognized.
Ive learned from this answer that this cannot be accomplished.
The target cannot be nested in another target. Instead, the target can be modified like this:
<Target Name="CheckIfShouldKillVBCSCompiler" BeforeTargets="build">
<PropertyGroup>
<ShouldKillVBCSCompiler>true</ShouldKillVBCSCompiler>
</PropertyGroup>
</Target>
However, this did not help resolve the original build issue i had. What did is a different approach found here

Filter CopyLocalLockFileAssemblies output

I set CopyLocalLockFileAssemblies to true and want to filter the output. So I used the following code:
<Target Name="FilterCopyLocalItems" AfterTargets="ResolveLockFileCopyLocalProjectDeps">
<ItemGroup>
<ReferenceCopyLocalPaths Remove="#(ReferenceCopyLocalPaths)" Condition="'%(Filename)' == 'Microsoft.Extensions.DependencyInjection.Abstractions'" />
</ItemGroup>
</Target>
But this code did not work, how can I put a filter on the output?
Your target FilterCopyLocalItems is to remove the reference dll from the output folder.
I wonder if you means that the target cannot be executed.
For me, I used the below xml code in my net core project which installed the nuget package Microsoft.Extensions.DependencyInjection.Abstractions.
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.1</TargetFramework>
</PropertyGroup>
<PropertyGroup>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
</PropertyGroup>
<Target Name="FilterCopyLocalItems" AfterTargets="ResolveLockFileCopyLocalProjectDeps">
<ItemGroup>
<ReferenceCopyLocalPaths Remove="#(ReferenceCopyLocalPaths)" Condition="'%(Filename)' == 'Microsoft.Extensions.DependencyInjection.Abstractions'" />
</ItemGroup>
</Target>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="3.1.6" />
</ItemGroup>
</Project>
You can find the target under the Detailed output build log.
Enter Tools-->Options-->Projects and Solutions-->Build and Run-->set MSBuild project build output verbosity to Detailed.
And you can see the target by searching its name under the detailed output log while you build it.
It will prevent the Microsoft.Extensions.DependencyInjection.Abstractions.dll being generated in the output folder.
Update 1
Actually, you may do some extra operation which causes the target ResolveLockFileCopyLocalProjectDeps not to be triggered. Due to the lack of your detailed project structure and CSPROJ file, I did not notice that.
For your situation, the target ResolvePackageDependenciesForBuild works well.
So in your side, you should use this:
<Target Name="FilterCopyLocalItems" AfterTargets="ResolvePackageDependenciesForBuild">
<ItemGroup>
<ReferenceCopyLocalPaths Remove="#(ReferenceCopyLocalPaths)" Condition="'%(Filename)' == 'Microsoft.Extensions.DependencyInjection.Abstractions'" />
</ItemGroup>
</Target>
Besides, when you execute the target, please do not add <ExcludeAssets>Runtime</ExcludeAssets> under the PackageReference of your nuget package, its effect is actually the role of your target FilterCopyLocalItems. At runtime, remove the related package.dll in the output folder. See this document.
So you should delete it to avoid reuse.

New csproj re-generates files forever when Visual Studio is open

I'm generating some .cs files using node.exe before the compilation, which I then include in my project.
I call node.exe and define the steps for the generation via CSProj. After migrating the CSProj to the new format <Project Sdk="Microsoft.NET.Sdk"> the files are constantly being generated, erased and generated again while Visual Studio is open (tested with VS2017 and VS2019).
This is my targets file:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
...
</PropertyGroup>
<Target Name="SetVariables">
<ItemGroup>
<_NodeJsExe Include="$(MSBuildThisFileDirectory)..\..\..\Node.js.redist\*\tools\win-x64\node.exe"/>
</ItemGroup>
<PropertyGroup>
<__NodeJsExe>#(_NodeJsExe);</__NodeJsExe>
<NodeJsExe>$(__NodeJsExe.Substring(0, $(__NodeJsExe.IndexOf(';'))))</NodeJsExe>
</PropertyGroup>
</Target>
<Target Name="ProblemHere" BeforeTargets="TransformDuringBuild;PrepareForBuild;CoreCompile" DependsOnTargets="SetVariables;CleanFilesBeforeBuild">
<Exec Command=""$(NodeJsExe)&quot..." />
</Target>
<Target Name="CleanFilesBeforeBuild">
<ItemGroup>
<Ts2LangFiles Include="$(GeneratedFolder)\**\*.cs" />
</ItemGroup>
<Delete Files="#(Ts2LangFiles)"/>
</Target>
</Project>
Anyone knows what the problem is?
Found out that Visual Studio is building some targets at design-time.
If I add this to my custom target, the files stop being constantly generated by MSBuild:
<Target Name="ProblemHere" Condition="$(DesignTimeBuild) != true And $(BuildingProject) == true" BeforeTargets="TransformDuringBuild;PrepareForBuild;CoreCompile" DependsOnTargets="SetVariables;CleanFilesBeforeBuild">
I just have yet to understand if this is a bug in Visual Studio or if the problem is mine and I'm using the targets like I'm not supposed to.

XSLTC.EXE MSBuild Task

I have several XSLTs used in my ASP.NET web application.
I want these files to be compiled to dll whenever I build the project.
Currently, I'm compiling the xslts manually by invoking xsltc.exe from vs2010 tools command prompt.
How can I add msbuild task for xsltc.exe so that it will generate assembly whenevr i build my project?
I'm using .NET 4.0.
That works but doesn't really wrap the tool in a MSBuild friendly way.
I came up with this (which was good enough to get by).
<!-- The Transform File Names... -->
<ItemGroup>
<XsltcTransform Include="Transform1.xslt">
<!-- And the generated .Net Class name. -->
<Class>Transform1Class</Class>
</XsltcTransform>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- Sadly using $(OutDir) MUST come after the Import of CSharp.targets -->
<PropertyGroup>
<XSLTCOutputDll>$(OutDir)xslts.dll</XSLTCOutputDll>
</PropertyGroup>
<Target Name="FindXSLTC">
<PropertyGroup>
<XSLTC>"$(TargetFrameworkSDKToolsDirectory)xsltc.exe"</XSLTC>
</PropertyGroup>
</Target>
<Target Name="XSLTC" Inputs="#(XsltcTransform)" Outputs="$(XSLTCOutputDll)" DependsOnTargets="FindXSLTC">
<Exec Command="$(XSLTC) /out:"$(XSLTCOutputDll)" #(XsltcTransform -> ' /class:%(Class) %(FullPath) ')" />
</Target>
<Target Name="BeforeResolveReferences" DependsOnTargets="XSLTC">
</Target>
These targets will let you compile multiple transforms into one DLL.
Running XSLTC before "BeforeResolveRefereneces" is necessary so that you can have an assembly reference to the generated DLL.
<PropertyGroup>
<WinSDK>C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\Bin</WinSDK>
</PropertyGroup>
<Target Name="Build">
<Exec Command="%22$(WinSDK)\xsltc.exe%22 /out:$(OutputPath)\_PublishedWebsites\xyzapp\bin\Xslts.dll /class:ABC %22$(MSBuildProjectDirectory)\xyzapp\a.xslt%22 /class:DEF %22$(MSBuildProjectDirectory)\xyzapp\b.xslt%22 /class:GHI %22$(MSBuildProjectDirectory)\xyzapp\c.xslt%22"/>
</Target>

Categories