MSBuild + Scope of property - msbuild

I am defining a property in the .csproj file
<PropertyGroup>
<ProjectGuid>{3099AE33-98E7-4018-B0C3-4C3A37A6D56E}</ProjectGuid>
<OutputType>Exe</OutputType>
<!-- Property for CASI Dev Build location -->
<AppRoot>$(INETROOT)\target\distrib\$(BuildType)\$(BuildArchitecture)\CASI_Dev</AppRoot>
<DeploymentBranch>Dev</DeploymentBranch>
I also have a referenced project: say
<ItemGroup>
<ProjectReference Include="$(INETROOT)\refproj.csproj">
<Project>{F73278A5-AB7E-4FFD-8592-F135E7DB06F2}</Project>
<Name>RemoteProj</Name>
</ProjectReference>
Is there some way to access the property $(DeploymentBranch) or $(AppRoot) in the referenced project i.e in file refproj.csproj.
If i try to access them now the value is blank

Based on what I know about MsBuild this is not possible by "standard" way. But here are the options you can achieve it:
1) Pass property values in MSBuild command line for building your root solution:
msbuild mySolution.sln /t:Build /p:Configuration=...;Platform=...;DeploymentBranch=...;AppRoot=...
2) In your refproj.csproj, you can import main.csproj . but keep in mind the "relativeness" of your paths and that main.csproj might override your refproj.csproj properties and targets.
See more information here

Related

Set msbuild OutputPath conditioned on project name

I'm trying to improve a build-server setup for .NET solutions. The build is done using msbuild solution.sln /p:OutputPath="$pwd/build" and an additional Directory.Solution.targets to work around a quirk of msbuild (from related question, see its content below). Now we've decided to move our tests to separate projects so that the test dlls and their dependencies don't land in the OutputPath. Usually this wouldn't be a problem, but for historical reasons every single project in the solution has its OutputPath set to ..\..\. This is why I'm overriding it when calling msbuild. But now I need to only override it for some of the projects - and I can't figure out how.
The project structure right now looks like this.
app/
sources/
Complete.sln
Proj1/
proj1.csproj
Proj1.Test/
proj1.test.csproj
Building the solution from Visual Studio by a developer results in this (only showing new files), which is fine.
app/
proj1.exe
sources/Proj1.Tests/bin/Release/
proj1.exe
proj1.tests.dll
But building it with msbuild Complete.sln /p:OutputPath="$pwd/build" /t:BuildAll results in following.
app/build/
proj1.exe
proj1.tests.dll
Is there a way without changing solution or project files to get the following?
app/build/
proj1.exe
app/tests/
proj1.exe
proj1.tests.dll
I.e. I want to set the OutputPath for every *.Tests.csproj to /tests, and for every other project to /build.
Alternatively, is there a way to call msbuild Complete.sln in such a way it builds either all the projects from that solution with names that don't end with "Tests" or only those that do?
The Directory.Solution.targets mentioned above (using it for reasons) looks like following.
<Project>
<Target Name="SetSkip">
<ItemGroup>
<ProjectReference Update="*">
<SkipNonexistentProjects>Build</SkipNonexistentProjects>
</ProjectReference>
</ItemGroup>
</Target>
<Target Name="BuildAll" DependsOnTargets="SetSkip">
<CallTarget Targets="Build"/>
</Target>
</Project>
UPDATE since the number of files grows, for easier testing I've created a repository.
You could try adding the following to Directory.Build.targets (I have not tested it, but it should work):
<PropertyGroup>
<OutputPath Condition="'$(BuildOutputPath)' != ''
and !$(MSBuildProjectName.EndsWith('.Test'))">$(BuildOutputPath)</OutputPath>
<OutputPath Condition="'$(TestOutputPath)' == ''
and $(MSBuildProjectName.EndsWith('.Test'))">$(TestOutputPath)</OutputPath>
</PropertyGroup>
Then invoke msbuild like so:
msbuild Complete.sln /p:TestOutputPath="$pwd\tests" /p:BuildOutputPath="$pwd\build"
There would be other options, like putting the required build or tests paths directly in the properties.

Where do I set version of Asp.NET Core project? [duplicate]

Since dotnet core moved back to the .csproj format, there is a new autogenerated MyProject.AssemblyInfo.cs which contains, among others:
[assembly: AssemblyCompany("MyProject")]
[assembly: AssemblyVersion("1.0.0.0")]
Note that this is automatically regenerated every build.
Previously, the file was found in the /obj/ directory, now it appears to be only in memory as the file can't be found on disk and clicking the error message does not open any file.
This is the error message:
Since they are defined there, I can't define them myself in the classical AssemblyInfo.cs.
Where/how can I define the Company and Version of a project?
As you've already noticed, you can control most of these settings in .csproj.
If you'd rather keep these in AssemblyInfo.cs, you can turn off auto-generated assembly attributes.
<PropertyGroup>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
</PropertyGroup>
If you want to see what's going on under the hood, checkout Microsoft.NET.GenerateAssemblyInfo.targets inside of Microsoft.NET.Sdk.
Those settings have moved into the .csproj file.
By default, they don't show up but you can discover them from Visual Studio 2017 in the project properties Package tab.
Once saved those values can be found in MyProject.csproj
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net461</TargetFramework>
<Version>1.2.3.4</Version>
<Authors>Author 1</Authors>
<Company>Company XYZ</Company>
<Product>Product 2</Product>
<PackageId>MyApp</PackageId>
<AssemblyVersion>2.0.0.0</AssemblyVersion>
<FileVersion>3.0.0.0</FileVersion>
<NeutralLanguage>en</NeutralLanguage>
<Description>Description here</Description>
<Copyright>Copyright</Copyright>
<PackageLicenseUrl>License URL</PackageLicenseUrl>
<PackageProjectUrl>Project URL</PackageProjectUrl>
<PackageIconUrl>Icon URL</PackageIconUrl>
<RepositoryUrl>Repo URL</RepositoryUrl>
<RepositoryType>Repo type</RepositoryType>
<PackageTags>Tags</PackageTags>
<PackageReleaseNotes>Release</PackageReleaseNotes>
</PropertyGroup>
In the file explorer properties information tab, FileVersion is shown as "File Version" and Version is shown as "Product version"
I do the following for my .NET Standard 2.0 projects.
Create a Directory.Build.props file (e.g. in the root of your repo)
and move the properties to be shared from the .csproj file to this file.
This also enables central management of these shared properties in a multi project solution, allowing for example to set the copyright and/or version numbers only once for all projects.
MSBuild will pick it up automatically and apply them to the autogenerated AssemblyInfo.cs.
They also get applied to the nuget package when building one with dotnet pack or via the UI in Visual Studio 2017.
See https://learn.microsoft.com/en-us/visualstudio/msbuild/customize-your-build
Example:
<Project>
<PropertyGroup>
<Company>Some company</Company>
<Copyright>Copyright © 2020</Copyright>
<AssemblyVersion>1.0.0.1</AssemblyVersion>
<FileVersion>1.0.0.1</FileVersion>
<Version>1.0.0.1</Version>
<!-- ... -->
</PropertyGroup>
</Project>
You can always add your own AssemblyInfo.cs, which comes in handy for InternalsVisibleToAttribute, CLSCompliantAttribute and others that are not automatically generated.
Adding AssemblyInfo.cs to a Project
In Solution Explorer, right click on <project name> > Add > New Folder.
Name the folder "Properties".
Right click on the "Properties" folder, and click Add > New Item....
Select "Class" and name it "AssemblyInfo.cs".
Suppressing Auto-Generated Attributes
If you want to move your attributes back to AssemblyInfo.cs instead of having them auto-generated, you can suppress them in MSBuild as natemcmaster pointed out in his answer.
Adding to NightOwl888's answer, you can go one step further and add an AssemblyInfo class rather than just a plain class:
I want to extend this topic/answers with the following. As someone mentioned, this auto-generated AssemblyInfo can be an obstacle for the external tools. In my case, using FinalBuilder, I had an issue that AssemblyInfo wasn't getting updated by build action. Apparently, FinalBuilder relies on ~proj file to find location of the AssemblyInfo. I thought, it was looking anywhere under project folder. No. So, changing this
<PropertyGroup>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
</PropertyGroup>
did only half the job, it allowed custom assembly info if built by VS IDE/MS Build. But I needed FinalBuilder do it too without manual manipulations to assembly info file. I needed to satisfy all programs, MSBuild/VS and FinalBuilder.
I solved this by adding an entry to the existing ItemGroup
<ItemGroup>
<Compile Remove="Common\**" />
<Content Remove="Common\**" />
<EmbeddedResource Remove="Common\**" />
<None Remove="Common\**" />
<!-- new added item -->
<None Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
Now, having this item, FinalBuilder finds location of AssemblyInfo and modifies the file. While action None allows MSBuild/DevEnv ignore this entry and no longer report an error based on Compile action that usually comes with Assembly Info entry in proj files.
C:\Program Files\dotnet\sdk\2.0.2\Sdks\Microsoft.NET.Sdk\build\Microsoft.NET.Sdk.DefaultItems.targets(263,5):
error : Duplicate 'Compile' items were included. The .NET SDK includes 'Compile' items from your project directory by default.
You can either remove these items from your project file, or set the 'EnableDefaultCompileItems' property to 'false' if you want to explicitly include them in your project file.
For more information, see https://aka.ms/sdkimplicititems. The duplicate items were: 'AssemblyInfo.cs'
Thanks, this helped me a lot.
In my case, building the project Blazor Server Side Website was successful both on Release and Debug, but publishing the website still failed with the Duplicate Attribute error, which confused me a bit.
The solution was to add <GenerateAssemblyInfo>false</GenerateAssemblyInfo> both to the .csproj and .pubxml file:
Path: <Project>/Properties/PublishProfiles/<ProfileName>.pubxml:
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0"
xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
...
<!-- Add the line below -->
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
</PropertyGroup>
</Project>
With .NET 5+, you can use AssemblyMetadata:
<AssemblyMetadata Include="Bar" Value="Baz" />

