-
Notifications
You must be signed in to change notification settings - Fork 529
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] Ignore global
Alias when emitting code.
#4554
Conversation
I have one little question that wouldn't change this PR about In 49d12fd, the To minimize potential conflicts in |
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.
Draft release notes
Here's a candidate release note for this change. Feel free edit as desired and add it to this pull request. Thanks much! (Additional info about preparing draft release notes can now be found in the release notes README.)
#### Application and library build and deployment
* *error CS1681: You cannot redefine the global extern alias* prevented
building app projects successfully if the *.csproj* file included `global`
in the `<Aliases></Aliases>` metadata element of a referenced
Xamarin.Android class library.
@@ -49,7 +49,7 @@ public void CreateImportMethods (IEnumerable<ITaskItem> libraries) | |||
continue; | |||
} | |||
string alias = assemblyPath.GetMetadata ("Aliases"); | |||
bool hasAlias = !string.IsNullOrEmpty (alias); | |||
bool hasAlias = !string.IsNullOrEmpty (alias) && !aliases.Contains ("global"); |
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 just noticed yesterday that Visual Studio includes global
in the Aliases property input by default, so it's somewhat likely for projects to have global
included even if they don't need it. With that in mind, I'm wondering if it would be best to handle the following scenario:
-
There are two assemblies that provide the same namespace, and both contain Android resources.
-
Each assembly includes
global
in the%(Aliases)
along with a unique name, so for example:<Aliases>global,assembly1</Aliases>
<Aliases>global,assembly2</Aliases>
-
Everywhere in hand-written C# files, the project is careful to use
extern alias assembly1
andextern alias assembly2
and to prefix each type name or using statement with the appropriate alias. -
With this
&& !aliases.Contains ("global")
change, I think the generatedResource.designer.cs
file will not prefix the types from either assembly?
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.
Perhaps even more interesting is to add two more points to the scenario:
-
In addition to the conflicting namespaces, each assembly also contains its own namespace that does not conflict with the other assembly.
-
Some of the hand-written C# files use those non-conflicting namespaces, and they don't use any
extern alias
statements because both namespaces are accessible directly underglobal
without conflicts.
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 think if there is a global
in the Aliases Metadata provided by the user, we don't need to emit ANY extern alias
values. since the normal global
one will just work.
If global
is not present in that metadata, then global will NOT work, so we need the alias.
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.
Here's a test that covers the scenario I'm picturing. Removing && !aliasMetaData.Contains ("global")
from ResourceDesignerImportGenerator.cs
and GenerateResourceDesigner.cs
allows this test to pass:
[Test]
public void CheckMultipleLibraryProjectReferenceAlias ([Values (true, false)] bool withGlobal)
{
var path = Path.Combine (Root, "temp", TestName);
var library1 = new XamarinAndroidLibraryProject () {
ProjectName = "Library1",
};
var library2 = new XamarinAndroidLibraryProject () {
ProjectName = "Library2",
RootNamespace = "Library1"
};
var proj = new XamarinAndroidApplicationProject () {
References = {
new BuildItem.ProjectReference (Path.Combine("..", library1.ProjectName, Path.GetFileName (library1.ProjectFilePath)), "Library1") {
Metadata = { { "Aliases", withGlobal ? "global,Lib1A,Lib1B" : "Lib1A,Lib1B" } },
},
new BuildItem.ProjectReference (Path.Combine("..", library2.ProjectName, Path.GetFileName (library2.ProjectFilePath)), "Library2") {
Metadata = { { "Aliases", withGlobal ? "global,Lib2A,Lib2B" : "Lib2A,Lib2B" } },
},
},
};
using (var builder1 = CreateDllBuilder (Path.Combine (path, library1.ProjectName), cleanupAfterSuccessfulBuild: false, cleanupOnDispose: false)) {
builder1.ThrowOnBuildFailure = false;
Assert.IsTrue (builder1.Build (library1), "Library should have built.");
using (var builder2 = CreateDllBuilder (Path.Combine (path, library2.ProjectName), cleanupAfterSuccessfulBuild: false, cleanupOnDispose: false)) {
builder2.ThrowOnBuildFailure = false;
Assert.IsTrue (builder2.Build (library2), "Library should have built.");
using (var b = CreateApkBuilder (Path.Combine (path, proj.ProjectName), cleanupAfterSuccessfulBuild: false, cleanupOnDispose: false)) {
b.ThrowOnBuildFailure = false;
Assert.IsTrue (b.Build (proj), "Project should have built.");
string[] text = File.ReadAllLines (Path.Combine (Root, path, proj.ProjectName, "Resources", "Resource.designer.cs"));
Assert.IsTrue (text.Count (x => x.Contains ("Library1.Resource.String.library_name")) == 2, "library_name resource should be present exactly once for each library");
Assert.IsTrue (text.Count (x => x == "extern alias Lib1A;" || x == "extern alias Lib1B;") <= 1, "No more than one extern alias should be present for each library.");
}
}
}
}
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.
True, however I was trying to make this work in a way that if global
is present, then we don't need to emit ANY aliases in order for the designer file to compile. This is what the customer was expecting #4542 (comment). And it's how it used to work when we completely ignored Aliases. global
means we don't need anything.
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.
Whoops. I think I might have obscured the key concern by including additional asserts. It's the step of building the app project that concerns me. That fails right now for this PR because Resource.designer.cs
contains exactly the kind of C# compiler conflict that aliases solve:
Build FAILED.
"C:\Source\xamarin-android\bin\TestDebug\temp\CheckMultipleLibraryProjectReferenceAliasTrue\UnnamedProject\UnnamedProject.csproj" (Build;SignAndroidPackage target) (1) ->
(CoreCompile target) ->
Resources\Resource.designer.cs(28,4,28,29): error CS0433: The type 'Resource' exists in both 'Library1, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null' and 'Library2, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null'
Resources\Resource.designer.cs(29,4,29,29): error CS0433: The type 'Resource' exists in both 'Library1, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null' and 'Library2, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null'
Resources\Resource.designer.cs(30,4,30,29): error CS0433: The type 'Resource' exists in both 'Library1, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null' and 'Library2, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null'
Resources\Resource.designer.cs(31,4,31,29): error CS0433: The type 'Resource' exists in both 'Library1, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null' and 'Library2, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null'
0 Warning(s)
4 Error(s)
Due to the following generated lines:
global::Library1.Resource.Drawable.Icon = global::UnnamedProject.Resource.Drawable.Icon;
global::Library1.Resource.String.library_name = global::UnnamedProject.Resource.String.library_name;
global::Library1.Resource.Drawable.Icon = global::UnnamedProject.Resource.Drawable.Icon;
global::Library1.Resource.String.library_name = global::UnnamedProject.Resource.String.library_name;
That is, even though both libraries have global
in the list of aliases, Resource.designer.cs
must not use global
for both libraries or there will be a conflict. It seems to me the simplest way to handle an arbitrary number of conflicting libraries (admittedly unusual, but might as well handle it if we can) is probably to use the first non-global
alias for every libraries, which is the nice behavior this PR gives after removing && !aliasMetaData.Contains ("global")
.
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.
So with the proposed change we will ALWAYS use the alias even if global is present. I guess the downside of that is users will see a change in their Resource.designer.cs when they upgrade.
Shame we don't have any data on how often the RootNamespace is changed to match that of an identical library :(
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.
Oh yeah, that is an interesting trade-off to consider.
I suppose one little reassurance is that anyone who would get a change in Resource.designer.cs
with this PR would already have a change in behavior in Xamarin.Android 10.2 (d16-5) and Xamarin.Android 10.3 (d16-6) due to the initial feature addition from d18c824. So only users updating all at once from Xamarin.Android 10.1 (d16-4) or lower up to Xamarin.Android 10.4 (d16-7) or higher could benefit from changes in this PR. If there hasn't been much feedback about the change yet, then it's probably not too terribly common in user projects. (Though admittedly, there's a chance that projects that are slower to update Visual Studio versions might also be more likely to use %(Aliases)
metadata.)
Maybe, like the reporter of #4542 was mentioning, an idea could be to provide an extra way to control how aliases are used in Resource.designer.cs
. I wonder if any other .NET project type has a way to handle this. I think code-behind for XAML wouldn't have the issue since XAML files have to specify the CLR namespaces. I think Resources.Designer.cs
files for .resx
files wouldn't have to handle it either since there is only one assembly involved. Maybe ASP.NET or WCF has a scenario?
1c0c83e
to
6416838
Compare
Review update To help make it easy to skim to the latest status of the discussion on this PR at a glance, I'll add the results from running the
|
@brendanzagaeski I reworked this and added your test. Can you re-review please. |
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 good to me on functionality now. Thanks for bearing with me on assessing the behavior for this!
If you get a quick moment to add the release note into a file named Documentation/release-notes/4554.md
as part of this PR, that would be perfect. Feel free to copy and paste the candidate note if it looks alright to you:
#### Application and library build and deployment
* *error CS1681: You cannot redefine the global extern alias* prevented
building app projects successfully if the *.csproj* file included `global`
in the `<Aliases></Aliases>` metadata element of a referenced
Xamarin.Android class library.
Thanks again!
src/Xamarin.Android.Build.Tasks/Tasks/GenerateResourceDesigner.cs
Outdated
Show resolved
Hide resolved
src/Xamarin.Android.Build.Tasks/Tasks/GenerateResourceDesigner.cs
Outdated
Show resolved
Hide resolved
src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/AndroidUpdateResourcesTest.cs
Outdated
Show resolved
Hide resolved
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 good! Thanks once more!
In progress commit message:
|
string [] aliases = aliasMetaData.Split (new [] {','}, StringSplitOptions.RemoveEmptyEntries); | ||
foreach (var alias in aliases) { | ||
string aliasName = alias.Trim (); | ||
if (string.Compare ("global", aliasName, StringComparison.OrdinalIgnoreCase) == 0) |
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'm not certain that OrdinalIgnoreCase
is appropriate here, as C# is a case sensitive language. I'm not sure it's "unreasonable" for someone to use Global
as an alias.
…unless MSBuild metadata is by design case insensitive, in which case this is appropriate. I don't think that's the case (?).
Fixes dotnet#4542 Our previous fix d18c824 did not take into account that users might need to add `global` to their list of aliases in the `ProjectReferece` MetaData. This then causes the following error Error CS1681: You cannot redefine the global extern alias (CS1681) So to fix this we need to ignore the `global` entry if one is present.
…e. (#4554) Fixes: #4542 Our previous fix in d18c824 did not take into account that users might need to add `global` to their list of aliases in the `%(ProjectReference.Aliases)` metadata. This then causes the following error: Error CS1681: You cannot redefine the global extern alias (CS1681) Fix this by ignoring the `global` entry if one is present.
Fixes #4542
Our previous fix d18c824 did not take into account that users might
need to add
global
to their list of aliases in theProjectReferece
MetaData. This then causes the following error
So to fix this we need to ignore the
global
entry if one is present.