In .nuget BeforeTargets ="BeforeBuild" should call only once for the Solution even though it has 40 projects? - visual-studio-2010

I am looking for a way to run my defined Target only once per build process and not for every project that gets build.
I have defined the following in nuget.targets file
<Message Text="custom clear!!!" Importance="High" />
<ItemGroup>
<_tenant Include="$(PackagesDir)\Tenant.1.1.0\**\*">
</ItemGroup>
<Delete Files="#(_tenant)">
<RemoveDir Directories="$(PackagesDir)\Tenant.1.1.0\>
</Target>```
Should only run once no matter how many projects the msbuild process is building, currently it happens for each project.
It shouldn't matter if I hit (Re-)Build Soltution or (Re-)Build [ProjectName] or hit F5 in Visual Studio, as long any build happens I want to exectue MyTarget only once.

Related

VS 2017 builds code redundantly when switching from msbuild console build over to IDE (the first time)

VS 2017 sets LastActiveSolutionConfig for a web application on its project load, thus triggering subsequent build of that project, because:
The property is set in the respective .csproj.user file, which is created, if needed.
The .csproj.user file is part of project dependencies
So by creating it, VS causes the project to be built the next time.
Imagine building it all on the command line with msbuild after cleaning up the workspace, then switching back to VS and hitting the build button. And it is building again!
So, there are these stupid auto generated CS files related to workflows, that are generated only by VS, not msbuild (TemporaryGeneratedFile_036C0B5B-1481-4323-8D20-8F5ADCB23D92.cs and friends) - our msbuild generates them on purpose to satisfy VS. Check.
Next we ensure all the Copy to Output Directory files use PreserveNewest - check.
I already forgot what else we had to do to make sure VS does not rebuild code redundantly when switching from msbuild to IDE. Now this one, which is new.
How can I prevent VS 2017 from adding this property? Is it absolutely necessary to have it?
In the mean-time, I will modify the .csproj files by adding it and see if it helps. Nobody builds Release locally at our place and it is always AnyCPU platform, so I do not care about other configurations, except Debug|AnyCPU.
This is what I would do:
Set the build verbosity to Diagnostic.
Build and look for where the msbuild file is located that generates this file:
TemporaryGeneratedFile_036C0B5B-1481-4323-8D20-8F5ADCB23D92.cs
Look for some conditions that you can alter to prevent the file from being
generated.
Set some property to alter the condition and prevent that file from being generated.
My solution is to generate the .csproj.user files if needed with the expected property. Which is incredibly annoying that one has to do it. Here is the build code that can go into your Directory.Build.Targets:
<Target Name="EnsureCSProjUserForWebApplications"
Condition="'$(IsWebApplication)' == True And !Exists('$(MSBuildProjectFullPath).user')">
<ItemGroup>
<CSProjUserContent Include="<?xml version="1.0" encoding="utf-8"?>" />
<CSProjUserContent Include="<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">" />
<CSProjUserContent Include="<PropertyGroup>" />
<CSProjUserContent Include="<LastActiveSolutionConfig>Debug|Any CPU</LastActiveSolutionConfig>" />
<CSProjUserContent Include="</PropertyGroup>" />
<CSProjUserContent Include="</Project>" />
</ItemGroup>
<WriteLinesToFile File="$(MSBuildProjectFullPath).user" Lines="#(CSProjUserContent)" ContinueOnError="true" />
</Target>
IsWebApplication is computed like this:
<IsWebApplication>$(ProjectTypeGuids.Contains('349c5851-65df-11da-9384-00065b846f21'))</IsWebApplication>
Finally, the target is part of a larger series of targets that run at the beginning and validate the csproj matches our requirements or ensure certain conditions:
<PropertyGroup>
<EnsureXyzProjectSettingsDependsOn>
AssertIISExpress;
AssertNoAssemblyInfo;
AssertDebugSymbols;
AssertLocalApplicationHostFile;
AssertImportsDFVersioning;
EnsureSharedBinLink;
AssertSharedBinOutputPath;
AssertHintPaths;
EnsureCSProjUserForWebApplications
</EnsureXyzProjectSettingsDependsOn>
</PropertyGroup>
<Target Name="EnsureXyzProjectSettings"
DependsOnTargets="$(EnsureXyzProjectSettingsDependsOn)"
BeforeTargets="BeforeBuild"
Condition="'$(SuppressStrictXyzTargetsChecks)' != true" />

MSBuild missing output files in AfterBuild when solution is cleaned

I'm sure there is something small that I'm missing. Here's the problem:
I have a solution that has multiple projects which after each build will be zipped. Here is an example of the zip creation in one project (they are pretty much identical in others):
<ItemGroup>
<CopySourceFiles Include="$(OutDir)\**\*.*" Exclude="$(OutDir)\**\*.pdb;$(OutDir)\*.mdf;$(OutDir)\*.ldf;$(OutDir)\*.vshost.*" />
</ItemGroup>
...
<Target Name="AfterBuild" Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<MakeDir Directories="$(OutDir)\..\zip_working" />
<!-- first copy the source files specified in the CorySourceFiles ItemGroup above. -->
<Copy SourceFiles="#(CopySourceFiles)" DestinationFiles="#(CopySourceFiles->'$(OutDir)\..\zip_working\%(RecursiveDir)%(Filename)%(Extension)')" />
<!-- Perform the zip by calling the UsingTask. Make sure the DestinationFiles above and the SourceDirectory below are pointing to the same place -->
<Zip SourceDirectory="$(OutDir)\..\zip_working" OutputFilename="$(OutDir)\..\zip\$(ProjectName).zip" />
<!-- Clean up. -->
<RemoveDir Directories="$(OutDir)\..\zip_working" />
</Target>
There is a final project which has links to the zipped files that it combines into a package. All appears normal, but apparently only when the bin and zip_working folders already exist. I.e. if I clean the solution, delete the bin folders and then rebuild, the final zip that is created in the "zip" folder for each project is empty...
And then the zip files have content only after I build again.
So I'm guessing that during the build process, the AfterBuild target is running before the build output files exist. Does that sound right? I trigger the builds purely from within Visual Studio.
Regardless, how can I ensure that I can run a task on build output files only after they've been created?
Applies to Visual Studio 2013 Update 5 / MSBuild 12.0
If you delete everything in OutDir and then build the project, a top-level (as in, not inside a target) ItemGroup is evaluated before the build even starts. Some info can be found here for example. In other words, before a build and with an empty OutDir $(OutDir)\**\*.* evaluates to nothing and your CopySourceFiles item is empty.
The solution is simply to move the ItemGroup inside of the AfterBuild target. It will then be evaluated after the build and hence gets a proper view on the current files in outDir.

Make visual studio build when output won't change?

In my project I have a json file I use for configuration that I have git set to ignore. When the repository is first cloned, the configuration file that is part of the project and that is copied to the output directory doesn't exist. I've gotten this to work using tasks in the 'BeforeBuild' target in the project that will copy the sample file to the actual config file if it doesn't exist.
<Target Name="BeforeBuild">
<ItemGroup>
<MySourceFiles Include="Configuration.sample.json" />
</ItemGroup>
<ItemGroup>
<MyDestinationFiles Include="Configuration.json" />
</ItemGroup>
<Message Importance="high" Condition="!Exists('#(MyDestinationFiles)')"
Text="Copying #(MySourceFiles) to #(MyDestinationFiles)" />
<Copy Condition="!Exists('#(MyDestinationFiles)')"
SourceFiles="#(MySourceFiles)"
DestinationFiles="#(MyDestinationFiles)" />
</Target>
So if I build the project, then delete the configuration file and do a build, nothing happens because no changes have been made that would change the outputs I think. Is there a way to change the project file so that a build will be flagged as necessary? It shouldn't come up very often and I can always do a 'Clean' or 'Rebuild' manually, but it's nagging at me since I'm just starting to learn MSBuild files.
From the documentation on a Target's Outputs attribute:
The files that form outputs into this target. Multiple files are
separated by semicolons. The timestamps of the files will be compared
with the timestamps of files in Inputs to determine whether the Target
is up to date
So if you add the paths to the outputfiles created by your Beforebuild target to it's Outputs attribute, at the start of every build msbuild will check if those files exist and if not it will start a build because now the project is considered to not be up-to-date anymore. In practice use:
<Target Name="BeforeBuild" Outputs="#(MyDestinationFiles)">

MVC3 project build fails because of web.config in obj folder

I have <MvcBuildViews>true</MvcBuildViews> setup in my project files.
Now when I do deployment the first time I get files in obj\release\package\packagetmp. Every subsequent build after this results in a faild build.
Web -> C:\Projects\ProjectX\Web\bin\ProjectX.Web.dll
C:\Projects\ProjectX\Web\obj\release\package\packagetmp\web.config(64):
error ASPCONFIG: It is an error to use a section registered as
allowDefinition='MachineToApplication' beyond application level.
This error can be caused by a virtual directory not being configured as an
application in IIS.
------ Skipped Publish: Project Web, Configuration: Release Any CPU ------
Now if delete the obj folder, I can build fine.
This is rather frustrating to have any build fail after I publish until I manually delete the obj folder. Is there anything I can do to fix this?
Add this to the .csproj file:
<Target Name="AfterBuild" Condition="'$(Configuration)'=='Release'">
<RemoveDir Directories="$(IntermediateOutputPath)" />
</Target>
Seems to delete the files in obj\Release but not the folder itself, at least on my machine.
This problem occurs because MvcBuildViews conflicts with Web Deploy packaging. I got the idea for this solution from http://www.zvolkov.com/clog/2011/02/16/asp-net-razor-lessons-learned/ :
<PropertyGroup>
<PackageDependsOn Condition="'$(DeployOnBuild)'=='true'">
CleanWebsitesPackage;
CleanWebsitesPackageTempDir;
CleanWebsitesTransformParametersFiles;
MvcBuildViews;
$(PackageDependsOn)
</PackageDependsOn>
<BuildDependsOn Condition="'$(DeployOnBuild)'!='true'">
$(BuildDependsOn);
MvcBuildViews
</BuildDependsOn>
</PropertyGroup>
<Target Name="MvcBuildViews" Condition="'$(MvcBuildViews)'=='true'">
<AspNetCompiler VirtualPath="temp" PhysicalPath="$(WebProjectOutputDir)" />
</Target>
Now AspNetCompiler will run before packaging. This works nicely with the msbuild /p:DeployOnBuild=True /p:DeployTarget=Package approach.
This is a known issue with MVC when you enable the build MVC views feature. Hopefully they get it fixed in the next version.
In the mean time, you can run the "Clean Solution" operation to clear out the obj folder instead of doing it manually. The downside is, your entire solution will be rebuilt every time.
Another option that I haven't done, but have considered if my project gets any bigger, is a pre-compile step in the MVC project to remove the web.config from obj\

How to turn off caching of build definitions in Visual studio

In project file I import my own target file
<Import Project="Build\CopyDependencies.target" />
and later I call target from that target file
<CallTarget Targets="CopyDependencies" UseResultsCache="false" />
If I edit CopyDependencies.target file I have to reload whole solution and only then changes to CopyDependencies.target take effect. I believe it is some sort of build definitions caching in Visual Studio? If it is, maybe it can be turned off?
Thanks #KazR
Here is a smaller Solution that you can insert into your .csproj file
<Target Name="AfterBuild">
<PropertyGroup>
<TempProjectFile>Build.$([System.Guid]::NewGuid()).proj</TempProjectFile>
</PropertyGroup>
<Copy SourceFiles="Build.proj" DestinationFiles="$(TempProjectFile)" />
<MSBuild Projects="$(TempProjectFile)" />
<ItemGroup>
<TempProjectFiles Include="Build.????????-????-????-????-????????????.proj"/>
</ItemGroup>
<Delete Files="#(TempProjectFiles)" />
</Target>
Problem solved
I don't know how you would disable the VS cache, however I may have a workaround that would allow you to edit the build target without having to reload the solution.
You could use the MSBuild task in your proj file to call a wrapper target that copies your CopyDependencies.target file to CopyDependencies.[RandomNumber].target, then invokes your CopyDependencies target in the newly created file, and finally deletes it.
This would force VS to reload the target on each invocation as the filename is different.
Here's an example:
myProject.proj
Add this to the AfterBuild target:
<MSBuild Projects="Wrapper.target" Targets="MyWrappedTarget" UnloadProjectsOnCompletion="true"/>
Wrapper.target
Here we have the target that will - at build time - copy the real target file and invoke the desired build target within it (I've used an inline c# task which is only available in MSBuild 4.0):
<UsingTask TaskName="RandomNumber" TaskFactory="CodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll">
<ParameterGroup>
<Number ParameterType="System.Int32" Output="true"/>
</ParameterGroup>
<Task>
<Code Type="Fragment" Language="cs">
<!-- CDATA -->
Random rndGenerator = new Random();
Number = rndGenerator.Next(Int32.MaxValue);
<!-- CDATA -->
</Code>
</Task>
</UsingTask>
<Target Name="MyWrappedTarget">
<Message Text="MyWrappedTarget target called"/>
<RandomNumber>
<Output TaskParameter="Number" PropertyName="FileNumber"/>
</RandomNumber>
<PropertyGroup>
<CopiedTarget>inner.test.$(FileNumber).target</CopiedTarget>
</PropertyGroup>
<Copy SourceFiles="inner.test.target" DestinationFiles="$(CopiedTarget)"/>
<MSBuild Projects="$(CopiedTarget)" Targets="_innerTestTarget"/>
<Delete Files="$(CopiedTarget)"/>
</Target>
inner.test.target
This contains the real build target you want to execute, in this example it's a simple file copy.
<Target Name="_innerTestTarget">
<Message Text="This is a inner test text message"/>
<Copy SourceFiles="x.txt" DestinationFiles="x1.txt"/>
</Target>
This isn't production ready, but hopefully illustrates my point.
With this (slightly convoluted) process in place, you can change the inner.test.target file without having to reload the solution in VS.
Here's a solution that doesn't require any MSBuild scripting at all.
I noticed that unloading and reloading a project doesn't get around the cache, but closing and reopening the solution does. In addition, Visual Studio will prompt you to reload the solution if it notices the .sln file has changed. And finally, this superuser question explains how to touch a file in Windows.
Putting these together, I added a Visual Studio external tool to touch the current solution file. Here's how:
Select TOOLS > External Tools ...
Click the Add button to add a new tool.
Set properties as follows:
Title: Reload Solution
Command: cmd.exe
Arguments: /c copy "$(SolutionFileName)"+>nul
Initial directory: $(SolutionDir)
and turn on Use Output window
Click OK to close the External Tools window
Now if you have made changes to your MSBuild files, just select TOOLS > Reload Solution and all your build files will be reloaded.
I'm using Windows 7 64-bit and Visual Studio 2012 Express for Windows Desktop.
I have a different solution, not involving temporary files:
Include.targets file:
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Target Name="Foobar">
<Copy SourceFiles="test.source" DestinationFiles="testFoobar.dest" />
</Target>
</Project>
Project file:
....
<Target Name="BeforeBuild">
<Exec Command="$(MSBuildToolsPath)\MSBuild.exe Include.targets /t:Foobar" ContinueOnError="false" />
</Target>
....
in this case VS does not recognize the MSBuild command, and does not cache the file.
happy coding!
Before running MSBuild I run this to clear the download cache:
call "%VS120COMNTOOLS%vsvars32.bat"
echo Clear download cache
gacutil -cdl

Resources