Skip to content

Conversation

@jonathanpeppers
Copy link
Member

When taking an audit of our build times, I was testing the following
scenario:

  • msbuild Droid.csproj /t:Install /bl:first.binlog
  • msbuild Droid.csproj /t:Install /bl:second.binlog

In theory, the "second" build should be very fast and basically not do
anything. Unfortunately that was not the case.

In my example, I saw build logs such as:

Target _LinkAssembliesNoShrink
    Building target "_LinkAssembliesNoShrink" partially, because some output files are out of date with respect to their input files.
    [ResolvedUserAssemblies: Input=C:\Users\myuser\.nuget\packages\xamarin.forms\3.0.0.482510\lib\MonoAndroid10\FormsViewGroup.dll, Output=obj\Debug\android\assets\FormsViewGroup.dll] Input file is newer than output file.
    [ResolvedUserAssemblies: Input=C:\Users\myuser\.nuget\packages\xamarin.forms\3.0.0.482510\lib\MonoAndroid10\Xamarin.Forms.Core.dll, Output=obj\Debug\android\assets\Xamarin.Forms.Core.dll] Input file is newer than output file.
    [ResolvedUserAssemblies: Input=C:\Users\myuser\.nuget\packages\xamarin.forms\3.0.0.482510\lib\MonoAndroid10\Xamarin.Forms.Platform.Android.dll, Output=obj\Debug\android\assets\Xamarin.Forms.Platform.Android.dll] Input file is newer than output file.
    [ResolvedUserAssemblies: Input=C:\Users\myuser\.nuget\packages\xamarin.forms\3.0.0.482510\lib\MonoAndroid10\Xamarin.Forms.Platform.dll, Output=obj\Debug\android\assets\Xamarin.Forms.Platform.dll] Input file is newer than output file.
    [ResolvedUserAssemblies: Input=C:\Users\myuser\.nuget\packages\xamarin.forms\3.0.0.482510\lib\MonoAndroid10\Xamarin.Forms.Xaml.dll, Output=obj\Debug\android\assets\Xamarin.Forms.Xaml.dll] Input file is newer than output file.
    LinkAssemblies

Looking at the LinkAssemblies MSBuild task, I didn't see anything
that would be setting the timestamps on copied files.

For timestamps to be correct, we should either:

  • Use MonoAndroidHelper.SetLastAccessAndWriteTimeUtc in C#
  • Use the <Touch /> MSBuild task after using the <Copy /> MSBuild
    task

This lead me to write a CheckTimestamps unit test that does the
following:

  • Store DateTime.UtcNow in a variable
  • Build an app
  • Make sure nothing in bin or obj are older than the start time

This uncovered even more out of date files such as:

  • mono.android.jar
  • Assemblies in obj/Debug/linksrc or $(MonoAndroidLinkerInputDir)

Fixes made in various places:

  • <LinkAssemblies /> task needed to use
    MonoAndroidHelper.SetLastAccessAndWriteTimeUtc everywhere
    MonoAndroidHelper.CopyIfChanged is used
  • The _CopyIntermediateAssemblies target appeared to have a typo. It
    was running a <Touch /> on ResolvedUserAssemblies where it
    looked like it should be ResolvedAssemblies from the above
    <Copy />
  • The _AddStaticResources target needed a <Touch /> for
    mono.android.jar

This fix should improve our incremental build times dramatically, as
these timestamps caused other targets to run that slow things down.

//Absolutely non of these files should be *older* than the starting time of this test!
var files = Directory.EnumerateFiles (intermediate, "*", SearchOption.AllDirectories).ToList ();
files.AddRange (Directory.EnumerateFiles (output, "*", SearchOption.AllDirectories));
var now = DateTime.UtcNow;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whoops I should delete this now variable, it's not used.

@jonathanpeppers
Copy link
Member Author

jonathanpeppers commented May 17, 2018

My new test failed on macOS, looking into it, a file produced by <Aapt /> had this assertion failure:

`/Users/builder/jenkins/workspace/xamarin-android-pr-builder/xamarin-android/bin/TestDebug/temp/CheckTimestamps/obj/Debug/res/drawable-hdpi/icon.png` is older than `5/16/2018 10:22:34 PM`, with a timestamp of `5/16/2018 10:22:34 PM`!

So this looks to be the issue where FileInfo.LastModifiedTimeUtc doesn't have Millisecond information running on Mono. I worked around this in the test by subtracting one second (good enough?).

DestinationFiles="@(_AndroidResolvedSatellitePaths->'$(MonoAndroidLinkerInputDir)%(DestinationSubDirectory)%(Filename)%(Extension)')"
SkipUnchangedFiles="true"
/>
<Touch Files="@(ResolvedUserAssemblies->'$(MonoAndroidLinkerInputDir)%(Filename)%(Extension)')" />
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dellis1972: Is this change correct? IIRC in Debug builds, @(ResolvedAssemblies) may contain system-installed assemblies such as Mono.Android.dll, which we almost certainly don't want to be <Touch/>ing...

Copy link
Member Author

@jonathanpeppers jonathanpeppers May 17, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What made me think this was a mistake was the previous line:

<Copy
	SourceFiles="@(ResolvedAssemblies)"
	DestinationFiles="@(ResolvedAssemblies->'$(MonoAndroidLinkerInputDir)%(Filename)%(Extension)')"
	SkipUnchangedFiles="true" />

I was thinking DestinationFiles from <Copy /> needs to match Files from <Touch />.

If an item in @(ResolvedAssemblies) was the system Mono.Android.dll, for example, @(ResolvedAssemblies->'$(MonoAndroidLinkerInputDir)%(Filename)%(Extension)') should evaluate to obj\Debug\linksrc\Mono.Android.dll.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think @jonathanpeppers is right on this one. We are touching the files in the Intermediate dir so it should be ok.

jonathanpeppers added a commit to jonathanpeppers/Xamarin.Forms that referenced this pull request May 17, 2018
Context: xamarin#2230

The main performance problem with the collection of MSBuild targets in
`Xamarin.Forms.targets` is they don't build incrementally. I addressed
this with `XamlC` using a "stamp" file; however, it is not quite so
easy to setup the same thing with `XamlG`.

They way "incremental" builds are setup in MSBuild, is by specifying
the `Inputs` and `Outputs` of a `<Target />`. MSBuild will partially
build a target when some outputs are not up to date, and skip it
entirely if they are all up to date.

The best docs I can find on MSBuild incremental builds:
https://msdn.microsoft.com/en-us/library/ms171483.aspx

Unfortunately a few things had to happen to make this work for
`XamlG`:
- Define a new target `_FindXamlGFiles` that is invoked before `XamlG`
- `_FindXamlGFiles` defines the `_XamlGInputs` and `_XamlGOutputs`
  `<ItemGroup />`'s
- `_FindXamlGFiles` must also define `<Compile />` and `<FileWrites />`,
  in case the `XamlG` target is skipped
- `XamlGTask` now needs to get passed in a list of `OutputFiles`,
  since we have computed these paths ahead of time
- `XamlGTask` should validate the lengths of `XamlFiles` and
  `OutputFiles` match, used error message from MSBuild proper:
  https://github.com/Microsoft/msbuild/blob/a691a44f0e515e9a03ede8df0bff22185681c8b9/src/Tasks/Copy.cs#L505

`XamlG` now builds incrementally!

To give some context on how much improvement we can see with build
times, consider the following command:

    msbuild Xamarin.Forms.ControlGallery.Android/Xamarin.Forms.ControlGallery.Android.csproj

If you run it once, it will take a while--this change will not improve
the first build. On the second build with the exact same command, it
*should* be much faster.

Before this commit, the second build on my machine takes:

    40.563s

After the change:

    23.692s

`XamlG` has cascading impact on build times when it isn't built
incrementally:
- The C# assembly always changes
- Hence, `XamlC` will always run
- Hence, `GenerateJavaStubs` will always run
- Hence, `javac.exe` and `dx.jar` will always run

I am making other improvements like this in Xamarin.Android itself,
such as:
dotnet/android#1693

Other changes:
- I reordered my past `<Output />` element in XamlC to match others in
  this file
- `FilesWrite` is actually supposed to be `FileWrites`, see canonical
  source of how `Clean` works and what `FileWrites` is here:
  dotnet/msbuild#2408 (comment)

Unfortunately, there is more to do to get this PR "feature complete":
- TESTS? This refactoring is complex and has a bit of nuance to it. I
  can't imagine we can get this improvement right (at all), without
  some new testing infrastructure in place that invokes MSBuild.
- There were some checks for `%(TargetPath)` being blank in the C#
  code of `XamlGTask`. I presume this is from the designer and/or
  design-time builds. We probably need to figure out the proper way to
  do this instead of using `%(TargetPath)` at all. After testing the
  current implementation I had all kinds of crazy temporary files in
  my `$(IntermediateOutputPath)`. This probably isn't the right way to
  handle this issue.
- CssG needs the exact same setup, as it was patterned after `XamlG`
…folders

When taking an audit of our build times, I was testing the following
scenario:
- `msbuild Droid.csproj /t:Install /bl:first.binlog`
- `msbuild Droid.csproj /t:Install /bl:second.binlog`

In theory, the "second" build should be very fast and basically not do
anything. Unfortunately that was not the case.

In my example, I saw build logs such as:

    Target _LinkAssembliesNoShrink
        Building target "_LinkAssembliesNoShrink" partially, because some output files are out of date with respect to their input files.
        [ResolvedUserAssemblies: Input=C:\Users\myuser\.nuget\packages\xamarin.forms\3.0.0.482510\lib\MonoAndroid10\FormsViewGroup.dll, Output=obj\Debug\android\assets\FormsViewGroup.dll] Input file is newer than output file.
        [ResolvedUserAssemblies: Input=C:\Users\myuser\.nuget\packages\xamarin.forms\3.0.0.482510\lib\MonoAndroid10\Xamarin.Forms.Core.dll, Output=obj\Debug\android\assets\Xamarin.Forms.Core.dll] Input file is newer than output file.
        [ResolvedUserAssemblies: Input=C:\Users\myuser\.nuget\packages\xamarin.forms\3.0.0.482510\lib\MonoAndroid10\Xamarin.Forms.Platform.Android.dll, Output=obj\Debug\android\assets\Xamarin.Forms.Platform.Android.dll] Input file is newer than output file.
        [ResolvedUserAssemblies: Input=C:\Users\myuser\.nuget\packages\xamarin.forms\3.0.0.482510\lib\MonoAndroid10\Xamarin.Forms.Platform.dll, Output=obj\Debug\android\assets\Xamarin.Forms.Platform.dll] Input file is newer than output file.
        [ResolvedUserAssemblies: Input=C:\Users\myuser\.nuget\packages\xamarin.forms\3.0.0.482510\lib\MonoAndroid10\Xamarin.Forms.Xaml.dll, Output=obj\Debug\android\assets\Xamarin.Forms.Xaml.dll] Input file is newer than output file.
        LinkAssemblies

Looking at the `LinkAssemblies` MSBuild task, I didn't see anything
that would be setting the timestamps on copied files.

For timestamps to be correct, we should either:
- Use `MonoAndroidHelper.SetLastAccessAndWriteTimeUtc` in C#
- Use the `<Touch />` MSBuild task after using the `<Copy />` MSBuild
  task

This lead me to write a `CheckTimestamps` unit test that does the
following:
- Store `DateTime.UtcNow` in a variable (and subtract one second for
  good measure)
- Build an app
- Make sure nothing in `bin` or `obj` are older than the start time

This uncovered even more out of date files such as:
- `mono.android.jar`
- Assemblies in `obj/Debug/linksrc` or `$(MonoAndroidLinkerInputDir)`

Fixes made in various places:
- `<LinkAssemblies />` task needed to use
  `MonoAndroidHelper.SetLastAccessAndWriteTimeUtc` everywhere
  `MonoAndroidHelper.CopyIfChanged` is used
- The `_CopyIntermediateAssemblies` target appeared to have a typo. It
  was running a `<Touch />` on `ResolvedUserAssemblies` where it
  looked like it should be `ResolvedAssemblies` from the above
  `<Copy />`
- The `_AddStaticResources` target needed a `<Touch />` for
  `mono.android.jar`

This fix should improve our incremental build times dramatically, as
these timestamps caused other targets to run that slow things down.
@dellis1972
Copy link
Contributor

Nice! _LinkAssembliesNoShrink running all the time has bothered me in the past, never had a chance to dig into it though. Good job :)

@dellis1972
Copy link
Contributor

@jonathanpeppers might be useful to know we have a CopyIfChanged Task [1] as well :)
Similar to Copy but does some intelligent stuff. It turns out the Copy task checks timestamps and contents and will Copy if either changed. So even if the timestamps match if the contents are different slightly it will copy. In our case we alter some of the intermediate files and even if we reset the timestamps Copy will still update them :/

[1] https://github.com/xamarin/xamarin-android/blob/master/src/Xamarin.Android.Build.Tasks/Tasks/CopyIfChanged.cs

@dellis1972
Copy link
Contributor

@jonathanpeppers @grendello

So this looks to be the issue where FileInfo.LastModifiedTimeUtc doesn't have Millisecond information running on Mono.

Is that a mono bug ? Should it contains millisecond info. Sounds a bit odd that a Unix system would be less accurate than windows?

@jonathanpeppers
Copy link
Member Author

So this looks to be the issue where FileInfo.LastModifiedTimeUtc doesn't have Millisecond information running on Mono.

I saw this behavior when debugging Xamarin.ProjectTools, there is also some comments like this: https://github.com/xamarin/xamarin-android/blob/62e7792ca52949f2fa213330d292865ad78d2bfa/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Common/XamarinProject.cs#L233-L247

