Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Xamarin.Android.Build.Tasks] Emit an Error if a custom view cannot be fixed up. #1720

Merged
merged 3 commits into from
Aug 2, 2018

Conversation

dellis1972
Copy link
Contributor

@dellis1972 dellis1972 commented May 23, 2018

Context #1711

When using a custom view within a layout file we replace the
namespace.classname with an md5hash.classname. We do this
by using the acwmap.txt file to make known types onto the
md5 hashed ones. We do this in a case sensitive manner. We
also only support Camel case and lower case type names.

ClassLibrary1.CustomView=md5d6f7135293df7527c983d45d07471c5e.CustomTextView
classlibrary1.CustomView=md5d6f7135293df7527c983d45d07471c5e.CustomTextView

Given that a user is able to type these manually it is highly
probable that typo's will occur. If for example a user types

Classlibrary1.CustomView

(Note the lower case L) this will NOT be fixed up. Instead the user will recieve the
following error at runtime.

FATAL UNHANDLED EXCEPTION: Android.Views.InflateException: Binary XML file line #1: Binary XML file line #1: Error inflating class Classlibrary1.CustomTextView ---> Android.Views.InflateException: Binary XML file line #1: Error inflating class Classlibrary1.CustomTextView ---> Java.Lang.ClassNotFoundException: Didn't find class "Classlibrary1.CustomTextView"

if you are using the control in a number of places this runtime error
does nothing to help track down the problem.

Instead what we should be doing is detecting these issues and emitting
a build error. This will provide the user not only the problem but
also a link to the file causing the probem.

TODO

  • Fix up the name so it points to the file in Resources not res
  • Add a Unit test.
  • Add Error code and document.

@dellis1972 dellis1972 added the do-not-merge PR should not be merged. label May 23, 2018
Log.LogDebugMessage (" ResourceNameCaseMap: {0}", ResourceNameCaseMap);

if (ResourceNameCaseMap != null)
foreach (var arr in ResourceNameCaseMap.Split (';').Select (l => l.Split ('|')).Where (a => a.Length == 2))
Copy link
Member

Choose a reason for hiding this comment

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

This same code is also in Aapt.cs. We should move this to a utility method.

@dellis1972
Copy link
Contributor Author

build

@dellis1972 dellis1972 removed the do-not-merge PR should not be merged. label Jun 19, 2018
@dellis1972
Copy link
Contributor Author

@jonpryor @jonathanpeppers this should be good to go now.

string targetfile = file;
if (targetfile.StartsWith (resdir, StringComparison.InvariantCultureIgnoreCase)) {
targetfile = file.Substring (resdir.Length).TrimStart (Path.DirectorySeparatorChar);
targetfile = resource_name_case_map.ContainsKey (targetfile) ? resource_name_case_map [targetfile] : targetfile;
Copy link
Member

Choose a reason for hiding this comment

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

It might be worth using TryGetValue so the dictionary lookup doesn't happen twice: ContainsKey and indexer.

I'm guessing resource_name_case_map, could be big?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

not sure. We would need to add a temp variable since it seems TryGetValue will null the output if it failed.

When this method returns, contains the value associated with the specified key, if the key is found; otherwise, the default value for the type of the value parameter. This parameter is passed uninitialized.

So we can't do

resource_name_case_map.TryGetValue (targetfile, out targetfile);

we would need to do

string temp;
if (resource_name_case_map.TryGetValue (targetfile, out temp))
    targetfile = temp;

not sure which is better.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ah, stuff it. Gonna change to TryGetValue

Copy link
Member

Choose a reason for hiding this comment

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

I think this could make it a little better:

if (resource_name_case_map.TryGetValue (targetfile, out string temp))
    targetfile = temp;

// we have elements with slightly different casing.
// lets issue a error.
logMessage (TraceLevel.Error, $"We found a matching key '{matchingKey.Key}' for '{elem.Name.ToString ()}'. But the casing was incorrect. Please correct the casing");
}
return false;
}
Copy link
Member

Choose a reason for hiding this comment

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

It looks like this method calls XElement.Name.ToString () alot, maybe calling it once and storing in a variable would be good?