MSBuild - Determine a solution's _PublishedWebsites

I am writing a web development targets file and would like to programmatically determine the name of the directory that appears beneath "_PublishedWebsites".
I currently have to use this:
$(BinariesRoot)\%(ConfigurationToBuild.FlavorToBuild)\_PublishedWebsites\ MyWebApplication
Any ideas?
(I am not using this for solutions with more than one website to publish)
The new Web Publishing Pipeline (WPP) in .NET 4.0 has a method for controlling the output location.
First, you need to opt-in to WPP during the execution of the CopyWebApplication target. Set the following MSBuild properties, either at command line or in the MSBuild project file:
<PropertyGroup>
<UseWPP_CopyWebApplication>True</UseWPP_CopyWebApplication>
<PipelineDependsOnBuild>False</PipelineDependsOnBuild>
</PropertyGroup>
The command line-variant is:
/p:UseWPP_CopyWebApplication=True /p:PipelineDependsOnBuild=False
Next, create a new MSBuild targets file in the same directory as your project and name it "ProjectName.wpp.targets" where "ProjectName" is the filename of your project, minus the extension. In other words, if you have "MyWebsite.csproj" you need to create "MyWebsite.wpp.targets". I find it helps to add the targets file to the project as well. It's not required, but it makes it easier to edit.
In the new targets file, you will need to override the WebProjectOutputDir property. Only do this when CopyWebApplication will be called - in other words, when the "OutDir" is redirected away from the "OutputPath":
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<WebProjectOutputDir Condition="'$(OutDir)' != '$(OutputPath)'">$(OutDir)Websites\MyCustomFolderName</WebProjectOutputDir>
</PropertyGroup>
</Project>
That's it - you should be good to go. You can test it locally by setting the OutDir property. Don't forget the trailing backslash:
msbuild MyWebsite.csproj /p:OutDir=C:\Development\WebOutputTest\

How to always execute a target in MSBuild

