Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line numberDiff line numberDiff line change
Expand Up@@ -22,6 +22,8 @@ void LogUnresolvableJavaPeerSkippedWarning (
string unresolvedAssemblyPath);
void LogJniAddNativeMethodRegistrationAttributeError (string managedTypeName);
void LogInvalidJavaNameError (string javaName, string invalidIdentifier);
void LogDuplicateJavaTypeError (string javaName);
void LogDuplicateJavaTypeDetailsError (string javaName, string managedTypeName);
void LogExportFieldWithParametersError ();
void LogExportFieldOnGenericTypeError ();
void LogExportFieldReturnsVoidError ();
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -105,6 +105,24 @@ internal bool ValidateJavaNames (IReadOnlyList<JavaPeerInfo> peers, string? appl
{
bool valid = true;
var reportedNames = new HashSet<string> (StringComparer.Ordinal);
foreach (var group in peers
.Where (ShouldGenerateJcw)
.GroupBy (peer => peer.JavaName, StringComparer.Ordinal)
.OrderBy (group => group.Key, StringComparer.Ordinal)) {
var firstAssemblyName = group.First ().AssemblyName;
if (group.All (peer => string.Equals (peer.AssemblyName, firstAssemblyName, StringComparison.Ordinal))) {
Comment thread
simonrozsival marked this conversation as resolved.
continue;
}

var javaName = JniSignatureHelper.JniNameToJavaBinaryName (group.Key);
logger.LogDuplicateJavaTypeError (javaName);
foreach (var peer in group
.OrderBy (peer => peer.ManagedTypeName, StringComparer.Ordinal)
.ThenBy (peer => peer.AssemblyName, StringComparer.Ordinal)) {
logger.LogDuplicateJavaTypeDetailsError (javaName, $"{peer.ManagedTypeName}, {peer.AssemblyName}");
}
valid = false;
}
if (applicationJavaClass is not null &&
JavaNameValidator.TryGetInvalidJavaSourceTypeSegment (applicationJavaClass, out var invalidApplicationIdentifier)) {
ReportInvalidName (applicationJavaClass, invalidApplicationIdentifier);
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -59,6 +59,10 @@ public void LogJniAddNativeMethodRegistrationAttributeError (string managedTypeN
log.LogCodedError ("XA4251", Properties.Resources.XA4251, managedTypeName);
public void LogInvalidJavaNameError (string javaName, string invalidIdentifier) =>
log.LogCodedError ("XA4258", Properties.Resources.XA4258, javaName, invalidIdentifier);
public void LogDuplicateJavaTypeError (string javaName) =>
log.LogCodedError ("XA4215", Properties.Resources.XA4215, javaName);
public void LogDuplicateJavaTypeDetailsError (string javaName, string managedTypeName) =>
log.LogCodedError ("XA4215", Properties.Resources.XA4215_Details, javaName, managedTypeName);
public void LogExportFieldWithParametersError () =>
log.LogCodedError ("XA4205", Java.Interop.Localization.Resources.JavaCallableWrappers_XA4205);
public void LogExportFieldOnGenericTypeError () =>
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -619,26 +619,44 @@ public void AndroidLibraryProjectsZipWithOddPaths ([Values (AndroidRuntime.CoreC
}
}

[Test]
public void DuplicateJCWNames ([Values (AndroidRuntime.CoreCLR, AndroidRuntime.NativeAOT)] AndroidRuntime runtime)
[TestCase ("llvm-ir", AndroidRuntime.CoreCLR)]
[TestCase ("trimmable", AndroidRuntime.CoreCLR)]
[TestCase ("trimmable", AndroidRuntime.NativeAOT)]
public void DuplicateJCWNames (string typemapImplementation, AndroidRuntime runtime)
{
bool isRelease = runtime == AndroidRuntime.NativeAOT;
if (IgnoreUnsupportedConfiguration (runtime, release: isRelease)) {
return;
}

// TODO: NativeAOT should fail the application build
if (runtime == AndroidRuntime.NativeAOT) {
Assert.Ignore ("NativeAOT doesn't fail the application build");
}
var source1 = """
namespace Library1;

var source = @"[Android.Runtime.Register (""examplelib.EmptyClass"")] public class EmptyClass : Java.Lang.Object { }";
[Android.Runtime.Register ("examplelib.DuplicatePeer")]
public class FirstPeer : Java.Lang.Object
{
public FirstPeer (int value)
{
}
}
""";
var source2 = """
namespace Library2;

[Android.Runtime.Register ("examplelib.DuplicatePeer")]
public class SecondPeer : Java.Lang.Object
{
public SecondPeer (string value)
{
}
}
""";
var library1 = new XamarinAndroidLibraryProject () {
IsRelease = isRelease,
ProjectName = "Library1",
Sources = {
new BuildItem.Source ("EmptyClass.cs") {
TextContent = () => source
new BuildItem.Source ("FirstPeer.cs") {
TextContent = () => source1
}
}
};
Expand All@@ -647,8 +665,8 @@ public void DuplicateJCWNames ([Values (AndroidRuntime.CoreCLR, AndroidRuntime.N
IsRelease = isRelease,
ProjectName = "Library2",
Sources = {
new BuildItem.Source ("EmptyClass.cs") {
TextContent = () => source
new BuildItem.Source ("SecondPeer.cs") {
TextContent = () => source2
}
}
};
Expand All@@ -662,21 +680,40 @@ public void DuplicateJCWNames ([Values (AndroidRuntime.CoreCLR, AndroidRuntime.N
},
};
app.SetRuntime (runtime);
var projectPath = Path.Combine ("temp", TestName);
app.SetProperty ("AndroidTypeMapImplementation", typemapImplementation);
var projectPath = Path.Combine ("temp", $"{TestName}_{Guid.NewGuid ():N}");
using var lib1b = CreateDllBuilder (Path.Combine (projectPath, library1.ProjectName), cleanupAfterSuccessfulBuild: false);
using var lib2b = CreateDllBuilder (Path.Combine (projectPath, library2.ProjectName), cleanupAfterSuccessfulBuild: false);

Assert.IsTrue (lib1b.Build (library1), "Build of Library1 should have succeeded");
Assert.IsTrue (lib2b.Build (library2), "Build of Library2 should have succeeded");

using var appb = CreateApkBuilder (Path.Combine (projectPath, app.ProjectName));
using var appb = CreateApkBuilder (Path.Combine (projectPath, app.ProjectName), cleanupAfterSuccessfulBuild: false);
appb.ThrowOnBuildFailure = false;
Assert.IsFalse (appb.Build (app), "Build of App1 should have failed");
IEnumerable<string> errors = appb.LastBuildOutput.Where (x => x.Contains ("error XA4215"));
Assert.NotNull (errors, "Error should be XA4215");
StringAssertEx.Contains ("EmptyClass", errors, "Error should mention the conflicting type name");
StringAssertEx.Contains ("Library1", errors, "Error should mention all of the assemblies with conflicts");
StringAssertEx.Contains ("Library2", errors, "Error should mention all of the assemblies with conflicts");
var errors = appb.LastBuildOutput.Where (x => x.Contains ("error XA4215")).ToList ();
Assert.IsNotEmpty (errors, "Error should be XA4215");
StringAssertEx.Contains ("examplelib.DuplicatePeer", errors, "Error should mention the conflicting Java type name");
StringAssertEx.Contains ("Library1.FirstPeer", errors, "Error should mention the first conflicting managed type");
StringAssertEx.Contains ("Library2.SecondPeer", errors, "Error should mention the second conflicting managed type");
Assert.IsFalse (appb.LastBuildOutput.Any (x => x.Contains ("warning XA4214")),
"Distinct managed type names should not produce XA4214.");

var acwMapFile = appb.Output.GetIntermediaryPath ("acw-map.txt");
if (typemapImplementation == "llvm-ir") {
var javaFile = appb.Output.GetIntermediaryPath (Path.Combine ("android", "src", "examplelib", "DuplicatePeer.java"));
FileAssert.Exists (javaFile, "llvm-ir generates JCW sources before XA4215 is detected.");
FileAssert.DoesNotExist (acwMapFile, "llvm-ir should not write an ambiguous acw-map.");
} else {
var typemapDirectory = appb.Output.GetIntermediaryPath ("typemap");
var javaFile = Path.Combine (typemapDirectory, "java", "examplelib", "DuplicatePeer.java");
FileAssert.DoesNotExist (javaFile, "A failed trimmable typemap build should not write an ambiguous JCW source.");
FileAssert.DoesNotExist (acwMapFile, "A failed trimmable typemap build should not write an ambiguous acw-map.");
if (Directory.Exists (typemapDirectory)) {
Assert.IsEmpty (Directory.GetFiles (typemapDirectory, "*.dll"),
"A failed trimmable typemap build should not write typemap assemblies.");
}
}
}

[Test]
Expand Down
Original file line numberDiff line numberDiff line change
Expand Up@@ -53,6 +53,10 @@ public void LogJniAddNativeMethodRegistrationAttributeError (string managedTypeN
logMessages.Add ($"XA4251: Type '{managedTypeName}' uses [JniAddNativeMethodRegistrationAttribute], which is not supported by the trimmable type map.");
public void LogInvalidJavaNameError (string javaName, string invalidIdentifier) =>
logMessages.Add ($"XA4258: Java name '{javaName}' contains reserved Java identifier '{invalidIdentifier}'.");
public void LogDuplicateJavaTypeError (string javaName) =>
logMessages.Add ($"XA4215: The Java type `{javaName}` is generated by more than one managed type.");
public void LogDuplicateJavaTypeDetailsError (string javaName, string managedTypeName) =>
logMessages.Add ($"XA4215: `{javaName}` generated by: {managedTypeName}");
public void LogExportFieldWithParametersError () =>
logMessages.Add ("XA4205: [ExportField] can only be used on methods with 0 parameters.");
public void LogExportFieldReturnsVoidError () =>
Expand DownExpand Up@@ -104,6 +108,34 @@ public void ValidateJavaNames_ContextualKeywordInPackage_IsValid ()
Assert.DoesNotContain (logMessages, message => message.Contains ("XA4258"));
}

[Fact]
public void ValidateJavaNames_DuplicateGeneratedJcw_LogsXA4215 ()
{
var peers = new List<JavaPeerInfo> {
new JavaPeerInfo {
JavaName = "examplelib/DuplicatePeer",
CompatJniName = "examplelib/DuplicatePeer",
ManagedTypeName = "Library1.FirstPeer",
ManagedTypeNamespace = "Library1",
ManagedTypeShortName = "FirstPeer",
AssemblyName = "Library1",
},
new JavaPeerInfo {
JavaName = "examplelib/DuplicatePeer",
CompatJniName = "examplelib/DuplicatePeer",
ManagedTypeName = "Library2.SecondPeer",
ManagedTypeNamespace = "Library2",
ManagedTypeShortName = "SecondPeer",
AssemblyName = "Library2",
},
};

Assert.False (CreateGenerator ().ValidateJavaNames (peers));
Assert.Contains (logMessages, message => message == "XA4215: The Java type `examplelib.DuplicatePeer` is generated by more than one managed type.");
Assert.Contains (logMessages, message => message == "XA4215: `examplelib.DuplicatePeer` generated by: Library1.FirstPeer, Library1");
Assert.Contains (logMessages, message => message == "XA4215: `examplelib.DuplicatePeer` generated by: Library2.SecondPeer, Library2");
}

[Theory]
[InlineData ("com/example/Outer$for", null, "for")]
[InlineData ("com/example/Outer$record", null, "record")]
Expand Down