MSBuild ItemGroup Condition Project Reference - c#

I'm trying to include a Project Reference based on a value in the ItemGroup Condition
This is what I have tried. With BeforeTargets Compile it does recognize the project but it won't let me build the project.
<Target Name="ErpSystemToUse" BeforeTargets="Compile">
<ReadLinesFromFile File="$(MSBuildProjectDirectory)\..\..\Presentation\Nop.Web\App_Data\erpSystemToUse.txt">
<Output TaskParameter="Lines" ItemName="ValueTextFile" />
</ReadLinesFromFile>
<Message Text="#(ValueTextFile)" Importance="high" />
<ItemGroup Condition="'#(ValueTextFile)' == 'Twinfield'">
<ProjectReference Include="..\..\Dimerce.Twinfield\Dimerce.Plugin.Misc.Twinfield\Dimerce.Plugin.Misc.Twinfield.csproj" />
</ItemGroup>
</Target>
Is what I am trying to achieve possible?

I have a project where I had to inspect output of System.Reflection.Emit. Decided to include package reference only when appropriate constant is defined
Main difference is that target executes before CollectPackageReferences. Try to change BeforeTargets.
Also use Inputs and Outputs to reduce build time
<Target Name="IncludeIlPackReference"
Inputs="$(DefineConstants)" Outputs="#(PackageReference)"
BeforeTargets="CollectPackageReferences"
Condition="$(DefineConstants.Contains('TRACE_GENERATED_IL'))">
<ItemGroup>
<PackageReference Include="Lokad.ILPack" Version="0.1.6" />
</ItemGroup>
</Target>

Related

Only build one TargetFramework while defining multiple <TargetFrameworks> to support custom builds for other TargetFramework