I have an MSBuild file that manipulates the AssemblyInfo file before the application is compiled. At the end of the build, it restores the AssemblyInfo file. It does this by backing up the file, manipulating it, and then after build time, restoring the file.
This works fairly well except when an error occurs during the build. It then does not restore the original file. Is there a way I can tell MSBuild to execute a target at the end of a build no matter if it succeeded or failed?
Based on your last comment to the original question I would take another approach, and forget the approach you are currently taking. You should know that your version info doesn't have to be in the AssemblyInfo.cs file. It can be in any code file, just as long as you only have attributes AssemblyVersion and AssemblyFileVersion defined once each. With that being said what I would do is follow these steps:
Remove AssemblyVersion & AssemblyFileVersion from AssemblyInfo.cs
Create a new file, name it whatever you want want in my case I put it at Properties\VersionInfo.cs. Do not add this file to the project.
Edit the project file to include that file into the list of file to be compiled only when you want it
Let's expand a bit on #3. When you build a .NET project, the project itself is an MSBuild file. Inside that file you will find an item declared Compile. This is the list of files that will be sent to the compiler to be compiled. You can dynamically include/exclude files from that list. In you case you want to include the VersionInfo.cs file only if you are building on the build server (or whatever other condition you define). For this example I defined that condition to be if the project was building in Release mode. So for Release mode VersionInfo.cs would be sent to the compiler, and for other builds not. Here are the contents of VersionInfo.cs
VersionInfo.cs
[assembly: System.Reflection.AssemblyVersion("1.2.3.4")]
[assembly: System.Reflection.AssemblyFileVersion("1.2.3.4")]
In order to hook this into the build process you have to edit the project file. In that file you will find an element (maybe more than 1 depending on project type). You should add a target similar to the following there.
<Target Name="BeforeCompile">
<ItemGroup Condition=" '$(Configuration)'=='Release' ">
<Compile Include="Properties\VersionInfo.cs" />
</ItemGroup>
</Target>
Here what I've done here is to define a target, BeforeCompile, which is a well-known target that you can override. See this MSDN article about other similar targets. Basically this is a target which will always be called before the compiler is invoked. In this target I add the VersionInfo.cs to the Compile item only if the Configuration property is set to release. You could define that property to be whatever you wanted. For instance if you have TFS as your build server then it could be,
<Target Name="BeforeCompile">
<ItemGroup Condition=" '$(TeamFoundationServerUrl)'!='' ">
<Compile Include="Properties\VersionInfo.cs" />
</ItemGroup>
</Target>
Because we know that TeamFoundationServerUrl is only defined when building through TFS.
If you are building form the command line then something like this
<Target Name="BeforeCompile">
<ItemGroup Condition=" '$(IncludeVersionInfo)'=='true' ">
<Compile Include="Properties\VersionInfo.cs" />
</ItemGroup>
</Target>
And when you build the project just do msbuild.exe YourProject.proj /p:IncludeVersion=true. Note: this will not work when building a solution.
What about changing the problem:
Add a "template" AssemblyInfo.cs.template to version control that represents your "ideal" AssemblyInfo.cs with regex hooks in there
Before build, copy the template to the real and apply your regexes
Add some kind of subversion ignore for AssemblyInfo.cs (I'm no svn expert, but I'm pretty sure there is a way you can tell it to ignore certain files)
In the event that your devs need to add some kind of customization that would normally appear in an AssemblyInfo.cs (eg InternalsVisibleTo), then get them to add it to a different .cs file that IS checked in.
As a further refinement, combine Sayed's solution with mine and remove version info stuff from the actual AssemblyInfo.cs and have a VersionInfo.cs.template that is checked in, that creates a VersionInfo.cs in BeforeBuild.
I never used it, but from the documentation it seems that the OnError Element is useful to what you're trying to achieve.
Causes one or more targets to execute,
if the ContinueOnError attribute is
false for a failed task.

How do I import the msbuildcommunitytasks project from another msbuild project with a relative file path?