@jonathanpeppers
Copy link
Member Author

The test failure on Windows, I've seen randomly before:

Xamarin.Android.Build.Tests.ResolveSdksTaskTests.ResolveSdkTiming

Task should not take more than 1 second to run.
Expected: less than or equal to 00:00:01
But was: 00:00:01.9140742

It worked for me locally, so maybe this should be checking against 2 seconds?

@dellis1972
Copy link
Contributor

dellis1972 commented May 17, 2018 via email

jonathanpeppers added a commit to jonathanpeppers/Xamarin.Forms that referenced this pull request May 17, 2018
Context: xamarin#2230

The main performance problem with the collection of MSBuild targets in
`Xamarin.Forms.targets` is they don't build incrementally. I addressed
this with `XamlC` using a "stamp" file; however, it is not quite so
easy to setup the same thing with `XamlG`.

They way "incremental" builds are setup in MSBuild, is by specifying
the `Inputs` and `Outputs` of a `<Target />`. MSBuild will partially
build a target when some outputs are not up to date, and skip it
entirely if they are all up to date.

The best docs I can find on MSBuild incremental builds:
https://msdn.microsoft.com/en-us/library/ms171483.aspx

Unfortunately a few things had to happen to make this work for
`XamlG`:
- Define a new target `_FindXamlGFiles` that is invoked before `XamlG`
- `_FindXamlGFiles` defines the `_XamlGInputs` and `_XamlGOutputs`
  `<ItemGroup />`'s
- `_FindXamlGFiles` must also define `<Compile />` and `<FileWrites />`,
  in case the `XamlG` target is skipped
- `XamlGTask` now needs to get passed in a list of `OutputFiles`,
  since we have computed these paths ahead of time
- `XamlGTask` should validate the lengths of `XamlFiles` and
  `OutputFiles` match, used error message from MSBuild proper:
  https://github.com/Microsoft/msbuild/blob/a691a44f0e515e9a03ede8df0bff22185681c8b9/src/Tasks/Copy.cs#L505

`XamlG` now builds incrementally!

To give some context on how much improvement we can see with build
times, consider the following command:

    msbuild Xamarin.Forms.ControlGallery.Android/Xamarin.Forms.ControlGallery.Android.csproj

If you run it once, it will take a while--this change will not improve
the first build. On the second build with the exact same command, it
*should* be much faster.

Before this commit, the second build on my machine takes:

    40.563s

After the change:

    23.692s

`XamlG` has cascading impact on build times when it isn't built
incrementally:
- The C# assembly always changes
- Hence, `XamlC` will always run
- Hence, `GenerateJavaStubs` will always run
- Hence, `javac.exe` and `dx.jar` will always run

I am making other improvements like this in Xamarin.Android itself,
that will further improve these times, such as:
dotnet/android#1693

Other changes:
- I reordered my past `<Output />` element in XamlC to match others in
  this file
- `FilesWrite` is actually supposed to be `FileWrites`, see canonical
  source of how `Clean` works and what `FileWrites` is here:
  dotnet/msbuild#2408 (comment)

Unfortunately, there is more to do to get this PR "feature complete":
- TESTS? This refactoring is complex and has a bit of nuance to it. I
  can't imagine we can get this improvement right (at all), without
  some new testing infrastructure in place that invokes MSBuild.
- There were some checks for `%(TargetPath)` being blank in the C#
  code of `XamlGTask`. I presume this is from the designer and/or
  design-time builds. We probably need to figure out the proper way to
  do this instead of using `%(TargetPath)` at all. After testing the
  current implementation I had all kinds of crazy temporary files in
  my `$(IntermediateOutputPath)`. This probably isn't the right way to
  handle this issue.
- CssG needs the exact same setup, as it was patterned after `XamlG`
jonathanpeppers added a commit to jonathanpeppers/Xamarin.Forms that referenced this pull request May 17, 2018
Context: xamarin#2230

The main performance problem with the collection of MSBuild targets in
`Xamarin.Forms.targets` is they don't build incrementally. I addressed
this with `XamlC` using a "stamp" file; however, it is not quite so
easy to setup the same thing with `XamlG`.

They way "incremental" builds are setup in MSBuild, is by specifying
the `Inputs` and `Outputs` of a `<Target />`. MSBuild will partially
build a target when some outputs are not up to date, and skip it
entirely if they are all up to date.

The best docs I can find on MSBuild incremental builds:
https://msdn.microsoft.com/en-us/library/ms171483.aspx

Unfortunately a few things had to happen to make this work for
`XamlG`:
- Define a new target `_FindXamlGFiles` that is invoked before `XamlG`
- `_FindXamlGFiles` defines the `_XamlGInputs` and `_XamlGOutputs`
  `<ItemGroup />`'s
- `_FindXamlGFiles` must also define `<Compile />` and `<FileWrites />`,
  in case the `XamlG` target is skipped
- `XamlGTask` now needs to get passed in a list of `OutputFiles`,
  since we have computed these paths ahead of time
- `XamlGTask` should validate the lengths of `XamlFiles` and
  `OutputFiles` match, used error message from MSBuild proper:
  https://github.com/Microsoft/msbuild/blob/a691a44f0e515e9a03ede8df0bff22185681c8b9/src/Tasks/Copy.cs#L505

`XamlG` now builds incrementally!

To give some context on how much improvement we can see with build
times, consider the following command:

    msbuild Xamarin.Forms.ControlGallery.Android/Xamarin.Forms.ControlGallery.Android.csproj

If you run it once, it will take a while--this change will not improve
the first build. On the second build with the exact same command, it
*should* be much faster.

Before this commit, the second build on my machine takes:

    40.563s

After the change:

    23.692s

`XamlG` has cascading impact on build times when it isn't built
incrementally:
- The C# assembly always changes
- Hence, `XamlC` will always run
- Hence, `GenerateJavaStubs` will always run
- Hence, `javac.exe` and `dx.jar` will always run

I am making other improvements like this in Xamarin.Android itself,
that will further improve these times, such as:
dotnet/android#1693

Other changes:
- I reordered my past `<Output />` element in XamlC to match others in
  this file
- `FilesWrite` is actually supposed to be `FileWrites`, see canonical
  source of how `Clean` works and what `FileWrites` is here:
  dotnet/msbuild#2408 (comment)

Unfortunately, there is more to do to get this PR "feature complete":
- TESTS? This refactoring is complex and has a bit of nuance to it. I
  can't imagine we can get this improvement right (at all), without
  some new testing infrastructure in place that invokes MSBuild.
- There were some checks for `%(TargetPath)` being blank in the C#
  code of `XamlGTask`. I presume this is from the designer and/or
  design-time builds. We probably need to figure out the proper way to
  do this instead of using `%(TargetPath)` at all. After testing the
  current implementation I had all kinds of crazy temporary files in
  my `$(IntermediateOutputPath)`. This probably isn't the right way to
  handle this issue.
- CssG needs the exact same setup, as it was patterned after `XamlG`
@jonathanpeppers
Copy link
Member Author

To give an idea of better builds times I'm seeing with this change, our Xamarin.Forms-Peformance-Integration project:

  • Build, with no changes: 4513ms -> 2772ms
  • Build, after touching Android resource file: 19420ms -> 18587ms
  • Build, after touching C# file: basically no change

The HelloWorld sample did not see much improvement. So I suspect these fixes will present improvements mainly to larger (or Xamarin.Forms) projects.

@dellis1972
Copy link
Contributor

@jonathanpeppers looks like you broke windows?

@jonathanpeppers
Copy link
Member Author

Yeah it is that 1 test failure for Xamarin.Android.Build.Tests.ResolveSdksTaskTests.ResolveSdkTiming.

Maybe [NonParallelizable] on this test would help?

This test can be a bit flaky, and fail randomly:

    Xamarin.Android.Build.Tests.ResolveSdksTaskTests.ResolveSdkTiming

    Task should not take more than 1 second to run.
    Expected: less than or equal to 00:00:01
    But was: 00:00:01.9140742

To improve this, I changed:
- The timeout to 2 seconds
- Added `[NonParallelizable]`, so the test should not have to compete
  for machine resources as much with other tests
jonathanpeppers added a commit to jonathanpeppers/Xamarin.Forms that referenced this pull request May 18, 2018
Context: xamarin#2230

The main performance problem with the collection of MSBuild targets in
`Xamarin.Forms.targets` is they don't build incrementally. I addressed
this with `XamlC` using a "stamp" file; however, it is not quite so
easy to setup the same thing with `XamlG`.

They way "incremental" builds are setup in MSBuild, is by specifying
the `Inputs` and `Outputs` of a `<Target />`. MSBuild will partially
build a target when some outputs are not up to date, and skip it
entirely if they are all up to date.

The best docs I can find on MSBuild incremental builds:
https://msdn.microsoft.com/en-us/library/ms171483.aspx

Unfortunately a few things had to happen to make this work for
`XamlG`:
- Define a new target `_FindXamlGFiles` that is invoked before `XamlG`
- `_FindXamlGFiles` defines the `_XamlGInputs` and `_XamlGOutputs`
  `<ItemGroup />`'s
- `_FindXamlGFiles` must also define `<Compile />` and `<FileWrites />`,
  in case the `XamlG` target is skipped
- `XamlGTask` now needs to get passed in a list of `OutputFiles`,
  since we have computed these paths ahead of time
- `XamlGTask` should validate the lengths of `XamlFiles` and
  `OutputFiles` match, used error message from MSBuild proper:
  https://github.com/Microsoft/msbuild/blob/a691a44f0e515e9a03ede8df0bff22185681c8b9/src/Tasks/Copy.cs#L505

`XamlG` now builds incrementally!

To give some context on how much improvement we can see with build
times, consider the following command:

    msbuild Xamarin.Forms.ControlGallery.Android/Xamarin.Forms.ControlGallery.Android.csproj

If you run it once, it will take a while--this change will not improve
the first build. On the second build with the exact same command, it
*should* be much faster.

Before this commit, the second build on my machine takes:

    40.563s

After the change:

    23.692s

`XamlG` has cascading impact on build times when it isn't built
incrementally:
- The C# assembly always changes
- Hence, `XamlC` will always run
- Hence, `GenerateJavaStubs` will always run
- Hence, `javac.exe` and `dx.jar` will always run

I am making other improvements like this in Xamarin.Android itself,
that will further improve these times, such as:
dotnet/android#1693

~~ New MSBuild Integration Tests ~~

Added some basic MSBuild testing infrastructure:
- Tests write project files to `bin/Debug/temp/TestName`
- Each test has an `sdkStyle` flag for testing the new project system
  versus the old one
- `[TearDown]` deletes the entire directory, with a retry for
  `IOException` on Windows
- Used the `Microsoft.Build.Locator` NuGet package for locating
  `MSBuild.exe` on Windows
- These tests take 2-5 seconds each

So for example, the simplest test, `BuildAProject` writes to
`Xamarin.Forms.Xaml.UnitTests\bin\Debug\temp\BuildAProject(False)\test.csproj`:

    <?xml version="1.0" encoding="utf-8"?>
    <Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
      <PropertyGroup>
        <Configuration>Debug</Configuration>
        <Platform>AnyCPU</Platform>
        <OutputType>Library</OutputType>
        <OutputPath>bin\Debug</OutputPath>
        <TargetFrameworkVersion>v4.7</TargetFrameworkVersion>
      </PropertyGroup>
      <ItemGroup>
        <Reference Include="mscorlib" />
        <Reference Include="System" />
        <Reference Include="Xamarin.Forms.Core.dll">
          <HintPath>..\..\Xamarin.Forms.Core.dll</HintPath>
        </Reference>
        <Reference Include="Xamarin.Forms.Xaml.dll">
          <HintPath>..\..\Xamarin.Forms.Xaml.dll</HintPath>
        </Reference>
      </ItemGroup>
      <ItemGroup>
        <Compile Include="AssemblyInfo.cs" />
      </ItemGroup>
      <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
      <Import Project="..\..\..\..\..\.nuspec\Xamarin.Forms.targets" />
      <ItemGroup>
        <EmbeddedResource Include="MainPage.xaml" />
      </ItemGroup>
    </Project>

Invokes `msbuild`, and checks the intermediate output for files being
generated.

Tested scenarios:
- Build a simple project
- Build, then build again, and make sure targets were skipped
- Build, then clean, make sure files are gone
- Build, with linked files
- Design-time build
- Call `UpdateDesignTimeXaml` directly
- Build, add a new file, build again
- Build, update timestamp on a file, build again
- XAML file with random XML content
- XAML file with invalid XML content
- A general `EmbeddedResource` that shouldn't go through XamlG

Adding these tests found a bug! `IncrementalClean` was deleting
`XamlC.stamp`. I fixed this by using `<ItemGroup />`, which will be
propery evaluated even if the target is skipped.

~~ Other Changes ~~

- `FilesWrite` is actually supposed to be `FileWrites`, see canonical
  source of how `Clean` works and what `FileWrites` is here:
  dotnet/msbuild#2408 (comment)
- Moved `DummyBuildEngine` into `MSBuild` directory--makes sense?
  maybe don't need to?
- Added a `XamlGDifferentInputOutputLengths` test to check the error
  message
- Expanded `DummyBuildEngine` so you can assert against log messages
- Changed a setting in `.Xamarin.Forms.Android.sln` so the unit test
  project is built
- My VS IDE monkeyed with a few files, and I kept any *good* (or
  relevant) changes: `Xamarin.Forms.UnitTests.csproj`,
  `Xamarin.Forms.Xaml.UnitTests\app.config`, etc.

There were some checks for `%(TargetPath)` being blank in the C# code
of `XamlGTask`. In that case it was using `Path.GetRandomFileName`,
but we can't do this if we are setting up inputs and outputs for
`XamlG`. I presume this is from the designer and/or design-time builds
before `DependsOnTargets="PrepareResourceNames"` was added. I tested
design-time builds in VS on Windows, and `$(TargetPath)` was set. To
be sure we don't break anything here, I exclude inputs to `XamlG` if
`%(TargetPath)` is somehow blank.

See relevant MSBuild code for `%(TargetPath)` here:

https://github.com/Microsoft/msbuild/blob/05151780901c38b4613b2f236ab8b091349dbe94/src/Tasks/Microsoft.Common.CurrentVersion.targets#L2822

~~ Future changes ~~

CssG needs the exact same setup, as it was patterned after `XamlG`.
This should probably be done in a future PR.
jonathanpeppers added a commit to jonathanpeppers/Xamarin.Forms that referenced this pull request May 18, 2018
Context: xamarin#2230

The main performance problem with the collection of MSBuild targets in
`Xamarin.Forms.targets` is they don't build incrementally. I addressed
this with `XamlC` using a "stamp" file; however, it is not quite so
easy to setup the same thing with `XamlG`.

They way "incremental" builds are setup in MSBuild, is by specifying
the `Inputs` and `Outputs` of a `<Target />`. MSBuild will partially
build a target when some outputs are not up to date, and skip it
entirely if they are all up to date.

The best docs I can find on MSBuild incremental builds:
https://msdn.microsoft.com/en-us/library/ms171483.aspx

Unfortunately a few things had to happen to make this work for
`XamlG`:
- Define a new target `_FindXamlGFiles` that is invoked before `XamlG`
- `_FindXamlGFiles` defines the `_XamlGInputs` and `_XamlGOutputs`
  `<ItemGroup />`'s
- `_FindXamlGFiles` must also define `<Compile />` and `<FileWrites />`,
  in case the `XamlG` target is skipped
- `XamlGTask` now needs to get passed in a list of `OutputFiles`,
  since we have computed these paths ahead of time
- `XamlGTask` should validate the lengths of `XamlFiles` and
  `OutputFiles` match, used error message from MSBuild proper:
  https://github.com/Microsoft/msbuild/blob/a691a44f0e515e9a03ede8df0bff22185681c8b9/src/Tasks/Copy.cs#L505

`XamlG` now builds incrementally!

To give some context on how much improvement we can see with build
times, consider the following command:

    msbuild Xamarin.Forms.ControlGallery.Android/Xamarin.Forms.ControlGallery.Android.csproj

If you run it once, it will take a while--this change will not improve
the first build. On the second build with the exact same command, it
*should* be much faster.

Before this commit, the second build on my machine takes:

    40.563s

After the change:

    23.692s

`XamlG` has cascading impact on build times when it isn't built
incrementally:
- The C# assembly always changes
- Hence, `XamlC` will always run
- Hence, `GenerateJavaStubs` will always run
- Hence, `javac.exe` and `dx.jar` will always run

I am making other improvements like this in Xamarin.Android itself,
that will further improve these times, such as:
dotnet/android#1693

~~ New MSBuild Integration Tests ~~

Added some basic MSBuild testing infrastructure:
- Tests write project files to `bin/Debug/temp/TestName`
- Each test has an `sdkStyle` flag for testing the new project system
  versus the old one
- `[TearDown]` deletes the entire directory, with a retry for
  `IOException` on Windows
- Used the `Microsoft.Build.Locator` NuGet package for locating
  `MSBuild.exe` on Windows
- These tests take 2-5 seconds each

So for example, the simplest test, `BuildAProject` writes to
`Xamarin.Forms.Xaml.UnitTests\bin\Debug\temp\BuildAProject(False)\test.csproj`:

    <?xml version="1.0" encoding="utf-8"?>
    <Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
      <PropertyGroup>
        <Configuration>Debug</Configuration>
        <Platform>AnyCPU</Platform>
        <OutputType>Library</OutputType>
        <OutputPath>bin\Debug</OutputPath>
        <TargetFrameworkVersion>v4.7</TargetFrameworkVersion>
      </PropertyGroup>
      <ItemGroup>
        <Reference Include="mscorlib" />
        <Reference Include="System" />
        <Reference Include="Xamarin.Forms.Core.dll">
          <HintPath>..\..\Xamarin.Forms.Core.dll</HintPath>
        </Reference>
        <Reference Include="Xamarin.Forms.Xaml.dll">
          <HintPath>..\..\Xamarin.Forms.Xaml.dll</HintPath>
        </Reference>
      </ItemGroup>
      <ItemGroup>
        <Compile Include="AssemblyInfo.cs" />
      </ItemGroup>
      <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
      <Import Project="..\..\..\..\..\.nuspec\Xamarin.Forms.targets" />
      <ItemGroup>
        <EmbeddedResource Include="MainPage.xaml" />
      </ItemGroup>
    </Project>

Invokes `msbuild`, and checks the intermediate output for files being
generated.

Tested scenarios:
- Build a simple project
- Build, then build again, and make sure targets were skipped
- Build, then clean, make sure files are gone
- Build, with linked files
- Design-time build
- Call `UpdateDesignTimeXaml` directly
- Build, add a new file, build again
- Build, update timestamp on a file, build again
- XAML file with random XML content
- XAML file with invalid XML content
- A general `EmbeddedResource` that shouldn't go through XamlG

Adding these tests found a bug! `IncrementalClean` was deleting
`XamlC.stamp`. I fixed this by using `<ItemGroup />`, which will be
propery evaluated even if the target is skipped.

~~ Other Changes ~~

- `FilesWrite` is actually supposed to be `FileWrites`, see canonical
  source of how `Clean` works and what `FileWrites` is here:
  dotnet/msbuild#2408 (comment)
- Moved `DummyBuildEngine` into `MSBuild` directory--makes sense?
  maybe don't need to?
- Added a `XamlGDifferentInputOutputLengths` test to check the error
  message
- Expanded `DummyBuildEngine` so you can assert against log messages
- Changed a setting in `.Xamarin.Forms.Android.sln` so the unit test
  project is built
- My VS IDE monkeyed with a few files, and I kept any *good* (or
  relevant) changes: `Xamarin.Forms.UnitTests.csproj`,
  `Xamarin.Forms.Xaml.UnitTests\app.config`, etc.

There were some checks for `%(TargetPath)` being blank in the C# code
of `XamlGTask`. In that case it was using `Path.GetRandomFileName`,
but we can't do this if we are setting up inputs and outputs for
`XamlG`. I presume this is from the designer and/or design-time builds
before `DependsOnTargets="PrepareResourceNames"` was added. I tested
design-time builds in VS on Windows, and `$(TargetPath)` was set. To
be sure we don't break anything here, I exclude inputs to `XamlG` if
`%(TargetPath)` is somehow blank.

See relevant MSBuild code for `%(TargetPath)` here:

https://github.com/Microsoft/msbuild/blob/05151780901c38b4613b2f236ab8b091349dbe94/src/Tasks/Microsoft.Common.CurrentVersion.targets#L2822

~~ Future changes ~~

CssG needs the exact same setup, as it was patterned after `XamlG`.
This should probably be done in a future PR.
@jonpryor jonpryor merged commit d7e3a23 into dotnet:master May 18, 2018
jassmith pushed a commit to xamarin/Xamarin.Forms that referenced this pull request May 24, 2018
Context: #2230

The main performance problem with the collection of MSBuild targets in
`Xamarin.Forms.targets` is they don't build incrementally. I addressed
this with `XamlC` using a "stamp" file; however, it is not quite so
easy to setup the same thing with `XamlG`.

They way "incremental" builds are setup in MSBuild, is by specifying
the `Inputs` and `Outputs` of a `<Target />`. MSBuild will partially
build a target when some outputs are not up to date, and skip it
entirely if they are all up to date.

The best docs I can find on MSBuild incremental builds:
https://msdn.microsoft.com/en-us/library/ms171483.aspx

Unfortunately a few things had to happen to make this work for
`XamlG`:
- Define a new target `_FindXamlGFiles` that is invoked before `XamlG`
- `_FindXamlGFiles` defines the `_XamlGInputs` and `_XamlGOutputs`
  `<ItemGroup />`'s
- `_FindXamlGFiles` must also define `<Compile />` and `<FileWrites />`,
  in case the `XamlG` target is skipped
- `XamlGTask` now needs to get passed in a list of `OutputFiles`,
  since we have computed these paths ahead of time
- `XamlGTask` should validate the lengths of `XamlFiles` and
  `OutputFiles` match, used error message from MSBuild proper:
  https://github.com/Microsoft/msbuild/blob/a691a44f0e515e9a03ede8df0bff22185681c8b9/src/Tasks/Copy.cs#L505

`XamlG` now builds incrementally!

To give some context on how much improvement we can see with build
times, consider the following command:

    msbuild Xamarin.Forms.ControlGallery.Android/Xamarin.Forms.ControlGallery.Android.csproj

If you run it once, it will take a while--this change will not improve
the first build. On the second build with the exact same command, it
*should* be much faster.

Before this commit, the second build on my machine takes:

    40.563s

After the change:

    23.692s

`XamlG` has cascading impact on build times when it isn't built
incrementally:
- The C# assembly always changes
- Hence, `XamlC` will always run
- Hence, `GenerateJavaStubs` will always run
- Hence, `javac.exe` and `dx.jar` will always run

I am making other improvements like this in Xamarin.Android itself,
that will further improve these times, such as:
dotnet/android#1693

~~ New MSBuild Integration Tests ~~

Added some basic MSBuild testing infrastructure:
- Tests write project files to `bin/Debug/temp/TestName`
- Each test has an `sdkStyle` flag for testing the new project system
  versus the old one
- `[TearDown]` deletes the entire directory, with a retry for
  `IOException` on Windows
- Used the `Microsoft.Build.Locator` NuGet package for locating
  `MSBuild.exe` on Windows
- These tests take 2-5 seconds each

So for example, the simplest test, `BuildAProject` writes to
`Xamarin.Forms.Xaml.UnitTests\bin\Debug\temp\BuildAProject(False)\test.csproj`:

    <?xml version="1.0" encoding="utf-8"?>
    <Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
      <PropertyGroup>
        <Configuration>Debug</Configuration>
        <Platform>AnyCPU</Platform>
        <OutputType>Library</OutputType>
        <OutputPath>bin\Debug</OutputPath>
        <TargetFrameworkVersion>v4.7</TargetFrameworkVersion>
      </PropertyGroup>
      <ItemGroup>
        <Reference Include="mscorlib" />
        <Reference Include="System" />
        <Reference Include="Xamarin.Forms.Core.dll">
          <HintPath>..\..\Xamarin.Forms.Core.dll</HintPath>
        </Reference>
        <Reference Include="Xamarin.Forms.Xaml.dll">
          <HintPath>..\..\Xamarin.Forms.Xaml.dll</HintPath>
        </Reference>
      </ItemGroup>
      <ItemGroup>
        <Compile Include="AssemblyInfo.cs" />
      </ItemGroup>
      <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
      <Import Project="..\..\..\..\..\.nuspec\Xamarin.Forms.targets" />
      <ItemGroup>
        <EmbeddedResource Include="MainPage.xaml" />
      </ItemGroup>
    </Project>

Invokes `msbuild`, and checks the intermediate output for files being
generated.

Tested scenarios:
- Build a simple project
- Build, then build again, and make sure targets were skipped
- Build, then clean, make sure files are gone
- Build, with linked files
- Design-time build
- Call `UpdateDesignTimeXaml` directly
- Build, add a new file, build again
- Build, update timestamp on a file, build again
- XAML file with random XML content
- XAML file with invalid XML content
- A general `EmbeddedResource` that shouldn't go through XamlG

Adding these tests found a bug! `IncrementalClean` was deleting
`XamlC.stamp`. I fixed this by using `<ItemGroup />`, which will be
propery evaluated even if the target is skipped.

~~ Other Changes ~~

- `FilesWrite` is actually supposed to be `FileWrites`, see canonical
  source of how `Clean` works and what `FileWrites` is here:
  dotnet/msbuild#2408 (comment)
- Moved `DummyBuildEngine` into `MSBuild` directory--makes sense?
  maybe don't need to?
- Added a `XamlGDifferentInputOutputLengths` test to check the error
  message
- Expanded `DummyBuildEngine` so you can assert against log messages
- Changed a setting in `.Xamarin.Forms.Android.sln` so the unit test
  project is built
- My VS IDE monkeyed with a few files, and I kept any *good* (or
  relevant) changes: `Xamarin.Forms.UnitTests.csproj`,
  `Xamarin.Forms.Xaml.UnitTests\app.config`, etc.

There were some checks for `%(TargetPath)` being blank in the C# code
of `XamlGTask`. In that case it was using `Path.GetRandomFileName`,
but we can't do this if we are setting up inputs and outputs for
`XamlG`. I presume this is from the designer and/or design-time builds
before `DependsOnTargets="PrepareResourceNames"` was added. I tested
design-time builds in VS on Windows, and `$(TargetPath)` was set. To
be sure we don't break anything here, I exclude inputs to `XamlG` if
`%(TargetPath)` is somehow blank.

See relevant MSBuild code for `%(TargetPath)` here:

https://github.com/Microsoft/msbuild/blob/05151780901c38b4613b2f236ab8b091349dbe94/src/Tasks/Microsoft.Common.CurrentVersion.targets#L2822

~~ Future changes ~~

CssG needs the exact same setup, as it was patterned after `XamlG`.
This should probably be done in a future PR.
jonathanpeppers added a commit to xamarin/Xamarin.Forms that referenced this pull request May 24, 2018
Context: #2230