The idea is simple:
I want to have a single project file, that can build multiple target frameworks and have references to different DLLs based on the framework used.
The issue that I am running into, is that when I select build on the project, it will build all frameworks defined in - but the .DLL referenced is newer than one of those target frameworks (which I need to build the same project with an older framework)
I only want to build a single framework automatically, then build the others manually.
<Project Sdk="Microsoft.NET.Sdk" InitialTargets="Test">
<Target Name="Test">
<Message Importance="high" Text="-- Building $(MSBuildProjectFile), TF=$(TargetFramework), Config=$(Configuration), Version=$(MyCustomVersion) --" />
</Target>
<PropertyGroup>
<TargetFrameworks>net472;net48</TargetFrameworks>
<MyCustomVersion Condition="'$(MyCustomVersion)' == ''">2022</MyCustomVersion>
<OutputPath>bin\$(Configuration)\$(MyCustomVersion)\</OutputPath>
<Configurations>Debug;Release</Configurations>
</PropertyGroup>
<PropertyGroup Condition="'$(MyCustomVersion)' == '2021' ">
<PlatformTarget>x64</PlatformTarget>
<DefineConstants>$(DefineConstants);V2021</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(MyCustomVersion)' == '2021' ">
<PlatformTarget>x64</PlatformTarget>
<DefineConstants>$(DefineConstants);V2021</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(MyCustomVersion)' == '2021' ">
<PlatformTarget>x64</PlatformTarget>
<DefineConstants>$(DefineConstants);V2021</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="'$(MyCustomVersion)' == '2022' ">
<PlatformTarget>x64</PlatformTarget>
<DefineConstants>$(DefineConstants);V2022</DefineConstants>
</PropertyGroup>
<ItemGroup Condition="'$(MyCustomVersion)' == '2022' ">
<Reference Include="Some48API">
<HintPath>path\to\Some48API.dll</HintPath>
<Private>False</Private>
</Reference>
</ItemGroup>
<Target Name="BuildOthers" BeforeTargets="DispatchToInnerBuilds" Condition="'$(MyCustomVersion)' == '2022'">
<Message Importance="high" Text="*** building 2019 ***" />
<MSBuild Projects="$(MSBuildProjectFile)" Properties="Configuration=$(Configuration);TargetFramework=net472;MyCustomVersion=2019" />
<Message Importance="high" Text="*** building 2020 ***" />
<MSBuild Projects="$(MSBuildProjectFile)" Properties="Configuration=$(Configuration);TargetFramework=net472;MyCustomVersion=2020" />
<Message Importance="high" Text="*** building 2021 ***" />
<MSBuild Projects="$(MSBuildProjectFile)" Properties="Configuration=$(Configuration);TargetFramework=net48;MyCustomVersion=2021" />
</Target>
</Project>
In the above project file, I have two TargetFrameworks: net472 and net48.
If I instead replace that with <TargetFramework>net48</TargetFramework> (and modify BeforeTargets="DispatchToInnerBuilds" to BeforeTargets="PreBuildEvent"), I will get a compile error
0>C:\Program Files\dotnet\sdk\6.0.202\Sdks\Microsoft.NET.Sdk\targets\Microsoft.PackageDependencyResolution.targets(267,5): Error NETSDK1005 : Assets file 'C:\Users\me\source\repos\Solution1\ClassLibrary1\obj\project.assets.json' doesn't have a target for 'net472'. Ensure that restore has run and that you have included 'net472' in the TargetFrameworks for your project.
So it seems like I need to provide both but I don't want to build both of them without my own custom control over it...
This issue is inspired by TheBuildingCoder and specifically the RevitAPI dll files are net48 only for 2021/2022 - so I don't want to build net47 as I get compile errors due to broken reference...
I realize that what I was trying to do doesn't make a whole lot of sense...
I want to prevent VS from building the other targets, only to manually build the other targets...
So instead of trying to prevent other targets getting built automatically, I can embrace that instead.
The changed project file looks like this (also more simplified):
<Project Sdk="Microsoft.NET.Sdk" InitialTargets="Test">
<Target Name="Test">
<Message Importance="high" Text="-- Building $(MSBuildProjectFile), TF=$(TargetFramework), Config=$(Configuration), Version=$(MyCustomVersion) --" />
</Target>
<PropertyGroup>
<TargetFrameworks>net472;net48</TargetFrameworks>
<MyCustomVersion Condition="'$(MyCustomVersion)' == '' and '$(TargetFramework)' == 'net472'">2019</MyCustomVersion>
<MyCustomVersion Condition="'$(MyCustomVersion)' == '' and '$(TargetFramework)' == 'net48'">2022</MyCustomVersion>
<OutputPath>bin\$(Configuration)\$(MyCustomVersion)\</OutputPath>
<Configurations>Debug;Release</Configurations>
<IsPublishable>False</IsPublishable>
</PropertyGroup>
<PropertyGroup>
<PlatformTarget>x64</PlatformTarget>
<DefineConstants>$(DefineConstants);V$(MyCustomVersion)</DefineConstants>
</PropertyGroup>
<Target Name="BuildOthers" BeforeTargets="DispatchToInnerBuilds" Condition=" '$(MyCustomVersion)' == '' ">
<MSBuild Projects="$(MSBuildProjectFile)" Properties="Configuration=$(Configuration);TargetFramework=net472;MyCustomVersion=2020" />
<MSBuild Projects="$(MSBuildProjectFile)" Properties="Configuration=$(Configuration);TargetFramework=net48;MyCustomVersion=2021" />
</Target>
</Project>
The only downside is that it doesn't build them in preferable order:
1>-- Building ClassLibrary1.csproj, TF=, Config=Debug, Version= --
1>-- Building ClassLibrary1.csproj, TF=net472, Config=Debug, Version=2020 --
1>ClassLibrary1 -> C:\Users\me\source\repos\Solution1\ClassLibrary1\bin\Debug\2020\net472\ClassLibrary1.dll
1>-- Building ClassLibrary1.csproj, TF=net48, Config=Debug, Version=2021 --
1>ClassLibrary1 -> C:\Users\me\source\repos\Solution1\ClassLibrary1\bin\Debug\2021\net48\ClassLibrary1.dll
1>-- Building ClassLibrary1.csproj, TF=net472, Config=Debug, Version=2019 --
1>ClassLibrary1 -> C:\Users\me\source\repos\Solution1\ClassLibrary1\bin\Debug\2019\net472\ClassLibrary1.dll
1>-- Building ClassLibrary1.csproj, TF=net48, Config=Debug, Version=2022 --
1>ClassLibrary1 -> C:\Users\me\source\repos\Solution1\ClassLibrary1\bin\Debug\2022\net48\ClassLibrary1.dll
2020,2021,2019,2022 but that is a minor inconvenience and can probably be fixed with some shuffling

