A colleague ran into a very interesting issue whilst working on his teams release build script. He was trying to get the custom built version number setup before a clean of the old source had taken place (so the version number could become the build number). To achieve this he was calling a sub script that (for use by a different target) setup a list of all the AssemblyInfo.cs files that needed to be updated with the version number. Later on in the build (after the clean / get) he reran this same script (for a different target) to write the version numbers into the AssembyInfo file. At this point the script would fail as one of the files wasn't there (a remnant of a previous packaging process). Much head scratching ensued as we tried to figure out how the heck an item list in a different script, invoked with MSBuild was populated from the first time it ran...
A theory was constructed that something was being cached, or data was moving in a way we didn't expect. Either the MSBuild task held onto scripts, or the item list was passed back to the calling script. The later theory was quickly discounted, which just left us with the MSBuild task caching script instances.
After a bit of digging around in the Microsoft.Build.Tasks assembly I managed to reach down into where it was actually executing MSBuild... not (as I'd thought) by spinning up a seperate process, but by calling back into it's own build engine. Fair enough, saves having to start a new process (although it explained some unusual file locking we'd seen as well). Further digging into the build engine found the following:
1: Project project = null;
2: Project existingProject = null;
3: Project project3 = (Project) this.projectsLoadedByHost[info.FullName];
4: if (project3 != null)
5: {
6: existingProject = project3;
7: if (project3.GlobalProperties.IsEquivalent(globalProperties))
8: {
9: project = project3;
10: }
11: }
Scripts are held firstly by the name of the script, and secondly BY THE PROPERTIES THEY ARE PASSED...
So recalling the same script with a different target but the same properties doesn't invoke a new instance of the script. No, the old one is reused and since Items are populated only when the script is first invoked the list of AssemblyInfo.cs files was as it was BEFORE the clean / get, not (as we wanted) afterwards.
In this instance the simple fix was to add an extra property to each of the two calls to ensure that the property lists were different ... and voila! Problem solved.