I am using .targets files to include common functionality in .csproj files.
In the target file, I want to check if a property was already set before, and if yes, do not set it again.
I need this because I am using the specific target file in many solutions, and I want to include the custum Property only if it wasn't set before.
The property I am talking about is
<PropertyGroup>
<CodeAnalysisRuleSet>$(SolutionDir)CustomizedAllRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
If this ruleset was specified before Importing the target file, I don't want to include it again in the .targets file.
How do I check in .csproj if the <CodeAnalysisRuleSet>...</CodeAnalysisRuleSet> was set before?
The pattern I've seen most often is to set it conditionally based on comparing it to an empty value:
<PropertyGroup>
<CodeAnalysisRuleSet Condition="'$(CodeAnalysisRuleSet)' == ''">$(SolutionDir)CustomizedAllRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
[Edit: responding to comment with code example]
Here's a longer example that works for me:
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<PropertyValue Condition="$(PropertyValue) == ''">Default value</PropertyValue>
</PropertyGroup>
<Target Name="Build">
<Message Text="$(PropertyValue)" />
</Target>
</Project>
Gives the console output:
Project "D:\temp\test.proj" on node 1 (default targets).
Build:
Default value
Done Building Project "D:\temp\test.proj" (default targets).
Related
We've got a simple common.props file that is basically doing what Directory.Build.props appears to do. It specifies various settings and is then imported to the csproj files that need it.
Here's the basics of the MyProject.common.props file:
<Project>
<PropertyGroup>
// some settings
</PropertyGroup>
<PropertyGroup>
<LangVersion>8.0</LangVersion>
//some more settings...
<MyNewVariable>3.1.22</MyNewVariable>
</PropertyGroup>
</Project>
Here's the basics of the csproj:
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\folderA\MyProject.common.props" />
<PropertyGroup>
<OutputType>Exe</OutputType>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="$(MyNewVariable)" />
</ItemGroup>
</Project>
When I use this approach I get the error:
Project dependency Microsoft.Extensions.Hosting.WindowsServices does
not contain an inclusive lower bound. Include a lower bound in the
dependency version to ensure consistent restore results.
However, if I just input 3.1.22 where the variable is it works fine. This suggests to me that the variable is coming through empty or not being picked up. If I remove the variable altogether and leave the Version blank I get the same error. I also double checked the path to the file by misspelling the import and I get a different error, so I know it can at least find the common.props file.
I have a NuGet package that adds to my C# projects a targets file with that content for the post build event.
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Target Name="ThisIsMyTarget" AfterTargets="Build">
<Exec Command="ThisIsMyCommand.exe"/>
</Target>
</Project>
In less than 1% of the projects, I don't need this command, I need another command to be executed. Is it possible with the targets files to suspend a target from another targets file?
Something like this:
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Target Name="ThisIsMyRareTarget" Overwrite="ThisIsMyTarget" AfterTargets="Build">
<Exec Command="ThisIsMyRareCommand.exe"/>
</Target>
</Project>
I don't want to split my NuGet package only for the 1% of the project.
All the credit goes to #Perry Quian-MSFT. He had right concept.
I have created 2 targets files / NuGet packages.
The standard package using this targets file.
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Target Name="ThisIsMyTarget" AfterTargets="Build" Condition="'$(DontUseStandardCommand)'!='true'">
<Exec Command="ThisIsMyCommand.exe"/>
</Target>
</Project>
The rare case targets file looks like this.
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Target Name="ThisIsMyRareTarget" AfterTargets="Build">
<Exec Command="ThisIsMyRareCommand.exe"/>
</Target>
<PropertyGroup>
<DontUseStandardCommand>true</DontUseStandardCommand>
</PropertyGroup>
</Project>
This is working fine in my setup, also independent of the order of the import targets files in the project file.
Overwrite targets post build event of another targets file
Actually, if you want to use multiple target files into nuget package and then switch between different target files depending on your needs, you cannot get what you want.
And as this document said, when you want to add a target file into a project by nuget, you should make sure the name of the target file is the same as the name of the nuget package so that it will work.
So you can only use one target file named <package_id>.targets and set a condidtion in it to distinguish which project environment to use.
Solution
1) please put these all in your <package_id>.targets file:
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Flag></Flag>
</PropertyGroup>
<Target Name="ThisIsMyTarget" AfterTargets="Build" Condition="'$(Flag)'!='true'">
<Exec Command="ThisIsMyCommand.exe"/>
</Target>
<Target Name="ThisIsMyRareTarget" AfterTargets="Build" Condition="'$(Flag)'=='true'">
<Exec Command="ThisIsMyRareCommand.exe"/>
</Target>
</Project>
2) then you pack your nuget project and when you install your nuget in other projects(less than 1% of the projects), you should define the property Flag to true on the end of the xxx.csproj file like this:
And less than 1% projects can use ThisIsMyRareTarget.exe. In other 99% projects, you should not define Flag property in xxx.csproj and it will automatic capture ThisIsMyCommand.exe.
Consider these two MSBuild files. First build-script.xml:
<?xml version="1.0" encoding="utf-8"?>
<!-- build-script.xml -->
<Project
ToolsVersion="4.0"
DefaultTargets="Build"
xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<OutputName>OutputName</OutputName>
</PropertyGroup>
<Import Project="build-script-to-import.xml"/>
</Project>
and then build-script-to-import.xml:
<?xml version="1.0" encoding="utf-8"?>
<!-- build-script-to-import.xml -->
<Project
ToolsVersion="4.0"
DefaultTargets="Build"
xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<OutputPath>Path\$(OutputName)</OutputPath>
</PropertyGroup>
<Target Name="Build">
<Message Text="$(OutputPath)" />
</Target>
</Project>
These two files represents a very simplified C# project file (build-script.xml) referencing a common targets file (build-script-to-import.xml). That means that it is not an option to change the build-script-to-import.xml file. The problem is that the OutputName is calculated, and I need to set it to this calculated value before I import the build-script-to-import.xml file. Do you know how I can accomplish this?
NOTE 1: The calculated value is in fact an assembly version which I am able to fetch from an assembly file with the GetAssemblyIdentity MSBuild task. The problem as I see it is that I may not call a target before the import happens. I would like to set the value for property OutputName with the help of the GetAssemblyIdentity MSBuild task.
NOTE 2: The build will be triggered by Visual Studio, so I may not use a batch file and send a property to MSBuild as a command line argument.
Move the declaration of $(OutputPath) inside your "Build" target. The only way to use "calculated" properties is to have them evaluated inside a target. Globally declared "static" properties will always be evaluated before any target runs, and presumably you need to run a target to calculate the value of $(OutputName). Just be sure your name calculating target runs before the "Build" target.
If Corresponding targets from imported project allows to modify DependsOnTargets
<Target Name="Build" DependsOnTargets="#(BuildDependsOn)" />
you can modify Item and add your target wich will prepare needed properties.
If it is not possible you can write wrapper to call underlying target/targets (Build/Rebuild/Clean) by yourself (without importing build-script-to-import.xml):
build-script.xml
<Target Name="Build" DependsOn="PrepareProperties">
<MSBuild Projects="build-script-to-import.xml"
Targets="Build"
Properties="OutputName=$(OutputName);$(OtherProperties)"/>
</Target>
Suggestion
Depends on your requirement to dynamically change OutputName there may be mach simpler solution. Just not to do it. It is rather simpler to write your own custom build step to copy from fixed output directory to your destination wich is calculated using your rules.
For example, I prefer practice when I have directory called latest with fixed path and a directory for each version of product. Every one can take latest version from that folder.
I've a MSBuild target in my csproj to copy files and folders of my web application to a target path after build.
<Target Name="PublishToFileSystem" DependsOnTargets="PipelinePreDeployCopyAllFilesToOneFolder">
...
If I call MSBuild via command line with the target "PublishToFileSystem" everything works fine.
But now I want to "use" this target also for a special configuration in Visual Studio (like Release, Debug, ...).
How can I assign a configuration to another target than the DefaultTarget "Build" set in the project with DefaultTargets:
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
Thanks, Konrad
Try to use AfterBuild target instead of PublishToFileSystem:
<Target Name="AfterBuild" DependsOnTargets="PipelinePreDeployCopyAllFilesToOneFolder">
or check Overriding Predefined Targets on MSDN
If you want to do this for a specific solution configuration and you're suffering from ciruclar dependencies as I was, the easiest thing I could come up with is writing your own Target to use as default target. That target starts other targets based on a condition on the configuration.
<Target Name="CustomBuild">
<CallTarget Targets="SignAndroidPackage" Condition="'$(Configuration)' == 'UITest'"/>
<CallTarget Targets="Build" Condition="'$(Configuration)' != 'UITest'"/>
</Target>
And then simply change the Default target at the top of the project definition to that CustomBuild target.
<Project DefaultTargets="CustomBuild" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
Here's an approach that might suit your need: run a custom msbuild target from VisualStudio
(this is trick #78 in the book MSBuild Trickery)
I have a bunch of ancillary XML and XSLT files that I want to edit and manage in visual studio.
The files do not logically belong under any code project in my solution and so in order to group them neatly, I have created a "dummy" C# dll project in visual studio and disabled it from building in Debug / release builds).
I wondered if there was a nicer way of achieving the same result (i.e. having all the files visible in solution explorer). What I think really want is a visual studio project type of "content only" but such a thing does not exist (or have I not looked hard enough?).
I have toyed with the idea of adding the files as solution items but then they seem harder to manage because creating a new "solution item folder" does not actually create a folder on disk.
Any one have any ideas?
Visual Studio 2015 has a project type called "Shared Project" which is essentially a content only project with no targets. It's listed under Visual C# but it can be used for any files.
A work colleague has come up with a solution.
He has suggested hand editing the project to remove the DefaultTargets from the Project (and delete a load of now unused properties).
MSBuild complains if there are no targets in the project so he has added three empty targets.
The final project looks something like this
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="3.5" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProductVersion>9.0.30729</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{541463A7-7CFA-4F62-B839-6367178B16BD}</ProjectGuid>
</PropertyGroup>
<ItemGroup>
... files ...
</ItemGroup>
<ItemGroup>
... files ...
</ItemGroup>
<Target Name="Build"/>
<Target Name="Rebuild"/>
<Target Name="Clean"/>
</Project>
Admittedly, this solution requires more fiddling that I would have liked but seems to achieve what I was after: namely a project that does not aattempt to produce any build output.
Andy posted a link with a solution that's mostly worked for me; basically delete the following line from the project file:
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
and add the following lines:
<Target Name="Build">
<Copy
SourceFiles="#(Content)"
DestinationFiles="#(Content->'$(OutputPath)%(RelativeDir)%(Filename)%(Extension)')" />
</Target>
<Target Name="Clean">
<Exec Command="rd /s /q $(OutputPath)" Condition="Exists($(OutputPath))" />
</Target>
<Target Name="Rebuild" DependsOnTargets="Clean;Build">
</Target>
I also found that disabling the project Debug property "Enable the Visual Studio hosting process" (for each configuration) prevented the MyProject.vshost.exe file from being generated.
As David I. McIntosh pointed out in a comment on this answer, if your project is part of a solution with multiple projects and any other projects use the same output path as the content-only project, the above Clean target will delete all of the files in the output path, i.e. the build output of other projects, and would thus only be correct if the content-only project is the first project built (among those sharing the same build output path). The following is a safer and friendlier Clean target for this scenario:
<Target Name="Clean">
<Delete Files="#(Content->'$(OutputPath)%(RelativeDir)%(Filename)%(Extension)')"/>
</Target>
Then, try creating a Blank solution. Create Empty project. Have your files in respective folders with in the solution folder. From property window, use the Show all files, include those folders into the project. There is no better solution other then this. I hope.
This answer is just a convenient consolidation of the answers above given by Chris Fewtrell and Kenny Evitt, along with the slight modification in my comments above, and a bit more detail on what the declaration of the content items should/could look like:
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{541463A7-7CFA-4F62-B839-6367178B16BD}</ProjectGuid>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == '64-bit|AnyCPU'">
<PlatformTarget>x64</PlatformTarget>
<OutputPath>..\builds\$(Configuration)\</OutputPath>
<IntermediateOutputPath>..\builds\$(Configuration)\Intermediate\YourProjectName\</IntermediateOutputPath>
</PropertyGroup>
<ItemGroup>
<Content Include="fileInProjectFolder.csv" />
<Content Include="SubDir\fileInSubdir.txt" />
<Content Include="..\actualSourceDirectoryOfFile\app.log.basic.config">
<Link>targetSubdirInOutputDir\app.log.basic.config</Link>
</Content>
<Content Include="..\actualSourceDirectoryOfFile\yetAnotherFile.config">
<Link>yetAnotherFile.config</Link>
</Content>
... more files ...
</ItemGroup>
<Target Name="Build">
<Copy
SourceFiles="#(Content)"
DestinationFiles="#(Content->'$(OutputPath)%(RelativeDir)%(Filename)%(Extension)')" />
</Target>
<Target Name="Clean">
<Delete Files="#(Content->'$(OutputPath)%(RelativeDir)%(Filename)%(Extension)')"/>
</Target>
<Target Name="Rebuild" DependsOnTargets="Clean;Build">
</Target>
</Project>
Note that this always copies all the "content" files to the output directory - the options "Copy If Newer", "Copy Always" and "Do Not Copy", as presented in the visual studio GUI ( appears as, for example, <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> in the .csproj file) are ignored.
In my situation, I needed to have a set of configuration files that would be common to many projects. To simply achieve this, I performed the following steps:
Create a Class Library project named "Configuration"
Delete all *.cs files from Configuration project
Put configuration files in a "Configuration" folder in the Configuration project
Copy configuration files to required projects in the post-build event. In Configuration project's Properties > Build Events > Post-build event:
xcopy "$(TargetDir)Configuration\*" "$(SolutionDir)TARGET_PROJECT\$(OutDir)" /i /v /q /s /y
In the above, replace TARGET_PROJECT with your actual project
This will copy all the files in the Configurations folder to the output directory of the project that needs the configuration files (eg. MyProject/bin/Debug, etc).