how to remove auto generated proto files from generated folder

I have the below MSBuild to generate .cs files from my proto files. The build works fine until I do a rebuild where it complains of Source file 'generated-proto-output/Trade.cs# specified multiple times.
How do I delete my .cs files before building/rebuilding everytime?
Error
Severity Code Description Project File Line Suppression State
Warning CS2002 Source file 'generated-proto-output\ErrorTrade.cs' specified multiple times MyComp.Trade.Model C:\dev\workspaces\trade-model-workspace\model\csharp\MyComp.Trade.Model
build snippet in csproj file
<ItemGroup>
<Protobuf Remove="%(RelativePath)generated-proto-output/**/*.cs" />
<Protobuf Include="../../proto/**/*.proto" ProtoRoot="../../proto/" OutputDir="%(RelativePath)generated-proto-output/" GrpcServices="None" />
<Protobuf Update="../../proto/**/*Service.proto" GrpcServices="Both" />
</ItemGroup>
UPDATE - Complete CSProj file (as requested by Lance)
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<PackageId>TradeStore.Model</PackageId>
<ProtoIncludes>.;../../proto</ProtoIncludes>
<OutputType>Library</OutputType>
<TargetFramework>netstandard2.0</TargetFramework>
<Protobuf_NoWarnMissingExpected>true</Protobuf_NoWarnMissingExpected>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Google.Protobuf" Version="3.6.1" />
<PackageReference Include="Grpc" Version="1.19.0" />
<PackageReference Include="Grpc.Tools" Version="1.19.0" PrivateAssets="All" />
</ItemGroup>
<ItemGroup>
<FilesToDelete Include="%(RelativePath)generated-proto-output/*.cs" />
</ItemGroup>
<Target Name="DeleteSpecificFiles" BeforeTargets="Build">
<Message Text="Specific Files: #(FilesToDelete)"/>
<Message Text ="Beginning to delete specific files before build or rebuild..."/>
<Delete Files="#(FilesToDelete)"/>
</Target>
<ItemGroup>
<Protobuf Include="../../proto/**/*.proto" ProtoRoot="../../proto/" OutputDir="%(RelativePath)generated-proto-output/" GrpcServices="None" />
<Protobuf Update="../../proto/**/*Service.proto" GrpcServices="Both" />
</ItemGroup>
</Project>
Try adding CompileOutputs="false" to the directive. This will suppress the warning and won't require you to delete files before building csharp protobuf build integration
How do I delete my .cs files before building/rebuilding everytime?
Try the following script with BeforeTargets below:
<Project...>
...
<ItemGroup>
<FilesToDelete Include="MyPath/*.cs" />
</ItemGroup>
<Target Name="DeleteSpecificFiles" BeforeTargets="build">
<Message Text="Specific Files: #(FilesToDelete)"/>
<Message Text ="Beginning to delete specific files before build or rebuild..."/>
<Delete Files="#(FilesToDelete)"/>
</Target>
</Project>
In addition:
Not seeing the whole content of your .csproj, so I can't figure out why the build snippet you use can't work. But a message task may help output some message whether the engine finds the files by your given path.
In visual studio, if you go Tools=>Options=>Project and Solutions=>Build and Run to change the build out verbosity to Detailed, you will see detailed output message after every build and rebuild.Ctrl+Fand type the Target name you will find the details about delete process:
Hope it makes some help for your trouble-shooting.

TextTransform on MSBuild only when file modified? (or an alternate route of doing this)

