This repository has been archived by the owner on May 1, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 1.9k
[msbuild] setup inputs and outputs for XamlC target #2230
Merged
Merged
Conversation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
dellis1972
approved these changes
Apr 3, 2018
samhouts
added
the
t/housekeeping ♻︎
Internal only changes, won't be included in release notes
label
Apr 4, 2018
jonathanpeppers
commented
Apr 5, 2018
.nuspec/Xamarin.Forms.targets
Outdated
<PropertyGroup> | ||
<_XamlCAlreadyExecuted>true</_XamlCAlreadyExecuted> | ||
</PropertyGroup> | ||
<Target Name="XamlC" Inputs="$(IntermediateOutputPath)$(TargetFileName)" Outputs="$(IntermediateOutputPath)XamlC.stamp"> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see a mistake, AfterTargets
got removed. I'll put that back
jonathanpeppers
force-pushed
the
xamlc-target
branch
from
April 5, 2018 16:42
d576545
to
b5072d1
Compare
if XamlC is ran on an already processed assembly, it mainly no-op (as the xaml files are removed from the resources), but every ms is good to shave. @jassmith @bholmes @davidortinau: shouldn't this be targeted to |
build |
StephaneDelcroix
approved these changes
Apr 11, 2018
Context: https://github.com/jonathanpeppers/XamarinAndroidBuildTimes After doing a profiling/review of Xamarin.Android project build times, I noticed that the `XamlC` target was running on every build no matter what. The project I used was the Forms Master/Detail template from VS 2017 15.6.4. In my test repo, I was timing the following situations: 1. Completely clean/fresh build 2. Build again, no changes 3. Change C#, build again 4. Change `AndroidResource` XML, build again In all cases `XamlC` was running, due to the task not having setup proper inputs and outputs for the MSBuild target. Thinking about it, it seemed like we could skip `XamlC` as long as the input assembly did not change, such as case no. 2 or no. 4. Changes to the `XamlC` target: - Setup `$(IntermediateOutputPath)$(TargetFileName)` (the assembly) as input - Setup a `XamlC.stamp` file as an output - `<Touch />` the `XamlC.stamp` file after running the `XamlCTask` - Add to the `FileWrites` MSBuild item, so that the `IncrementalClean` target doesn't delete the stamp file On my Windows machine, this improved the following build times for cases: 1. same (XamlC should run) 2. 3.685s -> 2.887s 3. same (XamlC should run) (would also be same as XAML changing) 4. 12.126s -> 11.214s Since this was basically an empty project, I suspect the improvements would be more drastic for apps with lots of XAML and using `XamlC`.
jonathanpeppers
force-pushed
the
xamlc-target
branch
from
April 11, 2018 15:33
b5072d1
to
65fbdf3
Compare
jassmith
approved these changes
Apr 11, 2018
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! 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, 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
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`
4 tasks
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.
jassmith
pushed a commit
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.
4 tasks
jonathanpeppers
added a commit
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
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`.
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Description of Change
Context: https://github.com/jonathanpeppers/XamarinAndroidBuildTimes
After doing a profiling/review of Xamarin.Android project build times, I
noticed that the
XamlC
target was running on every build no matterwhat. The project I used was the Forms Master/Detail template from VS
2017 15.6.4.
In my test repo, I was timing the following situations:
AndroidResource
XML, build againIn all cases
XamlC
was running, due to the task not having setupproper inputs and outputs for the MSBuild target. Thinking about it, it
seemed like we could skip
XamlC
as long as the input assembly did notchange, such as case no. 2 or no. 4.
Changes to the
XamlC
target:$(IntermediateOutputPath)$(TargetFileName)
(the assembly) as inputXamlC.stamp
file as an output<Touch />
theXamlC.stamp
file after running theXamlCTask
FileWrites
MSBuild item, so that theIncrementalClean
target doesn't delete the stamp file
_XamlCAlreadyExecuted
property as it should not beneeded after adding inputs and outputs
On my Windows machine, this improved the following build times for cases:
Since this was basically an empty project, I suspect the improvements
would be more drastic for apps with lots of XAML and using
XamlC
.Tests?
I didn't see any project where MSBuild-related tests live. Might be something needed? not sure how many MSBuild-related bugs are happening for XF.
Instead I tested the change by modifying
~\.nuget\packages\xamarin.forms\2.5.0.280555\build\netstandard1.0\Xamarin.Forms.targets
and saving MSBuild binary logs. Download here.You can find the before logs here.
Bugs Fixed
n/a
API Changes
n/a
Behavioral Changes
XamlC
should only run during a build if the input assembly has changed.PR Checklist