Please go easy I am new to msbuild and msbuildtasks!
How can I set a property which represents a relative file path to a targets file which I want to import? I need relative references so it will work on all dev machines. But the target for import is trying to use the relative file path internally, which won't work as it is re-evaluated relative to the imported target!
Effectively I am trying to work around the documented behaviour of imported projects:
All relative paths in imported
projects are interpreted relative to
the directory of the imported project.
Therefore, if a project file is
imported into several project files in
different locations, the relative
paths in the imported project file
will be interpreted differently for
each imported project.
There was a similar question at Is it possible to use MSBuild Extension Pack without installation?. That question was how to do the same with the MSBuild Extension Pack, both of which are similar in this aspect. For the Extension Pack you have to declare the property ExtensionTasksPath,and for the Community tasks you have to declare a similar property named MSBuildCommunityTasksLib. So in your case it should look like:
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<MSBuildCommunityTasksLib Condition="'$(MSBuildCommunityTasksLib)' == ''">E:\Data\Development\My Code\Community\MSBuild\CommunityTasks\</MSBuildCommunityTasksLib>
</PropertyGroup>
<Import Project="$(MSBuildCommunityTasksLib)MSBuild.Community.Tasks.Targets"/>
<Target Name="Demo">
<!-- Use the tasks here -->
</Target>
</Project>
Ok, I've found the answer. Essentially you have to set the property MSBuildCommunityTasksPath as a relative path back to the original containing directory.
For example, given a folder structure like this:
Root---project---Build---{My msbuild project}
|
|-Tools---MSBuildCommunityTasks---{Binaries and Targets}
Where :
{My msbuild project} is in Root\Project\Build\
{MSbuildCommunityTasks} is in Root\Project\Tools\MsBuildCommunityTasks
To get the targets project to reference its binaries via the property MSBuildCommunityTasksPath, it will find the tasks file like this:
<PropertyGroup>
<MSBuildCommunityTasksPath>..\MSBuildCommunityTasks\</MSBuildCommunityTasksPath> <!--Relative path back to yourself-->
</PropertyGroup>
Then you can import the targets file with another relative file reference :
<Import Project="..\..\Tools\MSBuildCommunityTasks\MsBuild.Community.Tasks.Targets"/>
#Sayed Ibrahim Hashimi
Talkin about MSBuild4
Just declaring the MSBuildCommunityTasksLib wont suffice cause if u check the MSBuild.Community.Tasks.Targets file the properties are declared as follows
<PropertyGroup>
<MSBuildCommunityTasksPath Condition="'$(MSBuildCommunityTasksPath)' == ''">$(MSBuildExtensionsPath)\MSBuildCommunityTasks</MSBuildCommunityTasksPath>
<MSBuildCommunityTasksLib>$(MSBuildCommunityTasksPath)\MSBuild.Community.Tasks.dll</MSBuildCommunityTasksLib>
</PropertyGroup>
So if U only over ride the MSBuildCommunityTasksLib it will again get over ridden in the MSBuild.Community.Tasks.Targets file as it is not conditional
So u HAVE TO ALSO OVERRIDE MSBuildCommunityTasksPath so that its proerty is NOT SET FROM MSBuildExtensionsPath but from ur custom path.
Correst me if I m wrong
This appears to be one answer:
http://social.msdn.microsoft.com/forums/en-US/msbuild/thread/feb782e3-72ae-4476-9011-617796f217b6
But this (if I understand it correctly) appears to be a ridiculous solution. To get the paths to work I need to change the imported project references? What would happen if I wanted to reference the imported project from third project in another folder?!?
I'm a noob at msbuild if I'm quite honest however I've just solved my own problem I had with this. I was turning one of the targets into its own project and it wasn't finding the paths for the msbuild community paths. If you look at your original project you may find something like this
<PropertyGroup>
<ExtensionTasksPath>./</ExtensionTasksPath>
<MSBuildCommunityTasksPath>./</MSBuildCommunityTasksPath>
</PropertyGroup>
<Import Project="MSBuildExtensionPack\MSBuild.ExtensionPack.tasks"/>
<Import Project="MSBuildCommunityTasks\MSBuild.Community.Tasks.Targets"/>
Copy this code into your new project and it should work.
I just wanted to add, since i cannot comment (rep), that to do a path to your particular project you can use $(SolutionDir) on your property group like so:
$(SolutionDir)\My Code\Community\MSBuild\CommunityTasks\
This way its not tied down to a specific drive and can be based off of the location of the project relative to your solutions directory structure.
Also thanks for the answer above it helped me in my project with the addition above.