I have a project that integrates TextTransform with MSBuild, but I am now at the point that the files are too many and it is slowing down development time.
The process that I have is, I have a POCO.cs; I use Roslyn to Parse the .cs file to gather some metadata; hand the metadata to a .tt file that will then generate a [Implementation].cs
Here is part of the .csproj file that pertains to my question:
<PropertyGroup>
<_CommonProgramFiles>$([System.Environment]::GetEnvironmentVariable('CommonProgramFiles(x86)'))</_CommonProgramFiles>
<_CommonProgramFiles Condition=" '$(_CommonProgramFiles)' == '' ">$(CommonProgramFiles)</_CommonProgramFiles>
<TextTransformPath Condition="'$(TextTransformPath)' == ''">$(_CommonProgramFiles)\Microsoft Shared\TextTemplating\$(VisualStudioVersion)\TextTransform.exe</TextTransformPath>
<!-- Initial default value -->
<_TransformExe>$(TextTransformPath)</_TransformExe>
<_RoslynDllPath>$(ProjectDir)Lib\RoslynWrapper.dll</_RoslynDllPath>
<!-- Cascading probing if file not found -->
<_TransformExe Condition="!Exists('$(_TransformExe)')">$(_CommonProgramFiles)\Microsoft Shared\TextTemplating\10.0\TextTransform.exe"</_TransformExe>
<_TransformExe Condition="!Exists('$(_TransformExe)')">$(_CommonProgramFiles)\Microsoft Shared\TextTemplating\11.0\TextTransform.exe"</_TransformExe>
<_TransformExe Condition="!Exists('$(_TransformExe)')">$(_CommonProgramFiles)\Microsoft Shared\TextTemplating\12.0\TextTransform.exe"</_TransformExe>
<!-- Future proof 'til VS2013+2 -->
<_TransformExe Condition="!Exists('$(_TransformExe)')">$(_CommonProgramFiles)\Microsoft Shared\TextTemplating\13.0\TextTransform.exe"</_TransformExe>
<_TransformExe Condition="!Exists('$(_TransformExe)')">$(_CommonProgramFiles)\Microsoft Shared\TextTemplating\14.0\TextTransform.exe"</_TransformExe>
</PropertyGroup>
<ItemGroup>
<Compile Include="SomePoco.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="SomePocoMetadata.cs">
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
<DependentUpon>SomePocoMetadata.tt</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<None Include="SomePocoMetadata.tt">
<Generator>TextTemplatingFileGenerator</Generator>
<LastGenOutput>SomePocoMetadata.cs</LastGenOutput>
</None>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Target Name="TransformOnBuild" AfterTargets="BeforeBuild">
<Error Text="Failed to find TextTransform.exe tool at '$(_TransformExe)." Condition="!Exists('$(_TransformExe)')" />
<ItemGroup>
<_TextTransform Include="#(None)" Condition="'%(None.Generator)' == 'TextTemplatingFileGenerator'" />
</ItemGroup>
<!-- Perform task batching for each file -->
<Exec Command=""$(_TransformExe)" "%(_TextTransform.FullPath)" -r "$(_RoslynDllPath)"" Condition="'%(_TextTransform.Identity)' != ''" />
</Target>
With the above, the .tt files are always transforming and it takes a couple of minutes to compile the DLL. Is there a way to do this in MSBuild to only transform when the SomePoco.cs file is modified only?
Is there maybe another approach that I should look into to get what i want accomplished?
Make sure you specify Inputs and Outputs attributes for your target.
In your specific case you seem to obtain name of one of the inputs inside the target, so you cannot use it in the enclosing Target's Input attribute. A workaround for you is to move the definition of _TextTransform item group outside of the target.
what i ended up doing was using with ITaskItem[] as one of the input parameters and in there i used C# to figure out the last modified of the files and determined if i needed to generate that way.

MSBuild copy files to directory path with wildcard

I have a DLL that I want to copy to "\Folder1\DestinationDir" and "\Folder2\DestinationDir". I tried using a wild carded destination path:
"\Folder*\DestinationDir",
but I got an error:
No Destination specified for Copy.
Here's my XML:
<ItemGroup>
<ItemToCopy Include="$(OutDir)Mydll.dll" />
</ItemGroup>
<ItemGroup>
<DeployPath Include="$(MSBuildProjectDirectory)\Folder*\DestinationDir" />
</ItemGroup>
<MakeDir Directories="$(DeployPath)" />
<Copy SourceFiles="#(ItemToCopy)" DestinationFolder="%(DeployPath.FullPath)" />
Any help would be much appreciated.
See also
Creating a list of Folders in an ItemGroup using MSBuild
You build file does not work because ItemToCopy does not expand directory paths, it expands files.
So, if you want to enumerate directories, you should target the existing files in those directoris, then get directory list from the file list.
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="3.5" DefaultTargets="Test" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<ItemToCopy Include="$(MSBuildProjectDirectory)\toCopy.txt" />
</ItemGroup>
<ItemGroup>
<DeployPath Include="$(MSBuildProjectDirectory)\Folder*\*.*" />
<DeployFolders Include="#(DeployPath->'%(RootDir)%(Directory)'->Distinct())" />
</ItemGroup>
<Target Name="Test">
<Copy SourceFiles="#(ItemToCopy)" DestinationFolder="%(DeployFolders.FullPath)" />
<Message Text="Destination folder = #(DeployFolders)" />
</Target>
</Project>
Note that this would NOT work for empty directories.
Another thread discusses this problem:
Creating a list of Folders in an ItemGroup using MSBuild
I would recommend to specify a set of folders explicitly.
This can be done with item metadata, for example, and not rely on existing folder structure:
<ItemGroup>
<DeploySpecificFolders Include="$(MSBuildProjectDirectory)\toCopy.txt">
<FolderToCopyTo>Folder1</FolderToCopyTo>
</DeploySpecificFolders>
</ItemGroup>
...
<Message Text="Specific folders = %(DeploySpecificFolders.FullPath) will be copies to %(DeploySpecificFolders.FolderToCopyTo)" />
<Copy SourceFiles="#(DeploySpecificFolders)" DestinationFolder="$(MSBuildProjectDirectory)\%(DeploySpecificFolders.FolderToCopyTo)" />

How can I read a value from appSettings.config in CSPROJ conditional

I would like to read an option out of my appSettings.config file to create a conditional section in my CSPROJ. I know how to do the conditional references with help from visual studio 2010 conditional references but I am not sure how to access the appSettings file from within.
Is this possible and if so, could someone provide some guidance please.
EDIT Following #palo's answer I now have:
<Target Name="BeforeBuild">
<XmlPeek XmlInputPath="SiteSettings.config" Query="appSettings/add[#key='cProjectNumber']/#value">
<Output TaskParameter="Result" ItemName="value" />
</XmlPeek>
<Message Text="TESTING: #(value)" Importance="high" />
</Target>
This works well and prints out the project number i.e Testing: 012. Now, how do I go about using it in some compile includes? I have tried:
<ItemGroup>
<Compile Include="Accounts\#(value)\Controls\MyControl.ascx.cs">
<SubType>ASPXCodeBehind</SubType>
</Compile>
</ItemGroup>
But I get an error saying:
The expression "Accounts\#(value)\Controls\MyControl.ascx.cs" cannot
be used in this context. Item lists cannot be concatenated with other
strings where an item list is expected. Use a semicolon to separate
multiple item lists.
If I understand correctly you need to read elements’ value for appconfig (xml file) and then use its value in your csproj file.
Try to use XmlPeek - http://msdn.microsoft.com/en-us/library/ff598684.aspx ; How to use XmlPeek task?
Keep in mind you the order of evaluation.
You cannot override global properties/items. But with msbuild 4.0 you can override items within BeforeTargets or by AfterTargets
Following #palo's answer I came up with the following (I will mark this as the answer as it details information on how to achieve what I wanted):
<Target Name="BeforeBuild">
<XmlPeek XmlInputPath="SiteSettings.config" Query="appSettings/add[#key='cProjectNumber']/#value">
<Output TaskParameter="Result" ItemName="value" />
</XmlPeek>
<Message Text="TESTING: #(value)" Importance="high" />
<PropertyGroup>
<ProjectNumber>#(value)</ProjectNumber>
</PropertyGroup>
<ItemGroup>
<Compile Include="Accounts\$(ProjectNumber)\Controls\MyControl.ascx.cs">
<SubType>ASPXCodeBehind</SubType>
</Compile>
</ItemGroup>
</Target>
With an XML structure like:
<appSettings>
<add key="cProjectNumber" value="123" />
</appSettings>

Categories

Resources