elem.Name = acwMap[elem.Name.ToString ()];
string name = elem.Name.ToString ();
if (acwMap.ContainsKey (name)) {
elem.Name = acwMap[name];
Copy link
Member

Choose a reason for hiding this comment

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

This one could also use TryGetValue, sorry I didn't see it before.

if (targetfile.StartsWith (resdir, StringComparison.InvariantCultureIgnoreCase)) {
targetfile = file.Substring (resdir.Length).TrimStart (Path.DirectorySeparatorChar);
string temp;
if (resource_name_case_map.TryGetValue (targetfile, out temp))
Copy link
Member

Choose a reason for hiding this comment

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

You might try the new out syntax:

if (resource_name_case_map.TryGetValue (targetfile, out string temp))

var result = new Dictionary<string, string> ();
if (resourceCaseMap != null) {
foreach (var arr in resourceCaseMap.Split (';').Select (l => l.Split ('|')).Where (a => a.Length == 2))
result [arr [1]] = arr [0]; // lowercase -> original
Copy link
Member

Choose a reason for hiding this comment

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

This is probably for the future, but I wonder if we could use Span<T> here to make this faster.

This current code looks like it would allocate a lot of strings.

@dellis1972
Copy link
Contributor Author

@jonathanpeppers fixed up the TryGetValue bits :)

…e fixed up.

Context dotnet#1711

When using a custom view within a layout file we replace the
`namespace.classname` with an `md5hash.classname`. We do this
by using the `acwmap.txt` file to make known types onto the
`md5` hashed ones. We do this in a case sensitive manner. We
also only support Camel case and lower case type names.

	ClassLibrary1.CustomView=md5d6f7135293df7527c983d45d07471c5e.CustomTextView
	classlibrary1.CustomView=md5d6f7135293df7527c983d45d07471c5e.CustomTextView

Given that a user is able to type these manually it is highly
probable that typo's will occur. If for example a user types

	Classlibrary1.CustomView

this will NOT be fixed up. Instead the user will recieve the
following error at runtime.

	FATAL UNHANDLED EXCEPTION: Android.Views.InflateException: Binary XML file line #1: Binary XML file line #1: Error inflating class Classlibrary1.CustomTextView ---> Android.Views.InflateException: Binary XML file line #1: Error inflating class Classlibrary1.CustomTextView ---> Java.Lang.ClassNotFoundException: Didn't find class "Classlibrary1.CustomTextView"

if you are using the control in a number of places this runtime error
does nothing to help track down the problem.

Instead what we should be doing is detecting these issues and emitting
a build error. This will provide the user not only the problem but
also a link to the file causing the probem.

TODO
----

[ ] Fix up the name so it points to the file in `Resources` not `res`
[ ] Add a Unit test.
[ ] Add Error code and document.
@jonpryor jonpryor merged commit 967fe94 into dotnet:master Aug 2, 2018
jonpryor pushed a commit that referenced this pull request Aug 3, 2018
…e fixed up. (#1720)

Context: #1711

When using a custom view within a layout file we replace the
`namespace.classname` with an `md5hash.classname`.  We do this by
using the `acwmap.txt` file to map known types onto the `md5` hashed
ones.  This is done in a case sensitive manner.  We also only support
identically-cased and lower-cased namespace/package names:

	ClassLibrary1.CustomView=md5d6f7135293df7527c983d45d07471c5e.CustomTextView
	classlibrary1.CustomView=md5d6f7135293df7527c983d45d07471c5e.CustomTextView

Given that a user is able to type these manually it is possible that
typo's will occur.  If for example a user types

	Classlibrary1.CustomView

this will NOT be fixed up, due to the lowercase `l` in `library`.
Instead the user will see the following error at runtime.

	FATAL UNHANDLED EXCEPTION: Android.Views.InflateException: Binary XML file line #1: Binary XML file line #1: Error inflating class Classlibrary1.CustomTextView
	---> Android.Views.InflateException: Binary XML file line #1: Error inflating class Classlibrary1.CustomTextView
	---> Java.Lang.ClassNotFoundException: Didn't find class "Classlibrary1.CustomTextView"

If the control is used in a number of places this runtime error does
nothing to help track down the problem.

Improve this scenario by detecting these issues and emitting an
XA1002 build error.  This will not only inform the user about the
problem but also provide a link to the file causing the problem.
@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