The main performance problem with the collection of MSBuild targets in
`Xamarin.Forms.targets` is they don't build incrementally. I addressed
this with `XamlC` using a "stamp" file; however, it is not quite so
easy to setup the same thing with `XamlG`.

They way "incremental" builds are setup in MSBuild, is by specifying
the `Inputs` and `Outputs` of a `<Target />`. MSBuild will partially
build a target when some outputs are not up to date, and skip it
entirely if they are all up to date.

The best docs I can find on MSBuild incremental builds:
https://msdn.microsoft.com/en-us/library/ms171483.aspx

Unfortunately a few things had to happen to make this work for
`XamlG`:
- Define a new target `_FindXamlGFiles` that is invoked before `XamlG`
- `_FindXamlGFiles` defines the `_XamlGInputs` and `_XamlGOutputs`
  `<ItemGroup />`'s
- `_FindXamlGFiles` must also define `<Compile />` and `<FileWrites />`,
  in case the `XamlG` target is skipped
- `XamlGTask` now needs to get passed in a list of `OutputFiles`,
  since we have computed these paths ahead of time
- `XamlGTask` should validate the lengths of `XamlFiles` and
  `OutputFiles` match, used error message from MSBuild proper:
  https://github.com/Microsoft/msbuild/blob/a691a44f0e515e9a03ede8df0bff22185681c8b9/src/Tasks/Copy.cs#L505

`XamlG` now builds incrementally!

To give some context on how much improvement we can see with build
times, consider the following command:

    msbuild Xamarin.Forms.ControlGallery.Android/Xamarin.Forms.ControlGallery.Android.csproj

If you run it once, it will take a while--this change will not improve
the first build. On the second build with the exact same command, it
*should* be much faster.

Before this commit, the second build on my machine takes:

    40.563s

After the change:

    23.692s

`XamlG` has cascading impact on build times when it isn't built
incrementally:
- The C# assembly always changes
- Hence, `XamlC` will always run
- Hence, `GenerateJavaStubs` will always run
- Hence, `javac.exe` and `dx.jar` will always run

I am making other improvements like this in Xamarin.Android itself,
that will further improve these times, such as:
dotnet/android#1693

~~ New MSBuild Integration Tests ~~

Added some basic MSBuild testing infrastructure:
- Tests write project files to `bin/Debug/temp/TestName`
- Each test has an `sdkStyle` flag for testing the new project system
  versus the old one
- `[TearDown]` deletes the entire directory, with a retry for
  `IOException` on Windows
- Used the `Microsoft.Build.Locator` NuGet package for locating
  `MSBuild.exe` on Windows
- These tests take 2-5 seconds each

So for example, the simplest test, `BuildAProject` writes to
`Xamarin.Forms.Xaml.UnitTests\bin\Debug\temp\BuildAProject(False)\test.csproj`:

    <?xml version="1.0" encoding="utf-8"?>
    <Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
      <PropertyGroup>
        <Configuration>Debug</Configuration>
        <Platform>AnyCPU</Platform>
        <OutputType>Library</OutputType>
        <OutputPath>bin\Debug</OutputPath>
        <TargetFrameworkVersion>v4.7</TargetFrameworkVersion>
      </PropertyGroup>
      <ItemGroup>
        <Reference Include="mscorlib" />
        <Reference Include="System" />
        <Reference Include="Xamarin.Forms.Core.dll">
          <HintPath>..\..\Xamarin.Forms.Core.dll</HintPath>
        </Reference>
        <Reference Include="Xamarin.Forms.Xaml.dll">
          <HintPath>..\..\Xamarin.Forms.Xaml.dll</HintPath>
        </Reference>
      </ItemGroup>
      <ItemGroup>
        <Compile Include="AssemblyInfo.cs" />
      </ItemGroup>
      <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
      <Import Project="..\..\..\..\..\.nuspec\Xamarin.Forms.targets" />
      <ItemGroup>
        <EmbeddedResource Include="MainPage.xaml" />
      </ItemGroup>
    </Project>

Invokes `msbuild`, and checks the intermediate output for files being
generated.

Tested scenarios:
- Build a simple project
- Build, then build again, and make sure targets were skipped
- Build, then clean, make sure files are gone
- Build, with linked files
- Design-time build
- Call `UpdateDesignTimeXaml` directly
- Build, add a new file, build again
- Build, update timestamp on a file, build again
- XAML file with random XML content
- XAML file with invalid XML content
- A general `EmbeddedResource` that shouldn't go through XamlG

Adding these tests found a bug! `IncrementalClean` was deleting
`XamlC.stamp`. I fixed this by using `<ItemGroup />`, which will be
propery evaluated even if the target is skipped.

~~ Other Changes ~~

- `FilesWrite` is actually supposed to be `FileWrites`, see canonical
  source of how `Clean` works and what `FileWrites` is here:
  dotnet/msbuild#2408 (comment)
- Moved `DummyBuildEngine` into `MSBuild` directory--makes sense?
  maybe don't need to?
- Added a `XamlGDifferentInputOutputLengths` test to check the error
  message
- Expanded `DummyBuildEngine` so you can assert against log messages
- Changed a setting in `.Xamarin.Forms.Android.sln` so the unit test
  project is built
- My VS IDE monkeyed with a few files, and I kept any *good* (or
  relevant) changes: `Xamarin.Forms.UnitTests.csproj`,
  `Xamarin.Forms.Xaml.UnitTests\app.config`, etc.

There were some checks for `%(TargetPath)` being blank in the C# code
of `XamlGTask`. In that case it was using `Path.GetRandomFileName`,
but we can't do this if we are setting up inputs and outputs for
`XamlG`. I presume this is from the designer and/or design-time builds
before `DependsOnTargets="PrepareResourceNames"` was added. I tested
design-time builds in VS on Windows, and `$(TargetPath)` was set. To
be sure we don't break anything here, I exclude inputs to `XamlG` if
`%(TargetPath)` is somehow blank.

See relevant MSBuild code for `%(TargetPath)` here:

https://github.com/Microsoft/msbuild/blob/05151780901c38b4613b2f236ab8b091349dbe94/src/Tasks/Microsoft.Common.CurrentVersion.targets#L2822

~~ Future changes ~~

CssG needs the exact same setup, as it was patterned after `XamlG`.
This should probably be done in a future PR.
rmarinho pushed a commit to xamarin/Xamarin.Forms that referenced this pull request May 25, 2018
* [XamlG] builds incrementally, add MSBuild integration tests

Context: #2230

The main performance problem with the collection of MSBuild targets in
`Xamarin.Forms.targets` is they don't build incrementally. I addressed
this with `XamlC` using a "stamp" file; however, it is not quite so
easy to setup the same thing with `XamlG`.

They way "incremental" builds are setup in MSBuild, is by specifying
the `Inputs` and `Outputs` of a `<Target />`. MSBuild will partially
build a target when some outputs are not up to date, and skip it
entirely if they are all up to date.

