Skip to content

[Xamarin.Android.Build.Tasks] Add ConvertCustomView Task - #2129

Merged
jonpryor merged 3 commits into
dotnet:masterfrom
dellis1972:Issue2100
Sep 5, 2018
Merged

[Xamarin.Android.Build.Tasks] Add ConvertCustomView Task#2129
jonpryor merged 3 commits into
dotnet:masterfrom
dellis1972:Issue2100

Conversation

@dellis1972

@dellis1972dellis1972 commented Aug 31, 2018

Copy link
Copy Markdown
Contributor

Fixed#2100

Currently ConvertResourcesCases is called twice on ALL
resources referenced by the project. This is terribly inefficient.
However it is required. The first pass is done before a Compile,
this fixes up the casing for things like drawables etc ready to
be processed by aapt. The second is done after GenerateJavaStubs
which happens AFTER the Compile step. This is to replace any
custom view references with the correct {md5}.View style references.
This is because we replace the normal readable namespace with an md5 hash.

The problem was that ConvertResourcesCases is doing a TON of
work it doesn't really need to do the second time around. For example
checking if it needs to lower case names of items. The second pass
really only needs to worry about custom views. In addition to that
it was also re-scanning ALL the files again.

This commit introduces a new Task ConvertCustomView. The sole
purpose of this task is to fix up the layout files which contain
custom views. It does nothing else. To make this even quicker we
modify ConvertResourcesCases to emit a mapping file (class-map.txt). This file
contains items like

MonoDroid.Example.MyLayout;/fullpath/to/file.xml
android.support.v7.widget.ActionBarOverlayLayout;/fullpath/to/some/other/file.xml

This allows us to know were the files are which contain ANY layout.

With this information in conjuction with the acw_map.txt file will
allow us to do a targeted update. So we go through the acw_map.txt
values and fix up those files where we have entires in the class-map.txt.

This reduces the amount of time spent processing files quite a bit.
For a Blank Xamarin Forms app from a clean build.

2639 ms ConvertResourcesCases 1 calls
3 ms ConvertCustomView 1 calls

Normally the ConvertResourcesCases would be called twice and would
take a total of 5-6 seconds.

  • Fix up Paths in Error Message
  • Write a decent Commit Message
  • General Clean up.

@dellis1972dellis1972 added the do-not-merge PR should not be merged. label Aug 31, 2018
@dellis1972dellis1972 changed the title [Xamarin.Android.Build.Tasks] Add ConvertCustomView Task [WIP[Xamarin.Android.Build.Tasks] Add ConvertCustomView Task [WIP]Aug 31, 2018
@dellis1972
dellis1972force-pushed the Issue2100 branch 4 times, most recently from fcd102e to 10d1f6cCompareSeptember 3, 2018 19:34
@dellis1972

Copy link
Copy Markdown
ContributorAuthor

@jonathanpeppers we need a VSTS build on this. I'm not sure if the tempdst change will break stuff on windows. I will try to test locally tomorrow

@dellis1972dellis1972 removed the do-not-merge PR should not be merged. label Sep 3, 2018
@dellis1972dellis1972 changed the title [Xamarin.Android.Build.Tasks] Add ConvertCustomView Task [WIP][Xamarin.Android.Build.Tasks] Add ConvertCustomView TaskSep 3, 2018
@jonathanpeppers

Copy link
Copy Markdown
Member

@dellis1972 I started one.

I had to update $(XABundleDownloadPrefix) to 'https://xamjenkinsartifact.azureedge.net/mono-jenkins/xamarin-android-1154/xamarin-android/bin/Debug', but it seemed like the first try failed building master. We'll see what happens.

@jonathanpeppers

Copy link
Copy Markdown
Member

One test is failing on Windows:

Xamarin.Android.Build.Tests.ConvertResourcesCasesTests.CheckClassIsNotReplacedWithMd5 / Debug
System.ArgumentNullException : Value cannot be null.
Parameter name: source
at System.Linq.Enumerable.FirstOrDefault[TSource](IEnumerable`1 source, Func`2 predicate)
at Xamarin.Android.Tasks.ConvertCustomView.<>c__DisplayClass16_1.<Execute>b__0(TraceLevel t, String m) in E:\A\_work\14\s\src\Xamarin.Android.Build.Tasks\Tasks\ConvertCustomView.cs:line 50
at Xamarin.Android.Tasks.ConvertCustomView.TryFixCustomView(XElement elem, Dictionary`2 acwMap, Action`2 logMessage) in E:\A\_work\14\s\src\Xamarin.Android.Build.Tasks\Tasks\ConvertCustomView.cs:line 147
at Xamarin.Android.Tasks.ConvertCustomView.Execute() in E:\A\_work\14\s\src\Xamarin.Android.Build.Tasks\Tasks\ConvertCustomView.cs:line 48
at Xamarin.Android.Build.Tests.ConvertResourcesCasesTests.CheckClassIsNotReplacedWithMd5() in E:\A\_work\14\s\src\Xamarin.Android.Build.Tasks\Tests\Xamarin.Android.Build.Tests\ConvertResourcesCasesTests.cs:line 91

@dellis1972

dellis1972 commented Sep 4, 2018 via email

Copy link
Copy Markdown
ContributorAuthor

map.Add (items [0], new HashSet<string> ());
if (!map[items [0]].Contains (items [1]))
map [items [0]].Add (items [1]);
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

If there are a lot of items in this dictionary, we should use some local variable so indexers aren't called as much.

Would help perf a little to be something like:

foreach(varsinFile.ReadLines(mapFile)){varsplit=s.Split(';');varkey=split[0];varvalue=split[1];if(!map.TryGetValue(key,outHashSet<string>items)){map[key]=items=newHashSet<string>();}if(!items.Contains(value)){items.Add(value);}}

@dellis1972
dellis1972force-pushed the Issue2100 branch 3 times, most recently from ec65bdc to fca3b91CompareSeptember 4, 2018 14:29
var value = items [1];
if (!map.ContainsKey (key))
map.Add (key, new HashSet<string> ());
if (!map[key].Contains (value))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

You don't need .Contains() here, as it's a HashSet<T>. Just always call map [key].Add(value).

var items = s.Split (';');
var key = items [0];
var value = items [1];
if (!map.ContainsKey (key))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This should instead do:

HashSet<string>set;if(!map.TryGetValue(key,outset)){map.Add(key,set=newHashSet<string>();}set.Add(value);

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

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

That is some nice code :)

if (!File.Exists (mapFile))
return map;
foreach (var s in File.ReadLines (mapFile)) {
var items = s.Split (';');

@jonpryorjonpryorSep 4, 2018

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Don't use string.Split(char[]); use string.Split(char[], int):

varitems=s.Split(new[]{';'},2);

This way if the filesystem path contains a ; it'll behave sanely.

@dellis1972

Copy link
Copy Markdown
ContributorAuthor

need to change the name from class-map to customview-map and the associated Load/Save methods.

break;
}
});
}, (e, filename) => {

@jonpryorjonpryorSep 4, 2018

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Would it be possible to use named parameters here? I don't know what this callback is supposed to do.

},customViewMapCallback:(e,filename)=>{
...

var temp = Path.GetTempFileName ();
try {
using (var m = new StreamWriter (temp)) {
foreach (var i in map) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This should contain a map.OrderBy (k => k.Key) or something. Enumeration order of Dictionaries is undefined, so it's possible that two "identical" Dictionary<string, HashSet,string>> values will result in different orders in m, which means the CopyIfChanged() (below) becomes "useless."

try {
using (var m = new StreamWriter (temp)) {
foreach (var i in map) {
foreach (var v in i.Value)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Similarly, enumeration order of a HashSet<string> is undefined, so this should be i.Value.OrderBy (f => f) so that we can produce consistent files.

@dellis1972
dellis1972force-pushed the Issue2100 branch 2 times, most recently from db4cdf7 to ae22049CompareSeptember 5, 2018 08:33
Fixeddotnet#2100
Currently `ConvertResourcesCases` is called twice on ALL
resources referenced by the project. This is terribly inefficient.
However it is required. The first pass is done before a `Compile`,
this fixes up the casing for things like drawables etc ready to
be processed by aapt. The second is done after `GenerateJavaStubs`
which happens AFTER the `Compile` step. This is to replace any
custom view references with the correct `{md5}.View` style references.
This is because we replace the normal readable namespace with an md5 hash.
The problem was that `ConvertResourcesCases` is doing a TON of
work it doesn't really need to do the second time around. For example
checking if it needs to lower case names of items. The second pass
really only needs to worry about custom views. In addition to that
it was also re-scanning ALL the files again.
This commit introduces a new Task `ConvertCustomView`. The sole
purpose of this task is to fix up the layout files which contain
custom views. It does nothing else. To make this even quicker we
modify `ConvertResourcesCases` to emit a mapping file (class-map.txt). This file
contains items like
MonoDroid.Example.MyLayout;/fullpath/to/file.xml
android.support.v7.widget.ActionBarOverlayLayout;/fullpath/to/some/other/file.xml
This allows us to know were the files are which contain ANY layout.
With this information in conjuction with the `acw_map.txt` file will
allow us to do a targeted update. So we go through the `acw_map.txt`
values and fix up those files where we have entires in the `class-map.txt`.
This reduces the amount of time spent processing files quite a bit.
For a Blank Xamarin Forms app from a clean build.
2639 ms ConvertResourcesCases 1 calls
3 ms ConvertCustomView 1 calls
Normally the `ConvertResourcesCases` would be called twice and would
take a total of 5-6 seconds.
Comment threadsrc/Xamarin.Android.Build.Tasks/Utilities/AndroidResource.cs
Comment threadsrc/Xamarin.Android.Build.Tasks/Tasks/ConvertCustomView.cs Outdated
Comment threadsrc/Xamarin.Android.Build.Tasks/Tasks/ConvertCustomView.cs Outdated
@jonpryor
jonpryor merged commit 1886e6f into dotnet:masterSep 5, 2018
@@ -85,11 +98,32 @@ public override bool Execute ()
var dstmodifiedDate = File.Exists (destfilename) ? File.GetLastWriteTimeUtc (destfilename) : DateTime.MinValue;
var tmpdest = Path.GetTempFileName ();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I don't see this file being used before it gets deleted

jonathanpeppers added a commit to jonathanpeppers/xamarin-android that referenced this pull request Sep 21, 2018
Context: dotnet#2088
Context: dotnet#2129
Context: dotnet#2199
In 4deec52, I fixed the #deletebinobj problem we discovered.
However...
I introduced a regression to incremental builds, I noticed that the
`_CompileJava` target is now running on a second build with no
changes. Third build? oddly it gets skipped...
It seems to be due to our use of flag files:
1. `_UpdateAndroidResgen` updates `R.cs.flag` and uses the file as an
output
2. `_GenerateJavaDesignerForComponent` now uses `R.cs.flag` as an
input
3. `_GenerateJavaStubs` *also* updates the timestamp on `R.cs.flag`.
This was added in 970da9e, as a workaround for our two instances
of `ConvertResourcesCases`.
4. `_GenerateJavaDesignerForComponent` will now run again on the next
build.
Since 1886e6f eliminated the second call to `ConvertResourcesCases`,
we don't need to update `R.cs.flag` in no. 3 any longer.
Removing the call to `<Touch />` `R.cs.flag` in `_GenerateJavaStubs`
fixed the issue, and I added some assertions in relevant tests to
check that the `_CompileJava` and `_GenerateJavaDesignerForComponent`
targets aren't running on incremental builds.
dellis1972 pushed a commit that referenced this pull request Sep 21, 2018
Context: #2088
Context: #2129
Context: #2199
In 4deec52, I fixed the #deletebinobj problem we discovered.
However...
I introduced a regression to incremental builds, I noticed that the
`_CompileJava` target is now running on a second build with no
changes. Third build? oddly it gets skipped...
It seems to be due to our use of flag files:
1. `_UpdateAndroidResgen` updates `R.cs.flag` and uses the file as an
output
2. `_GenerateJavaDesignerForComponent` now uses `R.cs.flag` as an
input
3. `_GenerateJavaStubs` *also* updates the timestamp on `R.cs.flag`.
This was added in 970da9e, as a workaround for our two instances
of `ConvertResourcesCases`.
4. `_GenerateJavaDesignerForComponent` will now run again on the next
build.
Since 1886e6f eliminated the second call to `ConvertResourcesCases`,
we don't need to update `R.cs.flag` in no. 3 any longer.
Removing the call to `<Touch />` `R.cs.flag` in `_GenerateJavaStubs`
fixed the issue, and I added some assertions in relevant tests to
check that the `_CompileJava` and `_GenerateJavaDesignerForComponent`
targets aren't running on incremental builds.
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Feb 2, 2024
Sign up for freeto 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.

Rework Layout CustomView replacement.

4 participants

@dellis1972@jonathanpeppers@jonpryor@StephaneDelcroix