Run target once for built project - msbuild

I have a msbuild script that compiles the files and then copies them to a different directory. To ensure only the freshly built files are in the destination, I want to delete the destination folder before.
I tried running it before the Build target like this
<Target Name="RemoveRelease64" BeforeTargets="Build" Condition="'$(Platform)' == 'x64' and '$(Configuration)' == 'Release'">
<RemoveDir Directories="$(SolutionDir)bin64" />
</Target>
But this is called for every project that my main project depends on. When starting a build for Project A, it first builds Project B, C and D. For every built project the target is called and the folder deleted. As such it deletes the folder too often.
Is there a way to call a target only once for the project that the build got started for?

Is there a way to call a target only once for the project that the
build got started for?
I think you use Directory.Build.props or Directory.Build.targets as the msbuild script and write the RemoveRelease64 in that file.
If you put the file in the solution folder, as its scope, it will works for the projects from current folder and any sub folders.
So as a suggestion, if you want the msbuild script to only work for project A rather than any referenced projects B,C,D, you should put the msbuild script only in the project folder of A where A.csproj file exists .
Suggestion
1) If you use Directory.Build.props or Directory.Build.targets, delete it at the solution folder or any parent directories, add it only in the folder of Project A where A.csproj file exists.
2) Besides, you could abandon using Directory.Build.props/targets file. Instead, you could rename it as RemoveRelease64.targets and please remember not to use those two names.
Use msbuild import node in A.csproj file and then it will only executes for Project A rather than B,C,D:
Add this in A.csproj file and remove any Directory.Build.props or Directory.Build.targets in solution folders and project folders.
<Import Project="xxx\RemoveRelease64.targets" />

Related

How to restore only a specific solution folder with an MSBuild command

I have more than hundred projects in two solution folders.
How can I build a specific solution folder with the msbuild command in CLI, like the same thing in Visual Studio with right click on a specific solution folder and click on Build?
I use Visual Studio 2019 16.8.3
Update:
I found the Build a solution folder with MSBuild Stack Overflow question, but as you can see in this question, anyone does not provide a built-in solution for restore or build or any other target run in a specific solution folder. I hope to find a way to run the target on a specific solution folder with a built-in way using MSBuild.
Just open Developer Command Prompt for Visual Studio and then type these to build the specific .sln file like this:
msbuild xxx\xxx.sln -t:build -p:Configuration=Debug
The solution folder from Solution Explorer is a virtual folder for the Visual Studio IDE. If you want to use an MSBuild command line, you have to create such a real folder. Otherwise, there isn't any such way.
1) Open Solution Explorer → Switch View, create a real folder (on my side, it is called test), and then drag the related projects into the folder.
2) create a file called test.proj on the solution folder. And then add these on that file:
<Project>
<ItemGroup>
<File Include="test\**\*.csproj"/>
</ItemGroup>
<Target Name="Build">
<MSBuild Projects="#(File)" Targets="Build"/>
</Target>
</Project>
3) use command line msbuild test.proj to get what you want.

Include additional files in build using MSBuild ProjectReference?

