-
Notifications
You must be signed in to change notification settings - Fork 532
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Some X.A targets do not respect Inputs/Outputs timestamp invariant #2247
Comments
Thinking about it more, I don’t know if there is any way you can salvage that Maybe what can be done is that the heavy targets output an intermediary file that’s always written to and then a second target copy that intermediate file to the real location if it has changed. That way the heavy task will respect incrementality and not be re-run all the time while the |
@garuma if these targets are running all the time, and taking > 0ms, we should look into this. The immediate idea that comes to mind is to use a "stamp" file: <Target Name="_ResolveLibraryProjectImports" Inputs="..." Outputs="$(IntermediateOutputPath)libraryprojectimports.stamp">
<!--Whatever the target does-->
<Touch Files="$(IntermediateOutputPath)libraryprojectimports.stamp" AlwaysCreate="True" />
<ItemGroup>
<FileWrites Include="$(IntermediateOutputPath)libraryprojectimports.stamp" />
</ItemGroup>
</Target We would also need to verify the inputs are correct. @garuma is there an easy way I can reproduce this? or can you attach binlogs? |
@jonathanpeppers yeah making everything do stamps might be the easiest things considering it's already done throughout. I had started writing a skeleton of unit-test showing the issue but basically the gist of it is to start |
@garuma I can reproduce this issue for [Test]
public void CopyIntermediateAssemblies ()
{
var target = "_CopyIntermediateAssemblies";
var proj = new XamarinFormsAndroidApplicationProject ();
using (var b = CreateApkBuilder (Path.Combine ("temp", TestName))) {
Assert.IsTrue (b.Build (proj), "first build should succeed");
Assert.IsFalse (b.Output.IsTargetSkipped (target), $"`{target}` should *not* be skipped!");
var assembly = Path.Combine (Root, b.ProjectDirectory, proj.IntermediateOutputPath, proj.ProjectName + ".dll");
FileAssert.Exists (assembly);
File.SetLastWriteTimeUtc (assembly, DateTime.UtcNow);
File.SetLastAccessTimeUtc (assembly, DateTime.UtcNow);
//NOTE: second build, target will run because inputs changed
Assert.IsTrue (b.Build (proj, doNotCleanupOnUpdate: true, saveProject: false), "second build should succeed");
Assert.IsFalse (b.Output.IsTargetSkipped (target), $"`{target}` should *not* be skipped on second build!");
//NOTE: third build, it should certainly *not* run! there are no changes
Assert.IsTrue (b.Build (proj, doNotCleanupOnUpdate: true, saveProject: false), "third build should succeed");
Assert.IsTrue (b.Output.IsTargetSkipped (target), $"`{target}` should be skipped on third build!");
}
} This is going to certainly impact incremental build times... I will probably investigate each target separately and create a PR for each. |
For reference, we also logged an enhancement in MSBuild itself to potentially surface those type of issues more easily: dotnet/msbuild#3817 |
Context: dotnet#2247 Context: dotnet#2128 The `_CopyIntermediateAssemblies` target has a bug for incremental builds, reproduced by the following: - Build a Xamarin.Android app project - `touch` the project's assembly in `$(IntermediateOutputPath)` - Build a second time. `_CopyIntermediateAssemblies` will run as expected. - Build a third time. `_CopyIntermediateAssemblies` will still run! When in this state, `_CopyIntermediateAssemblies` will always run. This is bad because it triggers other expensive targets like `_UpdateAndroidResgen` _every time_. I believe I introduced this in dotnet#2128, which was when I saw `_CopyIntermediateAssemblies` taking more time than it used to. It was a good tradeoff though, since it prevented `_UpdateAndroidResgen` from running too often. 15.9 does not have dotnet#2128. To fix this problem properly, I introduced a new `_copyintermediate.stamp` file to be used as the `Outputs` of the target. In addition to fixing our incremental build here, there should be performance gains in only verifying the timestamp of one file in `Outputs`. The `Inputs` of the `_CopyIntermediateAssemblies` were also incorrect, as it was not using the "satellite" assembly files as an input. This does not fully complete dotnet#2247, as there are two other targets listed I need to investigate further. These are likely unrelated to the changes in dotnet#2128.
…#2308) Context: #2247 Context: c2d3681 The `_CopyIntermediateAssemblies` target has a bug for incremental builds, reproduced by the following: 1. Build a Xamarin.Android app project 2. `touch` the project's assembly in `$(IntermediateOutputPath)` 3. Build a second time. `_CopyIntermediateAssemblies` will run as expected. 4. Build a third time. `_CopyIntermediateAssemblies` still runs! When in this state, `_CopyIntermediateAssemblies` will always run. This is bad because it triggers other expensive targets like `_UpdateAndroidResgen` *every time*. I believe I introduced this in c2d3681, which was when I saw `_CopyIntermediateAssemblies` taking more time than it used to. It was a good tradeoff though, since it prevented the `_UpdateAndroidResgen` target from running too often. d15-9 does not have c2d3681. To fix this problem properly, I introduced a new `_copyintermediate.stamp` file to be used as the `Outputs` of the target. In addition to fixing our incremental build here, there should be performance gains in only verifying the timestamp of one file in `Outputs`. The `Inputs` of the `_CopyIntermediateAssemblies` were also incorrect, as it was not using the "satellite" assembly files as an input. This does not fully finish #2247, as there are two other targets listed I need to investigate further. These are likely unrelated to the changes in c2d3681.
Fixes: dotnet#2247 We currently have two targets that fail to build incrementally: - `_ResolveLibraryProjectImports` - `_GeneratePackageManagerJava` Both of these targets declare `Outputs`, but don't always update the timestamp of the output file. They used `CopyIfChanged` logic, so they won't write to the file if nothing changed. However... We can't just add `<Touch/>` calls here either, as the `Outputs` of these targets are `Inputs` to other *slow* targets (`_CompileJava`, `_UpdateAndroidResgen`, and friends). If we used `<Touch/>` here, things would be worse and trigger the *slow* targets to run. So the only option here is to use a "stamp" file, this allows these targets to properly build incrementally and not trigger other targets. I updated a `IncrementalBuildTest` to include a test case checking all three of these targets. Future changes: - I am going to need to audit many of our MSBuild targets for this same problem, and so I will likely need to add many new stamp files. - To simplify this, we should adopt a new convention of placing stamp files in `$(IntermediateOutputPath)stamp\` or `$(_AndroidStampDirectory)`. - The stamp file should be named the same as the target where it is used as an `Output`. Documentation: I also started some initial documentation on "MSBuild Best Practices", which we can expand upon in future PRs. See the `Stamp Files` section on what is relevant in this PR.
Fixes: #2247 We currently have two targets that fail to build incrementally: - `_ResolveLibraryProjectImports` - `_GeneratePackageManagerJava` Both of these targets declare `Outputs`, but don't always update the timestamp of the output file. They used `CopyIfChanged` logic, so they won't write to the file if nothing changed. However... We can't just add `<Touch/>` calls here either, as the `Outputs` of these targets are `Inputs` to other *slow* targets (`_CompileJava`, `_UpdateAndroidResgen`, and friends). If we used `<Touch/>` here, things would be worse and trigger the *slow* targets to run. So the only option here is to use a "stamp" file, this allows these targets to properly build incrementally and not trigger other targets. I updated a `IncrementalBuildTest` to include a test case checking all three of these targets. Future changes: - I am going to need to audit many of our MSBuild targets for this same problem, and so I will likely need to add many new stamp files. - To simplify this, we should adopt a new convention of placing stamp files in `$(IntermediateOutputPath)stamp\` or `$(_AndroidStampDirectory)`. - The stamp file should be named the same as the target where it is used as an `Output`. Documentation: I also started some initial documentation on "MSBuild Best Practices", which we can expand upon in future PRs. See the `Stamp Files` section on what is relevant in this PR.
I'm debugging an incremental compilation for a project of ours and I have identified at least 3 spots where X.A targets are using
CopyIfChanged
or a variant-thereof and thus breaking inputs/outputs invariant.What I mean by this is that you have plenty of scenarios where some target
Inputs
are newer thanOutputs
causing the target to run yet those updated Inputs will end up generating the same Outputs content. Because of the logic ofCopyIfChanged
, this mean that no actual change is committed to Outputs and thus when the target finishes executing you have broken the invariant thatOutputs
always have to be newer that yourInputs
.Three places where I have seen this (there might be more):
_CopyIntermediateAssemblies
(Outputs are also wrong for this one, they lack the satellites copy)_GeneratePackageManagerJava
_ResolveLibraryProjectImports
The text was updated successfully, but these errors were encountered: