- 
                Notifications
    
You must be signed in to change notification settings  - Fork 1.9k
 
[XamlG] builds incrementally, add MSBuild integration tests #2825
Conversation
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.
looks fine.
back porting the unit tests wasn't necessary
| 
           I understand this is very critical, and should move fast. but as 3.0 is in -sr mode, we're likely to reach to more users by targeting to 3.1.0 and issue a new -pre  | 
    
| 
           Hmm, it's actually good these MSBuild tests are here because they all failed... I see something that got lost from: #2230 I'll just push changes here to fix this up.  | 
    
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.
55ccd6c    to
    760c358      
    Compare
  
    | 
           If you guys do fast-track this, the only thing I would recommend is to get some real QA using this in IDEs. I recently discovered VS for Mac has some custom code in this area, and we don't want to break them if they are relying on the past "always-build" behavior: https://github.com/xamarin/md-addins/blob/master/Xamarin.Forms.Addin/Xamarin.Forms.Addin/CSharpProjector.cs  | 
    
| 
           It will be put out as a -pre for the SR first (odd I know) so we have some decent testing time  | 
    
| 
           @jassmith not weird at all... it doesn't matter how many bugs get fixed, or how many "cool" new features are added if nobody can build, or if there are poor IDE experiences. I know I've been seeing some strange new behavior in VS Mac 7.5 when it comes to intellisense with generated files (generally speaking not just specific to XF).  | 
    
| 
           Weird, why are the paths different when these tests run: It looks like the unit tests have been copied to a new directory. @rmarinho ? I can make them check two places for   | 
    
| 
           Yeah the way the build phase is running on VSTS, it's breaking these tests: My change found the  I guess the build process is different on a release branch? Let me know what you guys think I should do. I can make these   | 
    
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`.
7c48cfe    to
    b546e0a      
    Compare
  
    | 
           This has an annoying side effect:  so, this should: 
  | 
    
| 
           also, the MSbuild unit tests are taking 1min and 27sec to run on this machine  | 
    
| 
           Sounds like the  As far as the tests go, they are going to be a bit slow due to their nature. Xamarin.Android's tests like this take 30 seconds to 1 min each... We could drop the  Do you want me to add these to #2755 or a future PR? Maybe just speed up the tests and the rest can be addressed in another PR?  | 
    
Context: #2230
The main performance problem with the collection of MSBuild targets in
Xamarin.Forms.targetsis they don't build incrementally. I addressedthis with
XamlCusing a "stamp" file; however, it is not quite soeasy to setup the same thing with
XamlG.They way "incremental" builds are setup in MSBuild, is by specifying
the
InputsandOutputsof a<Target />. MSBuild will partiallybuild 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:_FindXamlGFilesthat is invoked beforeXamlG_FindXamlGFilesdefines the_XamlGInputsand_XamlGOutputs<ItemGroup />'s_FindXamlGFilesmust also define<Compile />and<FileWrites />,in case the
XamlGtarget is skippedXamlGTasknow needs to get passed in a list ofOutputFiles,since we have computed these paths ahead of time
XamlGTaskshould validate the lengths ofXamlFilesandOutputFilesmatch, used error message from MSBuild proper:https://github.com/Microsoft/msbuild/blob/a691a44f0e515e9a03ede8df0bff22185681c8b9/src/Tasks/Copy.cs#L505
XamlGnow builds incrementally!To give some context on how much improvement we can see with build
times, consider the following command:
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:
After the change:
XamlGhas cascading impact on build times when it isn't builtincrementally:
XamlCwill always runGenerateJavaStubswill always runjavac.exeanddx.jarwill always runI 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:
bin/Debug/temp/TestNamesdkStyleflag for testing the new project systemversus the old one
[TearDown]deletes the entire directory, with a retry forIOExceptionon WindowsMicrosoft.Build.LocatorNuGet package for locatingMSBuild.exeon WindowsSo for example, the simplest test,
BuildAProjectwrites toXamarin.Forms.Xaml.UnitTests\bin\Debug\temp\BuildAProject(False)\test.csproj:Invokes
msbuild, and checks the intermediate output for files beinggenerated.
Tested scenarios:
UpdateDesignTimeXamldirectlyEmbeddedResourcethat shouldn't go through XamlGAdding these tests found a bug!
IncrementalCleanwas deletingXamlC.stamp. I fixed this by using<ItemGroup />, which will bepropery evaluated even if the target is skipped.
~~ Other Changes ~~
FilesWriteis actually supposed to beFileWrites, see canonicalsource of how
Cleanworks and whatFileWritesis here:Understanding the Clean target dotnet/msbuild#2408 (comment)
DummyBuildEngineintoMSBuilddirectory--makes sense?maybe don't need to?
XamlGDifferentInputOutputLengthstest to check the errormessage
DummyBuildEngineso you can assert against log messages.Xamarin.Forms.Android.slnso the unit testproject is built
relevant) changes:
Xamarin.Forms.UnitTests.csproj,Xamarin.Forms.Xaml.UnitTests\app.config, etc.There were some checks for
%(TargetPath)being blank in the C# codeof
XamlGTask. In that case it was usingPath.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 buildsbefore
DependsOnTargets="PrepareResourceNames"was added. I testeddesign-time builds in VS on Windows, and
$(TargetPath)was set. Tobe sure we don't break anything here, I exclude inputs to
XamlGif%(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.
Description of Change
Describe your changes here.
Bugs Fixed
API Changes
List all API changes here (or just put None), example:
Added:
Changed:
Behavioral Changes
Describe any non-bug related behavioral changes that may change how users app behaves when upgrading to this version of the codebase.
PR Checklist