I have a class library that has several files that it depends on, and those files must be packaged up for deployment with any project that depends on this library. Right now this means that I must customize each one of those dependent projects to ensure that they copy the files, in addition to adding the library as an MSBuild PrjectReference. When more files are added, all of the projects must be updated.
I've been looking through Microsoft.Common.targets for a way to include these files with the outputs of the library's own project file, so any project that has a ProjectReference to the library will automatically get the files when doing a build. I haven't gotten anything working yet, but I'm curious more generally if this is possible. It seems like it should be, and the _CopyFilesMarkedCopyLocal target even respects an otherwise-unused %(DestinationSubDirectory) metadata item that would allow for customized placement of those files, which would be perfect.
I believe what I'm missing is, for building a project A that depends on project B, the piece that adds the project outputs of project B into the items for project A's build.
EDIT: Leo's comment, I hadn't noticed that files marked with CopyToOutputDirectory are also copied to dependent project output directories because we use ItemGroups with names other than Content, EmbeddedResource, None, etc. Digging deeper, the target that uses those is GetCopyToOutputDirectoryItems and it appears to recursively call the MSBuild task to determine the project outputs, so I should be able to define some custom target that can be imported into our projects that adds our custom ItemGroups in the GetCopyToOutputDirectoryItems target, so that we don't have to use Content/None, etc.
However, the target that does the copying though is _CopyOutOfDateSourceItemsToOutputDirectoryAlways, which doesn't respect %(DestinationSubDirectory) unfortunately, and so all of these files are copied directly to $(OutDir).
My new goal is to see if there's some way to add custom files into the ReferenceCopyLocalPaths ItemGroup of dependent projects so that they are copied instead by the _CopyFilesMarkedCopyLocal target, which does utilize %(DestinationSubDirectory).
Adds custom build action for ItemGroups in the _CopyOutOfDateSourceItemsToOutputDirectoryAlways target
The above title should be closer to bwerks's goal. As test we could to know that the custom build action for ItemGroups would not copied to the Output directory, so we need to our custom build action to the target _CopyOutOfDateSourceItemsToOutputDirectoryAlways.
To accomplish this, open the file Microsoft.Common.CurrentVersion.targets in the MSBuild 15.0 folder C:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\MSBuild\15.0\Bin\Microsoft.Common.CurrentVersion.targets(Make sure you have sufficient permissions and back up it), find the target _CopyOutOfDateSourceItemsToOutputDirectoryAlways:
<Copy
SourceFiles = "#(_SourceItemsToCopyToOutputDirectoryAlways)"
DestinationFiles = "#(_SourceItemsToCopyToOutputDirectoryAlways->'$(OutDir)%(TargetPath)')"
...
>
<Output TaskParameter="DestinationFiles" ItemName="FileWrites"/>
</Copy>
Then we could to know the copy source file is #(_SourceItemsToCopyToOutputDirectoryAlways), search _SourceItemsToCopyToOutputDirectoryAlways this in the targets, you will find:
<ItemGroup>
<_SourceItemsToCopyToOutputDirectoryAlways KeepMetadata="$(_GCTODIKeepMetadata)" Include="#(ContentWithTargetPath->'%(FullPath)')" Condition="'%(ContentWithTargetPath.CopyToOutputDirectory)'=='Always'"/>
<_SourceItemsToCopyToOutputDirectory KeepMetadata="$(_GCTODIKeepMetadata)" Include="#(ContentWithTargetPath->'%(FullPath)')" Condition="'%(ContentWithTargetPath.CopyToOutputDirectory)'=='PreserveNewest'"/>
</ItemGroup>
Could to know the source file is #(ContentWithTargetPath), keeping search the ContentWithTargetPath in the target, finally we got following:
<AssignTargetPath Files="#(Content)" RootFolder="$(MSBuildProjectDirectory)">
<Output TaskParameter="AssignedFiles" ItemName="ContentWithTargetPath" />
</AssignTargetPath>
So, we could to know how the target is to copy the default build action file to the output directory.
Now, go to our custom build action, we just need add our custom build action to the ItemName="ContentWithTargetPath", so add following in the file Microsoft.Common.CurrentVersion.targets:
<AssignTargetPath Files="#(MyBuildAction)" RootFolder="$(MSBuildProjectDirectory)">
<Output TaskParameter="AssignedFiles" ItemName="ContentWithTargetPath" />
</AssignTargetPath>
Save it.
For the file in the project file .csproj:
<ItemGroup>
<MyBuildAction Include="TextFile1.txt">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</MyBuildAction>
</ItemGroup>
Hope this helps.

Binary files copied to a wrong folder on build

Recent libgit2sharp Nuget uses a new Nuget feature that allows you to include a piece of a build script in your NuGet. The purpose it to copy a native dll to a subfolder of the bin folder, like that:
<ItemGroup>
<None Include="$(MSBuildThisFileDirectory)\..\..\lib\net40\NativeBinaries\amd64\git2-e0902fb.dll">
<Link>NativeBinaries\amd64\git2-e0902fb.dll</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>...
Now, it was all nice and beautiful locally, but when I deployed it to AppHarbor, the native dlls appeared in the /bin folder (in addition to the target subfolder), which caused my app to fail.
The problem lies in the _CopyWebApplicationLegacy target, which does not execute locally (it's run only if you have a non-default output dir), thus I don't have this problem on my dev machine. Namely, it executes the following piece of code:
<!-- Copy items that have been marked to be copied to the bin folder -->
<Copy SourceFiles="#(_SourceItemsToCopyToOutputDirectory)"
DestinationFolder="$(WebProjectOutputDir)\bin"
SkipUnchangedFiles="true"
Retries="$(CopyRetryCount)"
RetryDelayMilliseconds="$(CopyRetryDelayMilliseconds)"/>
You can see that the target folder is always /bin -- I believe it's a bug in the Microsoft.WebApplication.targets file (I can't control it on the target machine).
Is there a simple fix, or should I revert to a script in the PostBuild event (which I'll have to update with each new version)?
As mentioned here: https://github.com/libgit2/libgit2sharp/issues/1089#issuecomment-111000722
the way AppHarbor is building your project, it's triggering the old _CopyWebApplicationLegacy target, and that is basically broken. It messes up all files that are using the Copy to Output Directory property by putting them directly into the output directory instead of respecting the relative folder structure. It also doesn't run any web.config transforms you may have.
You can make your project use the newer _CopyWebApplication target instead by adding the following to your project file:
<UseWPP_CopyWebApplication>True</UseWPP_CopyWebApplication>
<PipelineDependsOnBuild>False</PipelineDependsOnBuild>
The thing I'm not sure about is if AppHarbor has some reason why they wouldn't want you to use the newer copy target instead of the old broken one.

Modify files after deployment package is created

I'm in the process of creating a web deployment package through an automatic build trigger on the server.
The package should take care of everything (including creation of a specific website, apppool, and the latest code) on any server desired.
I extracted manually a deploy package from a configured local IIS site, containing all information needed by MsDeploy to create the site, apppool, etc...
They are present in following files
archive.xml
parameters.xml
systeminfo.xml
The idea is now that I would create automatic a deploy package on the build server, that contains the new compiled code, but with the above xml files in the .zip package.
Right now, I'm building the application, after which I execute a PowerShell script that will manually overwrite the files in the .zip with the ones I have.
However, I know you can extend the Target file (with a .wpp.targets file in your project) to plug into the pipeline and modify things along the way.
Unfortunately I'm getting a little lost with the information I found.
I'd like to:
1) configure the creation of the deployment package to use my existing .xml files.
2) if that's not possible, overwrite the files with my own files after the package creation.
My goal is to have a full executable deploy package after the build is finished, so I won't need to PowerShell script anymore.
Any information that will point me closer to a solution or helps me to understand more clearly msbuild targets and/or webdeploy is very appreciated.
I managed doing this by extending the Package MsBuild target.
Adding a .wpp.targets file in the root of the web project with following content.
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="..\packages\MSBuild.ExtensionPack.4.0\MSBuild.ExtensionPack.tasks"/>
<PropertyGroup>
<DeployFilesDirectory>$(MSBuildProjectDirectory)\Deploy\</DeployFilesDirectory>
</PropertyGroup>
<PropertyGroup>
<OnAfterPackageUsingManifest>
$(OnAfterPackageUsingManifest);
CopyDeployFiles;
ReplaceSetParametersFile;
<!--ZipDeploymentFiles;-->
</OnAfterPackageUsingManifest>
</PropertyGroup>
<Target Name="CopyDeployFiles">
<Message Text="Copy Deploy Files"></Message>
<ItemGroup>
<Files Include="$(DeployFilesDirectory)*.xml" Exclude="$(DeployFilesDirectory)setParameters.xml"></Files>
</ItemGroup>
<MSBuild.ExtensionPack.Compression.Zip TaskAction="AddFiles"
CompressFiles="#(Files)"
ZipFileName="$(PackageFileName)"
RemoveRoot="$(DeployFilesDirectory)"/>
</Target>
<Target Name="ReplaceSetParametersFile" DependsOnTargets="GenerateSampleDeployScript">
<Message Text="Replace Default SetParameters File"></Message>
<Copy DestinationFiles="$(GenerateSampleParametersValueLocationDefault)"
SourceFiles="$(DeployFilesDirectory)setParameters.xml"></Copy>
</Target>
</Project>
The first target is executed after MsDeploy has created the package and will replace the .xml files within the .zip file. I'm using the MsBuild.ExtensionPack Zip support.
The second target is executed after the build has created the sample .cmd and setParameters files and will overwrite the setParameters.xml with my own as well.
It takes a while to understand the concepts of MsBuild targets etc, but once you understand it becomes indeed very powerful.
Creating the package is now as simple as just launching the MsBuild
msbuild "D:\Projects\MyWebProject.csproj" /T:Package /P:Configuration=Release;Platform="AnyCPU";PackageLocation="D:\DeployPackage\package.zip";PublishProfile=MyProfile
And deploying is the same as before
package.deploy.cmd /Y –setParamFile :myParameterFile.xml
Assuming your paths stay the same, you can achieve this by specifying the existing zip as your -dest:package=package.zip. MSDeploy will automatically overwrite the files inside the zip.

MSBUILD Configuration: Copy output to staging area

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.