The best docs I can find on MSBuild incremental builds:
https://msdn.microsoft.com/en-us/library/ms171483.aspx

Unfortunately a few things had to happen to make this work for
`XamlG`:
- Define a new target `_FindXamlGFiles` that is invoked before `XamlG`
- `_FindXamlGFiles` defines the `_XamlGInputs` and `_XamlGOutputs`
  `<ItemGroup />`'s
- `_FindXamlGFiles` must also define `<Compile />` and `<FileWrites />`,
  in case the `XamlG` target is skipped
- `XamlGTask` now needs to get passed in a list of `OutputFiles`,
  since we have computed these paths ahead of time
- `XamlGTask` should validate the lengths of `XamlFiles` and
  `OutputFiles` match, used error message from MSBuild proper:
  https://github.com/Microsoft/msbuild/blob/a691a44f0e515e9a03ede8df0bff22185681c8b9/src/Tasks/Copy.cs#L505

`XamlG` now builds incrementally!

To give some context on how much improvement we can see with build
times, consider the following command:

    msbuild Xamarin.Forms.ControlGallery.Android/Xamarin.Forms.ControlGallery.Android.csproj

If you run it once, it will take a while--this change will not improve
the first build. On the second build with the exact same command, it
*should* be much faster.

Before this commit, the second build on my machine takes:

    40.563s

After the change:

    23.692s

`XamlG` has cascading impact on build times when it isn't built
incrementally:
- The C# assembly always changes
- Hence, `XamlC` will always run
- Hence, `GenerateJavaStubs` will always run
- Hence, `javac.exe` and `dx.jar` will always run

I am making other improvements like this in Xamarin.Android itself,
that will further improve these times, such as:
dotnet/android#1693

~~ New MSBuild Integration Tests ~~

Added some basic MSBuild testing infrastructure:
- Tests write project files to `bin/Debug/temp/TestName`
- Each test has an `sdkStyle` flag for testing the new project system
  versus the old one
- `[TearDown]` deletes the entire directory, with a retry for
  `IOException` on Windows
- Used the `Microsoft.Build.Locator` NuGet package for locating
  `MSBuild.exe` on Windows
- These tests take 2-5 seconds each

So for example, the simplest test, `BuildAProject` writes to
`Xamarin.Forms.Xaml.UnitTests\bin\Debug\temp\BuildAProject(False)\test.csproj`:

    <?xml version="1.0" encoding="utf-8"?>
    <Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
      <PropertyGroup>
        <Configuration>Debug</Configuration>
        <Platform>AnyCPU</Platform>
        <OutputType>Library</OutputType>
        <OutputPath>bin\Debug</OutputPath>
        <TargetFrameworkVersion>v4.7</TargetFrameworkVersion>
      </PropertyGroup>
      <ItemGroup>
        <Reference Include="mscorlib" />
        <Reference Include="System" />
        <Reference Include="Xamarin.Forms.Core.dll">
          <HintPath>..\..\Xamarin.Forms.Core.dll</HintPath>
        </Reference>
        <Reference Include="Xamarin.Forms.Xaml.dll">
          <HintPath>..\..\Xamarin.Forms.Xaml.dll</HintPath>
        </Reference>
      </ItemGroup>
      <ItemGroup>
        <Compile Include="AssemblyInfo.cs" />
      </ItemGroup>
      <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
      <Import Project="..\..\..\..\..\.nuspec\Xamarin.Forms.targets" />
      <ItemGroup>
        <EmbeddedResource Include="MainPage.xaml" />
      </ItemGroup>
    </Project>

Invokes `msbuild`, and checks the intermediate output for files being
generated.

Tested scenarios:
- Build a simple project
- Build, then build again, and make sure targets were skipped
- Build, then clean, make sure files are gone
- Build, with linked files
- Design-time build
- Call `UpdateDesignTimeXaml` directly
- Build, add a new file, build again
- Build, update timestamp on a file, build again
- XAML file with random XML content
- XAML file with invalid XML content
- A general `EmbeddedResource` that shouldn't go through XamlG

Adding these tests found a bug! `IncrementalClean` was deleting
`XamlC.stamp`. I fixed this by using `<ItemGroup />`, which will be
propery evaluated even if the target is skipped.

~~ Other Changes ~~

- `FilesWrite` is actually supposed to be `FileWrites`, see canonical
  source of how `Clean` works and what `FileWrites` is here:
  dotnet/msbuild#2408 (comment)
- Moved `DummyBuildEngine` into `MSBuild` directory--makes sense?
  maybe don't need to?
- Added a `XamlGDifferentInputOutputLengths` test to check the error
  message
- Expanded `DummyBuildEngine` so you can assert against log messages
- Changed a setting in `.Xamarin.Forms.Android.sln` so the unit test
  project is built
- My VS IDE monkeyed with a few files, and I kept any *good* (or
  relevant) changes: `Xamarin.Forms.UnitTests.csproj`,
  `Xamarin.Forms.Xaml.UnitTests\app.config`, etc.

There were some checks for `%(TargetPath)` being blank in the C# code
of `XamlGTask`. In that case it was using `Path.GetRandomFileName`,
but we can't do this if we are setting up inputs and outputs for
`XamlG`. I presume this is from the designer and/or design-time builds
before `DependsOnTargets="PrepareResourceNames"` was added. I tested
design-time builds in VS on Windows, and `$(TargetPath)` was set. To
be sure we don't break anything here, I exclude inputs to `XamlG` if
`%(TargetPath)` is somehow blank.

See relevant MSBuild code for `%(TargetPath)` here:

https://github.com/Microsoft/msbuild/blob/05151780901c38b4613b2f236ab8b091349dbe94/src/Tasks/Microsoft.Common.CurrentVersion.targets#L2822

~~ Future changes ~~

CssG needs the exact same setup, as it was patterned after `XamlG`.
This should probably be done in a future PR.

* [msbuild] improved lookup of Xamarin.Forms.targets in integration tests

Context: https://devdiv.visualstudio.com/DevDiv/_build?buildId=1717939
Context: https://devdiv.visualstudio.com/DevDiv/_build?buildId=1718306

It looks like the VSTS builds for release branches are running tests
in a staging directory. This means we can't reliably import
`Xamarin.Forms.targets` as what was working locally in the
Xamarin.Forms source tree.

So to fix this:
- Look for `.nuspec/Xamarin.Forms.targets`, at the default location
  and then using the `BUILD_SOURCESDIRECTORY` environment variable as
  a fallback
- Copy all `*.targets` files to the test directory
- Our `*.csproj` files under test can import the file from there.

We have to copy the targets files here to be sure that MSBuild can
load `Xamarin.Forms.Build.Tasks.dll`, which is also referenced by the
unit tests.

I also made the tests abort earlier if they can't find
`Xamarin.Forms.targets`.
@jonathanpeppers jonathanpeppers deleted the facepalm-timestamps branch December 14, 2018 16:41
@github-actions github-actions bot locked and limited conversation to collaborators Feb 2, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants