Skip to content

Commit

Permalink
[Xamarin.Android.Build.Tasks] Stop designtime designer being deleted. (
Browse files Browse the repository at this point in the history
…#1307)

Fixes: #1286

We have a number of problems with our DesignTime build system. The
main one which this PR addresses is the designer file is deleted
by `IncrementalClean` AND `CoreClean`. This them completely messes
up the DesignTime system since it can no longer find the file.

So what we should be doing is making sure we don't tell the build
system about the designer file ;-). We do this by not writing the
path to the `$(CleanFile)`.

~~ Some background on the [DesignTime build system][2] ~~

The primary entry point is the `CoreCompile` target, with occasional
calls to the `RefreshReferences` target. In theory
[`$(DesignTimeBuild)`][1] should be set to `true`; however, this is
not always the case which is why we have the `_SetupForDesignTimeBuild`
target which hooks into the `CoreCompile` target. Additionally,
`$(BuildingProject)` should be False. ([Additional information][2]).
However this document is not seem to be 100% accurate since the IDE's
(namely Visual Studio) do not always set the required properties. One
of the key requirements for design time builds seems to be that they
are independent of the main builds. The files it uses should not be
removed since the `CoreCompile` target does not seem to be called
consistently. For example, if the designer file is removed and the
`RefreshReferences` target is called, the design time build may well
fail because the targets required to re-generate the designer file
are not called.

Now for the problem: the `IncrementalClean` target runs between builds.
It seems to want to remove files from the previous build. I suspect the
purpose is to remove files that are no longer needed. (For example an
assembly which is no longer referenced.) The problem is that we have
a file (the design time `Resource.designer.cs`) which is not built as
part of the normal build. But this file was still being written to
`$(CleanFile)` which is the driver for the `IncrementalClean` process.
As a result, the file gets removed because its not in the list of
current `@(FileWrites)`. Not writing the name of this file to
`$(CleanFile)` seems to hide it from `IncrementalClean`.

There was also another problem with the `CoreClean` target. This was
also removing the designer files. Again this was down to the fact
that the file was listed in `$(CleanFile)`. However our older
workarounds did not work since they relied on `IncrementalClean`
running. For a `Clean` target invocation it does not. Again the
solution seems to be Don't Write the file to `$(CleanFile)`, which is
counterintuitive since we are told to write all files we generate to
that file.

[1]:  https://github.com/dotnet/project-system/blob/master/docs/design-time-builds.md#determining-whether-a-target-is-run-in-a-design-time-build
[2]: https://github.com/dotnet/project-system/blob/master/docs/design-time-builds.md
  • Loading branch information
dellis1972 authored and jonpryor committed Feb 15, 2018
1 parent 5f50dbf commit be0c744
Show file tree
Hide file tree
Showing 4 changed files with 83 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ Copyright (C) 2014 Xamarin. All rights reserved.
</PropertyGroup>

<Target Name="_RegisterAndroidFilesWithFileWrites" BeforeTargets="IncrementalClean" Condition=" '$(_IsRunningXBuild)' != 'true' ">
<CreateItem Include="$(OutDir)*.pdb;$(OutDir)*.dll;$(OutDir)*.dll.mdb;$(MonoAndroidIntermediateAssemblyDir)*.dll.mdb;$(MonoAndroidIntermediateAssemblyDir)*.pdb;$(MonoAndroidLinkerInputDir)*.dll.mdb;$(MonoAndroidLinkerInputDir)*.pdb;$(_AndroidManagedResourceDesignerFile)">
<CreateItem Include="$(OutDir)*.pdb;$(OutDir)*.dll;$(OutDir)*.dll.mdb;$(MonoAndroidIntermediateAssemblyDir)*.dll.mdb;$(MonoAndroidIntermediateAssemblyDir)*.pdb;$(MonoAndroidLinkerInputDir)*.dll.mdb;$(MonoAndroidLinkerInputDir)*.pdb">
<Output TaskParameter="Include" ItemName="_FilesToRegister" />
</CreateItem>
<CreateItem Include="$([System.IO.Path]::GetFullPath('%(_FilesToRegister.Identity)'))"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1014,7 +1014,7 @@ public void BuildAppWithManagedResourceParser()
"Target '_ManagedUpdateAndroidResgen' should not have run.");

Assert.IsTrue (appBuilder.Clean (appProj), "Clean should have succeeded");
Assert.IsFalse (File.Exists (designerFile), $"'{designerFile}' should have been cleaned.");
Assert.IsTrue (File.Exists (designerFile), $"'{designerFile}' should not have been cleaned.");

}
}
Expand Down Expand Up @@ -1110,10 +1110,10 @@ public void BuildAppWithManagedResourceParserAndLibraries ()


Assert.IsTrue (appBuilder.Clean (appProj), "Clean should have succeeded");
Assert.IsFalse (File.Exists (designerFile), $"'{designerFile}' should have been cleaned.");
Assert.IsTrue (File.Exists (designerFile), $"'{designerFile}' should not have been cleaned.");
designerFile = Path.Combine (Root, path, libProj.ProjectName, libProj.IntermediateOutputPath, "designtime", "Resource.Designer.cs");
Assert.IsTrue (libBuilder.Clean (libProj), "Clean should have succeeded");
Assert.IsFalse (File.Exists (designerFile), $"'{designerFile}' should have been cleaned.");
Assert.IsTrue (File.Exists (designerFile), $"'{designerFile}' should not have been cleaned.");


}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,85 @@ namespace Xamarin.Android.Build.Tests
[Parallelizable (ParallelScope.Children)]
public class IncrementalBuildTest : BaseTest
{
[Test]
public void IncrementalCleanDuringClean ()
{
var path = Path.Combine ("temp", TestName);
var proj = new XamarinAndroidApplicationProject () {
ProjectName = "App1",
IsRelease = true,
};
proj.SetProperty ("AndroidUseManagedDesignTimeResourceGenerator", "True");
proj.SetProperty ("BuildingInsideVisualStudio", "True");
using (var b = CreateApkBuilder (path)) {
b.Target = "Compile";
Assert.IsTrue(b.Build (proj), "DesignTime Build should have succeeded");
var designTimeDesigner = Path.Combine (Root, b.ProjectDirectory, proj.IntermediateOutputPath, "designtime", "Resource.designer.cs");
FileAssert.Exists (designTimeDesigner, $"{designTimeDesigner} should have been created.");
b.Target = "Build";
Assert.IsTrue(b.Build (proj), "Build should have succeeded");
FileAssert.Exists (designTimeDesigner, $"{designTimeDesigner} should still exist after Build.");
b.Target = "Clean";
Assert.IsTrue(b.Build (proj), "Clean should have succeeded");
FileAssert.Exists (designTimeDesigner, $"{designTimeDesigner} should still exist after Clean.");
b.Target = "Compile";
Assert.IsTrue(b.Build (proj), "Build should have succeeded");
FileAssert.Exists (designTimeDesigner, $"{designTimeDesigner} should still exist after Compile.");
b.Target = "Build";
Assert.IsTrue(b.Build (proj), "Build should have succeeded");
FileAssert.Exists (designTimeDesigner, $"{designTimeDesigner} should still exist after second Build.");
Assert.IsTrue(b.Build (proj), "Build should have succeeded");
FileAssert.Exists (designTimeDesigner, $"{designTimeDesigner} should still exist after third Build.");
b.Target = "Compile";
Assert.IsTrue(b.Build (proj), "Build should have succeeded");
FileAssert.Exists (designTimeDesigner, $"{designTimeDesigner} should still exist after second Compile.");
b.Target = "Clean";
Assert.IsTrue(b.Build (proj), "Clean should have succeeded");
FileAssert.Exists (designTimeDesigner, $"{designTimeDesigner} should still exist after second Clean.");
b.Target = "ReBuild";
Assert.IsTrue(b.Build (proj), "ReBuild should have succeeded");
FileAssert.Exists (designTimeDesigner, $"{designTimeDesigner} should still exist after ReBuild.");
}

}

[Test]
public void LibraryIncrementalBuild () {

var testPath = Path.Combine ("temp", TestName);
var class1Source = new BuildItem.Source ("Class1.cs") {
TextContent = () => @"
using System;
namespace Lib
{
public class Class1
{
public Class1 ()
{
}
}
}"
};
var lib = new XamarinAndroidLibraryProject () {
ProjectName = "Lib",
ProjectGuid = Guid.NewGuid ().ToString (),
Sources = {
class1Source,
},
};
using (var b = CreateDllBuilder (Path.Combine (testPath, "Lib"))) {
Assert.IsTrue (b.Build (lib), "Build should have succeeded.");
Assert.IsTrue (b.LastBuildOutput.ContainsText ("LogicalName=__AndroidLibraryProjects__.zip") ||
b.LastBuildOutput.ContainsText ("Lib.obj.Debug.__AndroidLibraryProjects__.zip,__AndroidLibraryProjects__.zip"),
"The LogicalName for __AndroidLibraryProjects__.zip should be set.");
class1Source.Timestamp = DateTime.UtcNow.Add (TimeSpan.FromMinutes (1));
Assert.IsTrue (b.Build (lib), "Build should have succeeded.");
Assert.IsTrue (b.LastBuildOutput.ContainsText ("LogicalName=__AndroidLibraryProjects__.zip") ||
b.LastBuildOutput.ContainsText ("Lib.obj.Debug.__AndroidLibraryProjects__.zip,__AndroidLibraryProjects__.zip"),
"The LogicalName for __AndroidLibraryProjects__.zip should be set.");
}
}

[Test]
public void AllProjectsHaveSameOutputDirectory()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1107,11 +1107,6 @@ because xbuild doesn't support framework reference assemblies.
<Compile Remove="@(CorrectCasedItem)" Condition=" '$(ManagedDesignTimeBuild)' == 'True' And '%(CorrectCasedItem.Identity)' != '' "/>
<Compile Include="$(_AndroidManagedResourceDesignerFile)" Condition=" '$(ManagedDesignTimeBuild)' == 'True' And Exists ('$(_AndroidManagedResourceDesignerFile)')" />
</ItemGroup>
<WriteLinesToFile
Condition="Exists ('$(_AndroidManagedResourceDesignerFile)')"
File="$(IntermediateOutputPath)$(CleanFile)"
Lines="$([System.IO.Path]::GetFullPath('$(_AndroidManagedResourceDesignerFile)'))"
Overwrite="false" />
</Target>

<!-- Resource Build -->
Expand Down

0 comments on commit be0c744

Please sign in to comment.