Get Relationship of project files and compile dynamically in TFS Build - visual-studio

I have a solution with multiple projects.
However, this solution to compile correctly, I need to compile a sequence of projects that are in the solution folder.
As I have many projects, in each folder, need to dynamically obtain the relationship of project files, eg. Csproj, to compile.
I've tried creating tasks, creating xml editing csproj file, but I get no result.
<PropertyGroup>
<SrcFolder>$(MSBuildProjectDirectory)\..\..</SrcFolder>
</PropertyGroup>
<ItemGroup>
<PluginProjectsFiles Include="$(SrcFolder)\Folder\SubFolder.*\*.csproj" />
</ItemGroup>
<Target Name="BuildPlugins">
<Message Text="Building Plugins" />
<MSBuild Projects="#(PluginProjectsFiles)" Targets="Clean;Build" Properties="Configuration=Release" />
<Message Text="Plugins Built" />
</Target>

Related

How do I prevent text template generator from running on every VS build?

I have a VS 2019 solution with several projects. There is one project that every other project depends on and I have some T4 templates in that project. The templates are regenerated every time I invoke the Build command (no changes) and therefore all the dependent projects are also rebuilt.
How can I fix this so that the templates are only regenerated when necessary? My project file has the following:
<PropertyGroup>
<TransformOnBuild>true</TransformOnBuild>
<OverwriteReadOnlyOutputFiles>true</OverwriteReadOnlyOutputFiles>
<TransformOutOfDateOnly>true</TransformOutOfDateOnly>
</PropertyGroup>
<Import Project="$(VSToolsPath)\TextTemplating\Microsoft.TextTemplating.targets" />
<ItemGroup>
<None Update="Messages\Messages.tt">
<Generator>TextTemplatingFileGenerator</Generator>
<LastGenOutput>Messages.generated.cs</LastGenOutput>
</None>
<EmbeddedResource Update="Messages\Messages.de.resx" />
<EmbeddedResource Update="Messages\Messages.resx">
<Generator>PublicResXFileCodeGenerator</Generator>
<LastGenOutput>Messages.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>
<Target Name="CallTransformOnBuild" BeforeTargets="CoreCompile">
<CallTarget Targets="TransformDuringBuild" />
</Target>
I only want the transform to run on build if:
Messages.Generated.cs doesn't exist
Messages.tt changed
Messages.resx changed
You need to make sure the following XML Tags' are set to false.
<TransformOnBuild>False</TransformOnBuild>
<OverwriteReadOnlyOutputFiles>False</OverwriteReadOnlyOutputFiles>

How to mark files of a folder as embedded resource with wildcards with the new project format?

My scenario is simple.
I have test project where i want all files within a folder to be marked as embedded resource by default. To prevent someone from doing mistakes here i want this to be automatic through wildcards
I looked at this question, which looked very promising.
MSBuild: Include a custom resource file as embedded resource
However that does not seem to work with the new csproj format. Does anyone know what i should be doing different for it to work with the new format?
My current code is this:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp2.1</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.9.0" />
<PackageReference Include="Shouldly" Version="3.0.2" />
<PackageReference Include="xunit" Version="2.4.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.0" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="3.2.1" />
<PackageReference Include="Microsoft.CodeAnalysis.Workspaces.MSBuild" Version="3.2.1" />
</ItemGroup>
<Target Name="BeforeBuild">
<CreateItem Include="TestContent\*.cs">
<Output ItemName="EmbeddedResource" TaskParameter="Include" />
</CreateItem>
</Target>
</Project>
You can try this script:
<Target Name="MyCustomStep" BeforeTargets="BeforeBuild">
<CreateItem Include="TestContent\*.cs">
<Output ItemName="EmbeddedResource" TaskParameter="Include" />
</CreateItem>
</Target>
There exists difference between the BeforeBuild Target in old and new csproj format. (Or maybe the difference between .net core and .net framewrok, not sure about this point)
Some discoveries when I set the msbuild verbosity to Detailed:
1.For projects that target .net framework using the old csproj format:
The BeforeBuild target will exactly execute the CreateItem Task. So it works for old-format project files.
2.For projects that target .net core using new sdk format:
The BeforeBuild target seems not to execute the task as what we expected.
After defining the Custom target which executes before the BeforeBuild target, it works in my machine:

How can I create a cycle of compiling my project with incrementing of some option?

I wrote a C# template for creating of the .Net extensions for AutoCAD. Before, for each AutoCAD version it is was necessary to point the individual referenses set, the output directory, the target .Net Framework Platform, etc. Exist many versions of AutoCAD: AutoCAD 2009, 2010, ..., 2015. Now my template do it instead of me. My csproj-file has the CAD_Year property:
<PropertyGroup>
<CAD_Year>2013</CAD_Year>
<Min_Year>2009</Min_Year>
<Max_Year>2015</Max_Year>
</PropertyGroup>
When I change CAD_Year value (manually edit this option in the csproj-file) - all settings of my project do change too according target AutoCAD version. It works fine.
But I need to compile my code for all versions of AutoCAD always... It is inconvenient to change the CAD_Year every time for this... :(((
How can I create the cycle of compiling my project for the versions Min_Year, ..., Max_Year when I press the Rebuild Solution menu item?
Thank you, #stijn. I will mark your answer as a solution. Here I create an "answer" for the code highlighting. My current code works:
<!-- Redefine the CoreClean target, otherwise MSBuild will remove all results
of building except for the last. -->
<Target Name="CoreClean">
<ItemGroup>
<AllFiles Include="$(OutputPath)\*.*" />
</ItemGroup>
<Copy SourceFiles="#(AllFiles)" DestinationFolder="$(OutputPath)\temp" />
</Target>
<Target Name="BatchRebuild">
<ItemGroup>
<CADYearsItem Include="$(BuildFor)" />
</ItemGroup>
<Msbuild Projects="$(MsBuildThisFile)" Targets="Rebuild" Properties="CAD_Year_Platform=%(CADYearsItem.Identity)" />
<ItemGroup>
<AllFilesBack Include="$(OutputPath)\temp\*.*" />
</ItemGroup>
<Move SourceFiles="#(AllFilesBack)" DestinationFolder="$(OutputPath)" />
<!-- Doesn't work for Debug. The $(OutputPath)\temp\ will not removed.
But it work for Release.-->
<RemoveDir Directories="$(OutputPath)\temp\" />
</Target>
I see, the RemoveDir task doesn't work for the Debug for me, but it is not a big problem. Now my template is complete, and I will do refactoring of this. Thank you very much!
If you add this to your project file:
<ItemGroup>
<CADYears Include="2013;2014;2015"/>
</ItemGroup>
<Target Name="BatchRebuild">
<Msbuild Projects="$(MsBuildThisFile)" Targets="Rebuild" Properties="CAD_Year=%(CADYears.Identity)"/>
</Target>
and call
msbuild <path_to_projectfile> /t:BatchRebuild
on the commandline, it will build path_to_projectfile 3 times each with a different CAD_Year property.
To get this invoked by VS is trickier since you need to override the Rebuild target, but this for instance works for VS2013 (Actualrebuild target was copied from the Rebuild target in C:\Program Files (x86)\MSBuild\12.0\Bin\Microsoft.Common.CurrentVersion.targets):
<ItemGroup>
<CADYears Include="2013;2014;2015"/>
</ItemGroup>
<Target Name="ActualRebuild"
Condition=" '$(_InvalidConfigurationWarning)' != 'true' "
DependsOnTargets="$(RebuildDependsOn)"
Returns="$(TargetPath)"/>
<Target Name="BatchRebuild">
<Msbuild Projects="$(MsBuildThisFile)" Targets="ActualRebuild" Properties="CAD_Year=%(CADYears.Identity)"/>
</Target>
<Target Name="Rebuild">
<Msbuild Projects="$(MsBuildThisFile)" Targets="BatchRebuild"/>
</Target>
Edit
Since the template system in VS tries to copies ItemGroups it finds in the project root (which seems like a bug to me, or at the least a very annoying feature) you can work around that by using a property and converting it into an item when needed:
<PropertyGroup>
<CADYears>2013;2014;2015<CADYears/>
</PropertyGroup>
<Target Name="BatchRebuild">
<ItemGroup>
<CADYearsItem Include="$(CADYears)"/>
</ItemGroup>
<Msbuild Projects="$(MsBuildThisFile)" Targets="Rebuild" Properties="CAD_Year=%(CADYearsItem .Identity)"/>
</Target>
Note: in the project you posted in the link you are invoking the Rebuild target in the Afterbuild target. I didn't try it, but that will almost certainly lead to infinite recursion. So you should stick to a solution like posted above with a seperate target.

MSBuild projects with different build-configs, without using sln

Related
I have two projects in my VS solution, BookApp.Web and BookApp.Domain.
BookApp.Web references BookApp.Domain.
BookApp.Web has the following build configurations: debug, staging, prod-eu, prod-us and prod-as. We have three datacenters for production and a staging environment.
BookApp.Domain so far only has two build configurations, debug.
When building the solution from within Visual Studio, I can use the build configurator to make sure that no matter what build config is selected for the Web project, the debug config is always used for the Domain project.
However, when building with MSBuild on my continuous integration server, things go wrong. I use this in my rollout.msbuild file:
<MSBuild Projects="BookApp.Web\BookApp.Web.csproj" Properties="Configuration=Prod-us" />
When I run this, MSBuild expects all dependent projects to have the same build configuration. Since that's not the case (and shouldn't be IMO), it fails with this error message:
The OutputPath property is not set for project 'BookApp.Domain.csproj'. Please check to make sure that you have specified a valid combination of Configuration and Platform for this project. Configuration='Prod-us' Platform='AnyCPU'.
An answer to a related question suggests creating separate .sln solutions for each build configuration and running that with MSBuild. To me that doesn't sound like a good idea.
Copying all the build configurations to the Domain project is also not ideal.
Is there a better way of telling MSBuild to use different build configs?
Have a look at this answer it explains how the configurations are passed from project to Project through the MSBuild Task and Using the MetaData of the configuration to pass the desired configuration for the target project
here
UPDATE
I created a Solution with a class library(Sample.Domain) and ConsoleApplication(SampleApp.Console). I added two more configurations to the SamplApp.Console: prod-us;prod-eu, Sample.Domain remained with debug;release.
I then Changed the csproj file of the ConsoleApplication, like so:
ProjectReferences
<!--<ItemGroup>
<ProjectReference Include="..\Sample.Domain\Sample.Domain.csproj">
<Project>{73e8a7fd-0a24-47c5-a527-7601550d4b92}</Project>
<Name>Sample.Domain</Name>
</ProjectReference>
</ItemGroup>-->
<ItemGroup>
<ProjectReference Include="..\Sample.Domain\Sample.Domain.csproj" >
<Targets>Build</Targets>
</ProjectReference>
</ItemGroup>
Added a switch case on the configuration passed to MSBuild, to configure some properties for Outputfiles and reference files:
<Choose>
<When Condition="'$(Configuration)' != 'Debug'">
<PropertyGroup>
<OutputProperty>$(OutputPath)\$(Configuration)</OutputProperty>
<FileCopy>$(OutputProperty)</FileCopy>
</PropertyGroup>
</When>
<Otherwise>
<PropertyGroup>
<OutputProperty>$(OutputPath)</OutputProperty>
<FileCopy>$(OutputProperty)</FileCopy>
</PropertyGroup>
</Otherwise>
</Choose>
Created a Target to switch the Configuration passed to MSBuild, so that Debug will pass Debug to Sample.Domain, everything else it will pass Release
<Target Name="MultiConfiguration" >
<CreateProperty Value="Debug">
<Output TaskParameter="Value" PropertyName="LibConfiguration" Condition="'$(Configuration)' == 'Debug'"/>
</CreateProperty>
<CreateProperty Value="Release">
<Output TaskParameter="Value" PropertyName="LibConfiguration" Condition="'$(Configuration)' != 'Debug' "/>
</CreateProperty>
</Target>
The Build Target uses the Properties we have added so the Output and Copy of references files will have the right values according to Configuration value
<!--Build Process-->
<Target Name="Build" DependsOnTargets="Clean;MultiConfiguration;ComputeProjectReference" >
<Csc Sources="#(Compile)" References="#(NewAssemblies)" TargetType="exe" OutputAssembly="$(OutputProperty)\$(AssemblyName).exe"/>
</Target>
<Target Name="ComputeProjectReference" Inputs="#(ProjectReference)" Outputs="%(ProjectReference.Identity)__Forced">
<MSBuild Projects="#(ProjectReference)" Targets="%(ProjectReference.Targets)" Properties="Configuration=$(LibConfiguration);Platform=AnyCPU;OutputPath=bin\$(LibConfiguration)">
<Output TaskParameter="TargetOutputs" ItemName="ResolvedProjectReferences"/>
</MSBuild>
</Target>
<Target Name="AfterProjectReference" AfterTargets="ComputeProjectReference">
<CreateItem Include="#(ResolvedProjectReferences)">
<Output TaskParameter="Include" ItemName="CopyFiles" />
</CreateItem>
<Copy SourceFiles="#(CopyFiles)" DestinationFolder="$(FileCopy)" SkipUnchangedFiles="false" />
<ItemGroup>
<NewAssemblies Include="$(OutputProperty)\%(CopyFiles.FileName)%(CopyFiles.Extension)" />
</ItemGroup>
</Target>
To call the Debug configuration is done like this
msbuild SampleApp.Console.csproj
To call (Release;prod-us;prod-eu;...) is done like this
msbuild SampleApp.Console.csproj /p:Configuration="prod-us" /p:OutputPath="bin"
I'm sure it can be optimized, and might be some way easier, but it works.

Visual Studio 2010 custom output path for references

I have VS2010 project with several third-party references. Is there any way to automatically output these references to $(OutputPath)\Libraries instead of just $(OutputPath)?
Right now I have a custom AfterBuild target which looks like this,
<Target Name="AfterBuild">
<ItemGroup>
<LibFiles Include="$(SolutionDir)\lib\dotnetzip-1.9\Release\Ionic.Zip.dll" />
<LibFiles Include="$(SolutionDir)\lib\ninject-2.2.0.0\Ninject.dll" />
<LibFiles Include="$(SolutionDir)\lib\nlog-2.0.0.2000\NLog.dll" />
<LibFiles Include="$(SolutionDir)\lib\nlog-2.0.0.2000\NLog.Extended.dll" />
</ItemGroup>
<Copy SourceFiles="#(LibFiles)" DestinationFolder="$(OutputPath)\Libraries" />
</Target>
However this gets tiring since I have to manually add references to #(LibFiles) when adding a reference in VS.
Is there an easier way?
Try to do it this way:
<ItemGroup>
<LibFiles Include="$(SolutionDir)\lib\**\*.dll" />
</ItemGroup>
<Target Name="AfterBuild" Inputs="#(LibFiles)">
<Copy SourceFiles="#(LibFiles)" DestinationFolder="$(OutputPath)\Libraries" />
</Target>
Pros:
you don't have to modify AfterBuild target everytime you add new
reference into your projects
libraries are copied only once or if datetime of any of files in
#(LibFiles) is changed (after update)
Cons:
you will have more dlls in Libraries folder, I guess. But you can filter them using Exclude="$(SolutionDir)\lib\**\Debug\*.dll" for example

Resources