Project file
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.2</TargetFramework>
</PropertyGroup>
<PropertyGroup>
<GenerateFile>DataFileList.txt</GenerateFile>
</PropertyGroup>
<ItemGroup>
<DataFiles Include="**\*.dat" />
</ItemGroup>
<ItemGroup>
<UpToDateCheckInput Include="#(DataFiles)" />
<UpToDateCheckBuilt Include="$(OutputPath)$(GenerateFile)" />
</ItemGroup>
<Target Name="TestBuild" AfterTargets="Build" Inputs="#(DataFiles)" Outputs="$(OutputPath)$(GenerateFile)">
<Message Text="**** TestBuild ****" />
<WriteLinesToFile File="$(OutputPath)$(GenerateFile)" Lines="#(DataFiles)" Overwrite="true" />
</Target>
</Project>
What I have tried
The operation sequence and results are shown below.
First of all, basic operation when rebuild and build are repeated.
Rebuild -> build run and "TestBuild" target executed 🆗
Build -> build run and "TestBuild" target skipped 🆗
Build once more -> build skipped 🆗
Next, update the dat file and build the result as shown below.
Build -> build run and "TestBuild" target executed 🆗
Build once more -> build run and "TestBuild" target skipped 🆖
Build once more -> build run and "TestBuild" target skipped 🆖 (Also, the build always runs after this)
The results I expect are below.
Build once more -> build skipped
Questions
UpToDateCheckBuilt doesn't seem to work, but I want to know if it is.
I want to know the correct way to write UpToDateCheckBuilt.
I want to know the correct way to write UpToDateCheckBuilt.
Try changing the part of the content in your script from:
<ItemGroup>
<UpToDateCheckInput Include="#(DataFiles)" />
<UpToDateCheckBuilt Include="$(OutputPath)$(GenerateFile)" />
</ItemGroup>
To:
<ItemGroup>
<!--<UpToDateCheckInput Include="#(DataFiles)" />-->
<UpToDateCheckBuilt Include="$(OutputPath)$(GenerateFile)" Original="#(DataFiles)"/>
</ItemGroup>
UpToDateCheckBuilt doesn't seem to work, but I want to know if it is.
The reason of this strange behavior in step 5 and 6 is more related to <UpToDateCheckInput Include="#(DataFiles)" />. Your TestBuild target's Up-To-Date check works well, the reason why build always start after step4 is you set the xx.dat as the input file for the whole project.
Description:
In VS IDE, it has a Up-To-Date check for the whole project.
During build process of .net core app, there's a CoreCompile target which works to compile the source code(xx.cs) to output Application.dll or Aplication.exe.
For this important target, its input files are source files(xx.cs) and referenced assemblies.
1.After you do some changes to xx.dat file,since this file is input file of the project after you use UpToDateCheckInput and it's newer than output files, the build will execute in step4. And your TestBuild target will also run since the xx.dat is also the input file for itself. But since this file is not the input file for CoreCompile target, the output xx.exe or xx.dll are up-to-date with respect to the input files(xx.cs, referenced dlls), this target won't run! It means, the build of the project starts in step4 while vs won't compile the source file again, so the output xx.exe or xx.dll is always the original one.
2.Then, after step4, no matter how many times you try to build the project, the xx.dat is always newer than the original output assembly, then the build will always start.
Related
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" />
I created a custom Task that executes after the build operation.
<Target Name="AfterBuild" />
<Target Name="MyTarget"
AfterTargets="AfterBuild">
<MyTask ... />
</Target>
QUESTION: Is it possible to execute the task, if the build operation was triggered, but did not perform, because there are no changes in the project / no need to build again?
In other words: I want to execute the task always at the end of the build process, even if the project was not built again.
UPDATE: Using AfterTargets="Build" or setting the property <DisableFastUpToDateCheck>true</DisableFastUpToDateCheck> does not help.
After triggering the Build process a second time, I only get the Output: Build: 0 succeeded, 0 failed, 1 up-to-date, 0 skipped
Is it possible to execute the task, if the build operation was triggered, but did not perform, because there are no changes in the project / no need to build again?
If I understand you correctly, you can define this property in your project file:
<PropertyGroup>
<DisableFastUpToDateCheck>true</DisableFastUpToDateCheck>
</PropertyGroup>
Note: This method seems that Visual Studio is bypassing normal up-to-date checks of MSBuild and using some sort of custom check that is faster, but has a side effect of breaking customized build targets.
Update:
Not sure the reason why this method not work on your project, let me make the answer more detail:
Define the property in your project file:
Add the custom MSBuild task with some messages info.
Build the project, check the output(log file verbosity is Normal).
Build the project again, check the output again.
If I use AfterTargets="Build" instead of AfterBuild, the message is written to the Output window every time I build the solution (.NET Core Console App with a .NET Standard Class Libary).
<Target Name="MyAfterBuild" AfterTargets="Build">
<Message Importance="High" Text="Hello World!" />
</Target>
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.
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)">
Since this question seems to have baffled / underwhelmed SO I will rephrase it with a partially formed idea of my own.
Could I somehow set up a batch file or something that runs after the whole solution is built, and this batch file would call msbuild to build specific targets inside a certain project? In order for it to work, I would have to somehow force msbuild build the target without regard to whether it thinks it's "up to date", because that is the core issue I'm butting up against.
Since you are dealing with building specifically you may want to replace your batch file with an MSBuild file. For example:
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<SolutionsToBuild Include="MySolution.sln"/>
<Projects Include="Proj1.csproj"/>
<Projects Include="Proj2.csproj"/>
<Projects Include="Proj3.csproj"/>
</ItemGroup>
<Target Name="BuildAll">
<!-- Just executes the DefaultTargets (Build) -->
<MSBuild Projects="#(SolutionsToBuild)"/>
<!-- Call Rebuild if you think its not building correctly -->
<MSBuild Projects="#(Projects)"
Targets="Rebuild"/>
</Target>
</Project>
Then you just invoke msbuild.exe on this file with:
msbuild.exe Build.proj /t:BuildAll
Since you said that you want to build specific projects after the solution is built just put those into the Projects ItemGroup as shown and use the MSBuild task to build them after the solution has been built. I've specified the Rebuild target to make sure you get a clean build.