I using VS2017 for my solution.
I have a main repository in bitbucket (say directory name is A) and few dependencies in the form of Subtree (this is inside A folder and other subfolder say B).
I want to add a post build in all Subtree (project inside B) such that if the subtree project is present under a folder A (in this case) then copy the dll from B's bin folder to a folder in A. But this script should not run if subtree project in master directory.
So resolve I wanted to find out the parent directory of folder B. If this parent direct is A then only copy dll from B/din/.dll to A/Assembles/bin/.dll
How can I find out of B's parent folder is A in post build script in VS2017
Run post-build event in condition
So, you want to run a PostBuild event only in a specific case. For achieve that, you may use Condition.
In your Condition, you want to check the parent folder of your Solution or Project dir (honestly, I'm not sure what you meant).
MSBuild get the parent of dir
How you can get the parent dir?
<PropertyGroup>
<ProjectParentDir>$([System.IO.Path]::GetDirectoryName($(ProjectDir))))</ProjectParentDir>
<SolutionParentDir>$([System.IO.Path]::GetDirectoryName($(SolutionDir)))</SolutionParentDir>
</PropertyGroup>
Answer
So, now you can combine the above knowledge:
<Target Name="PostBuild" AfterTargets="PostBuildEvent" Condition="$(ProjectParentDir.EndsWith('A'))">
// Do your post build
</Target>
Comment
Because I think that maybe your question is wrong, and maybe you can achieve your solution without PostBuild, I think you may use the tool I described to control the OutputPath itself.
<PropertyGroup>
<ProjectParentDir>$([System.IO.Path]::GetDirectoryName($(ProjectDir))))</ProjectParentDir>
</PropertyGroup>
<PropertyGroup Condition="$(ProjectParentDir.EndsWith('A'))">
<OutputPath>Path/to/somewhere</OutputPath>
</PropertyGroup>
<PropertyGroup Condition="!$(ProjectParentDir.EndsWith('A'))">
<OutputPath>Path/to/somewhere</OutputPath>
</PropertyGroup>
Read More
I'm not sure my syntax is correct, please read more:
Condition
Property-Functions
Related
I have a solution composed with different projects from different path. We use foundation projects from a vanilla folder and then project specific projects from specific directory. Example:
specific project directory: c:\proj\specific
vanilla project directory: c:\proj\vanilla
vanilla project x path: c:\proj\vanilla\repo\src\project\x\code\
In each vanilla project we have a publish profile that points to the root directory and includes a publishsettings.targets file that has the actual target where the project should be published. By using this structure we can have a lot of projects and publish them using a single target so we don't need to change that target in all projects.
We discovered now that we have a problem when using these vanilla projects as the path used in publish profile is relative to vanilla directory and actually we need it to be relative to the specific project directory (solution directory).
In our publish profile we have:
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="..\..\..\..\..\..\publishsettings.targets" />
<PropertyGroup>
...
</PropertyGroup>
</Project>
So we need a way to specify the actual sln directory to this path so we can include the correct target so when we do the publish from visual studio it will publish to the specific project and not vanilla one.
I tried finding a "MSBuildSolutionDirectory" but it only seems to be a "MSBuildProjectDirectory" variable that can be used.
Does anyone knows a way I could get the path
Project="c:\proj\vanilla\publishsettings.targets"
to actually be
Project="c:\proj\specific\publishsettings.targets"
by using some msbuild or custom variable and not hardcoding it?
I need it to work both with vanilla (as I have a vanilla.sln) and also with specific project (as I have a X.sln).
Here is a way to make your own version of the MSBuildSolutionDirectory you were hoping to see built in:
<PropertyGroup>
<SolutionDirectory>$([MSBuild]::GetDirectoryNameOfFileAbove(`$(MSBuildProjectDirectory)`, `YOUR_SOLUTION_NAME.sln`))\</SolutionDirectory>
</PropertyGroup>
Notes on the GetDirectoryNameOfFileAbove MSBuild property function:
From http://blogs.msdn.com/b/visualstudio/archive/2010/04/02/msbuild-property-functions.aspx:
$([MSBuild]::GetDirectoryNameOfFileAbove(directory, filename) Looks
in the designated directory, then progressively in the parent
directories until it finds the file provided or hits the root. Then it
returns the path to that root.
From my own testing:
The returned path does not include a trailing backslash.
If the filename is not found, an empty string is returned.
In order to emulate the "PerProject" option in TFS 2013's XAML build in the new Build 2015 task based builds, I'd like to be able to pass the SolutionName to the msbuild commandline arguments without having to manually set it every time.
I'd like to do something like:
/p:OutputPath=$(Build.BinariesDirectory)\$(SolutionName)\
Where I'd like MsBuild to infer the $(SolutionName) parameter. But when passing this on the commandline, the new task runner will substitute the $(Build.BinariesDirectory) with the correct target path and leaves $(SolutionName) alone. Unfortunately MsBuild subsequently also leaves the property alone:
Copying file from "obj\Debug\TFSBuild.exe" to "bin\debug\$(SolutionName)\TFSBuild.exe".
TFSBuild -> b\$(SolutionName)\TFSBuild.exe
Copying file from "obj\Debug\TFSBuild.pdb" to "b\$(SolutionName)\TFSBuild.pdb".
I can't remember a way to pass a property to the commandline and have it do late-expansion... Any tips?
For those looking to emulate SingleFolder or AsConfigured, those are easy:
SingleFolder -> /p:OutputPath="$(Build.BinariesDirectory)"
Asconfigured -> don't pass OutputPath
PerProject -> /p:OutputPath="$(Build.BinariesDirectory)\HARDCODESOLUTIONNAME"
As I feared there doesn't seem to be a simple way to override a property from the commandline and "inject" the value of another property into it during the evaluation stage.
There are a few ways to get around it, but they're not ideal and certainly not universal for each language supported by MsBuild. A pity.
I've debugged the MsBuild targets files and found a solution to reproduce the old behaviour from the 2005/2008 era. Not entirely per solution, but it does redirect projects into a subfolder.
/p:GenerateProjectSpecificOutputFolder=true /p:OutDirWasSpecified=true
/p:OutputPath=$(Build.BinariesDirectory)
Normally, $(SolutionName) is defined when executing solution-level MSBuild pipelines, such as running dotnet restore in the root solution directory.
To make $(SolutionName) available for project-level MSBuild pipelines, add a Directory.Build.props file in the root of your solution with the following contents:
<Project>
<PropertyGroup>
<SolutionName Condition="'$(SolutionName)' == ''">
$([System.IO.Path]::GetFileNameWithoutExtension($([System.IO.Directory]::GetFiles("$(MSBuildThisFileDirectory)", "*.sln")[0])))
</SolutionName>
</PropertyGroup>
</Project>
Now $(SolutionName) will be defined even when executing project-level MSBuild pipelines.
This answer works best when there is exactly one solution file in the root of the solution directory. You'll need to massage the above a bit for other project structures.
Of course, you can also be lazy and specify the solution name directly, but this opens up the possibility of refactoring issues (need to remember to update this file if the solution name changes).
<Project>
<PropertyGroup>
<SolutionName Condition="'$(SolutionName)' == ''">
MySolutionName
</SolutionName>
</PropertyGroup>
</Project>
One solution is to mimic such 'late evaluation' yourself by altering OutputPath withing the projectfile. To do without manually changing each single project file you can use the CustomBeforeMicrosoftCSharpTargets extension point. Which is an fancy way of saying it is just a property which when found and pointing to an existing file, will lead that file to be imported somewhere before all the actual build logic. Here's the idea: create a file like paths.targets somewhere - either include it in source control or you can generate it on the fly as part of the build process. Contents:
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="12.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<OutputPath Condition="'$(OutputPathBaseDir)'!=''">$(OutputPathBaseDir)\$(SolutionName)</OutputPath>
</PropertyGroup>
</Project>
So this just overrides OutputPath to some base dir + solutionname. Then if you build the solution like
msbuild my.sln /p:CustomBeforeMicrosoftCSharpTargets=paths.targets;
OutputPathBaseDir=$(Build.BinariesDirectory)
each project will import the paths.targets file and set output property to valueOfBinariesDirectory\my which I think is exactly what you are after.
You are right that TFS vNext build can't recognize $(SolutionName) in OutputPath, as $(SolutionName) doesn't list in the Predefined variables.
As an alternative, we may name the build definition with the solution name, then specify the MSBuild argument to: /p:OutputPath="$(Build.BinariesDirectory)\$(Build.DefinitionName)"in this way, we can get the output under the solution name.
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.
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.
Ok, I am not the best at MSBUILD. Actually, I don't know anything. I need some help configuring my solution file to tell MSbuild to copy the compiled output into a staging area. The solution has about 35 projects. All I need is around 5 of them the to be copied to the staging folder in release mode. Please assume I know nothing...
Thanks
You could create a Target that looks like this:
<Target Name="CopyFiles" DependsOnTargets="YourBuildTargets">
<CreateItem Include="YourSolutionPath\bin\$(Configuration)\*.*">
<Output ItemName="YourProjectOutputFiles" TaskParameter="Include"></Output>
</CreateItem>
<Copy SourceFiles="#(YourProjectOutputFiles)" DestinationFolder="$(DestinationFolder)"></Copy>
</Target>
This will create a target called CopyFiles which depends on the completion of YourBuildTargets (You can put multiple dependencies in there, separated with semi colons). It describes an Item which includes all of the files (*.*) in the project directory. Then it runs the Copy command, and copies the files described by the item to the destination folder. If you have lots of projects all being build by 1 MSBuild script, you would have a CreateItem node for each project to index the files, and a corresponding Copy node to perform the copy.
Or if you just want to do this for each project separately, just put this block inside each .csproj file.
Now just include the CopyFiles target in the list of targets that gets build by your script.