Skip to content
Open
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
1 change: 1 addition & 0 deletions Documentation/docs-mobile/messages/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,7 @@ Either change the value in the AndroidManifest.xml to match the $(SupportedOSPla
+ [XA4247](xa4247.md): Could not resolve POM file for artifact '{artifact}'.
+ [XA4248](xa4248.md): Could not find NuGet package '{nugetId}' version '{version}' in lock file. Ensure NuGet Restore has run since this `<PackageReference>` was added.
+ [XA4235](xa4249.md): Maven artifact specification '{artifact}' is invalid. The correct format is 'group_id:artifact_id:version'.
+ [XA4250](xa4250.md): Manifest-referenced type '{type}' was not found in any scanned assembly. It may be a framework type.
+ XA4300: Native library '{library}' will not be bundled because it has an unsupported ABI.
+ [XA4301](xa4301.md): Apk already contains the item `xxx`.
+ [XA4302](xa4302.md): Unhandled exception merging \`AndroidManifest.xml\`: {ex}
Expand Down
33 changes: 33 additions & 0 deletions Documentation/docs-mobile/messages/xa4250.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
---
title: .NET for Android warning XA4250
description: XA4250 warning code
ms.date: 04/07/2026
f1_keywords:
- "XA4250"
---

# .NET for Android warning XA4250

## Example message

Manifest-referenced type '{0}' was not found in any scanned assembly. It may be a framework type.

```text
warning XA4250: Manifest-referenced type 'com.example.MainActivity' was not found in any scanned assembly. It may be a framework type.
```

## Issue

The build found a type name in `AndroidManifest.xml`, but it could not match that name to any Java peer discovered in the app's managed assemblies.

This can be expected for framework-provided types, but it can also indicate that the manifest entry does not match the name generated for a managed Android component.

## Solution

If the manifest entry refers to an Android framework type, this warning can usually be ignored.

Otherwise:

1. Verify the `android:name` value in the manifest.
2. Ensure the managed type is included in the app build.
3. Check for namespace, `[Register]`, or nested-type naming mismatches between the manifest and the managed type.
Original file line number Diff line number Diff line change
Expand Up @@ -116,10 +116,7 @@ class ManifestGenerator
}

// Apply manifest placeholders
string? placeholders = ManifestPlaceholders;
if (placeholders is not null && placeholders.Length > 0) {
ApplyPlaceholders (doc, placeholders);
}
ApplyPlaceholders (doc, ManifestPlaceholders);

return (doc, providerNames);
}
Expand Down Expand Up @@ -250,8 +247,12 @@ XElement CreateRuntimeProvider (string name, string? processName, int initOrder)
/// Replaces ${key} placeholders in all attribute values throughout the document.
/// Placeholder format: "key1=value1;key2=value2"
/// </summary>
static void ApplyPlaceholders (XDocument doc, string placeholders)
internal static void ApplyPlaceholders (XDocument doc, string? placeholders)
{
if (placeholders.IsNullOrEmpty ()) {
return;
}

var replacements = new Dictionary<string, string> (StringComparer.Ordinal);
foreach (var entry in placeholders.Split (PlaceholderSeparators, StringSplitOptions.RemoveEmptyEntries)) {
var eqIndex = entry.IndexOf ('=');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,6 @@ public interface ITrimmableTypeMapLogger
void LogGeneratedRootTypeMapInfo (int assemblyReferenceCount);
void LogGeneratedTypeMapAssembliesInfo (int assemblyCount);
void LogGeneratedJcwFilesInfo (int sourceCount);
void LogRootingManifestReferencedTypeInfo (string javaTypeName, string managedTypeName);
void LogManifestReferencedTypeNotFoundWarning (string javaTypeName);
}
Original file line number Diff line number Diff line change
Expand Up @@ -69,16 +69,22 @@ public sealed record JavaPeerInfo
/// Types with component attributes ([Activity], [Service], etc.),
/// custom views from layout XML, or manifest-declared components
/// are unconditionally preserved (not trimmable).
/// May be set to <c>true</c> after scanning when the manifest references a type
/// that the scanner did not mark as unconditional. Should only ever be set
/// to <c>true</c>, never back to <c>false</c>.
/// </summary>
public bool IsUnconditional { get; init; }
public bool IsUnconditional { get; set; }

/// <summary>
/// True for Application and Instrumentation types. These types cannot call
/// <c>registerNatives</c> in their static initializer because the native library
/// (<c>libmonodroid.so</c>) is not loaded until after the Application class is instantiated.
/// Registration is deferred to <c>ApplicationRegistration.registerApplications()</c>.
/// This may also be set after scanning when a type is only discovered from
/// manifest <c>android:name</c> usage on <c>&lt;application&gt;</c> or
/// <c>&lt;instrumentation&gt;</c>.
/// </summary>
public bool CannotRegisterInStaticConstructor { get; init; }
public bool CannotRegisterInStaticConstructor { get; set; }

/// <summary>
/// Marshal methods: methods with [Register(name, sig, connector)], [Export], or
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ public TrimmableTypeMapResult Execute (
return new TrimmableTypeMapResult ([], [], allPeers);
}

RootManifestReferencedTypes (allPeers, PrepareManifestForRooting (manifestTemplate, manifestConfig));

var generatedAssemblies = GenerateTypeMapAssemblies (allPeers, systemRuntimeVersion);
var jcwPeers = allPeers.Where (p =>
!frameworkAssemblyNames.Contains (p.AssemblyName)
Expand Down Expand Up @@ -139,4 +141,148 @@ List<GeneratedJavaSource> GenerateJcwJavaSources (List<JavaPeerInfo> allPeers)
logger.LogGeneratedJcwFilesInfo (sources.Count);
return sources.ToList ();
}

internal void RootManifestReferencedTypes (List<JavaPeerInfo> allPeers, XDocument? doc)
{
if (doc?.Root is not { } root) {
return;
}

XNamespace androidNs = "http://schemas.android.com/apk/res/android";
XName attName = androidNs + "name";
var packageName = (string?) root.Attribute ("package") ?? "";

var componentNames = new HashSet<string> (StringComparer.Ordinal);
var deferredRegistrationNames = new HashSet<string> (StringComparer.Ordinal);
foreach (var element in root.Descendants ()) {
switch (element.Name.LocalName) {
case "application":
case "activity":
case "instrumentation":
case "service":
case "receiver":
case "provider":
var name = (string?) element.Attribute (attName);
if (name is not null) {
var resolvedName = ResolveManifestClassName (name, packageName);
componentNames.Add (resolvedName);

if (element.Name.LocalName is "application" or "instrumentation") {
deferredRegistrationNames.Add (resolvedName);
}
}
break;
}
}

if (componentNames.Count == 0) {
return;
}

// Build lookup by both Java and compat dot-names. Keep '$' for nested types,
// because manifests commonly use '$', but also include the Java source form.
var peersByDotName = new Dictionary<string, List<JavaPeerInfo>> (StringComparer.Ordinal);
foreach (var peer in allPeers) {
AddJniLookupNames (peersByDotName, peer.JavaName, peer);
if (peer.CompatJniName != peer.JavaName) {
AddJniLookupNames (peersByDotName, peer.CompatJniName, peer);
}
}

foreach (var name in componentNames) {
if (peersByDotName.TryGetValue (name, out var peers)) {
foreach (var peer in peers) {
if (deferredRegistrationNames.Contains (name)) {
peer.CannotRegisterInStaticConstructor = true;
}

if (!peer.IsUnconditional) {
peer.IsUnconditional = true;
logger.LogRootingManifestReferencedTypeInfo (name, peer.ManagedTypeName);
}
}
} else {
logger.LogManifestReferencedTypeNotFoundWarning (name);
}
}
}

static void AddPeerByDotName (Dictionary<string, List<JavaPeerInfo>> peersByDotName, string dotName, JavaPeerInfo peer)
{
if (!peersByDotName.TryGetValue (dotName, out var list)) {
list = [];
peersByDotName [dotName] = list;
}

list.Add (peer);
}

static XDocument? PrepareManifestForRooting (XDocument? manifestTemplate, ManifestConfig? manifestConfig)
{
if (manifestTemplate is null && manifestConfig is null) {
return null;
}

var doc = manifestTemplate is not null
? new XDocument (manifestTemplate)
: new XDocument (
new XElement (
"manifest",
new XAttribute (XNamespace.Xmlns + "android", ManifestConstants.AndroidNs.NamespaceName)));

if (doc.Root is not { } root) {
return doc;
}

if (manifestConfig is null) {
return doc;
}

if (((string?) root.Attribute ("package")).IsNullOrEmpty () && !manifestConfig.PackageName.IsNullOrEmpty ()) {
root.SetAttributeValue ("package", manifestConfig.PackageName);
}

ManifestGenerator.ApplyPlaceholders (doc, manifestConfig.ManifestPlaceholders);

if (!manifestConfig.ApplicationJavaClass.IsNullOrEmpty ()) {
var app = root.Element ("application");
if (app is null) {
app = new XElement ("application");
root.Add (app);
}

if (app.Attribute (ManifestConstants.AttName) is null) {
app.SetAttributeValue (ManifestConstants.AttName, manifestConfig.ApplicationJavaClass);
}
}

return doc;
}

static void AddJniLookupNames (Dictionary<string, List<JavaPeerInfo>> peersByDotName, string jniName, JavaPeerInfo peer)
{
var simpleName = JniSignatureHelper.GetJavaSimpleName (jniName);
var packageName = JniSignatureHelper.GetJavaPackageName (jniName);
var manifestName = packageName.IsNullOrEmpty () ? simpleName : packageName + "." + simpleName;
AddPeerByDotName (peersByDotName, manifestName, peer);

var javaSourceName = JniSignatureHelper.JniNameToJavaName (jniName);
if (javaSourceName != manifestName) {
AddPeerByDotName (peersByDotName, javaSourceName, peer);
}
}

/// <summary>
/// Resolves an android:name value to a fully-qualified class name.
/// Names starting with '.' are relative to the package. Names with no '.' at all
/// are also treated as relative (Android tooling convention).
/// </summary>
static string ResolveManifestClassName (string name, string packageName)
{
return name switch {
_ when name.StartsWith (".", StringComparison.Ordinal) => packageName + name,
_ when name.IndexOf ('.') < 0 && !packageName.IsNullOrEmpty () => packageName + "." + name,
_ => name,
};
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/Xamarin.Android.Build.Tasks/Properties/Resources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -1068,6 +1068,11 @@ To use a custom JDK path for a command line build, set the 'JavaSdkDirectory' MS
<value>Maven artifact specification '{0}' is invalid. The correct format is 'group_id:artifact_id:version'.</value>
<comment>The following are literal names and should not be translated: Maven, group_id, artifact_id
{0} - A Maven artifact specification</comment>
</data>
<data name="XA4250" xml:space="preserve">
<value>Manifest-referenced type '{0}' was not found in any scanned assembly. It may be a framework type.</value>
<comment>The following are literal names and should not be translated: Manifest, framework.
{0} - Java type name from AndroidManifest.xml</comment>
</data>
<data name="XA0142" xml:space="preserve">
<value>Command '{0}' failed.\n{1}</value>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ public void LogGeneratedTypeMapAssembliesInfo (int assemblyCount) =>
log.LogMessage (MessageImportance.Low, $"Generated {assemblyCount} typemap assemblies.");
public void LogGeneratedJcwFilesInfo (int sourceCount) =>
log.LogMessage (MessageImportance.Low, $"Generated {sourceCount} JCW Java source files.");
public void LogRootingManifestReferencedTypeInfo (string javaTypeName, string managedTypeName) =>
log.LogMessage (MessageImportance.Low, $"Rooting manifest-referenced type '{javaTypeName}' ({managedTypeName}) as unconditional.");
public void LogManifestReferencedTypeNotFoundWarning (string javaTypeName) =>
log.LogCodedWarning ("XA4250", Properties.Resources.XA4250, javaTypeName);
}

public override string TaskPrefix => "GTT";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,11 +123,60 @@ public void Execute_ParsesTargetFrameworkVersion (string tfv)
Assert.IsTrue (task.Execute (), $"Task should succeed with TargetFrameworkVersion='{tfv}'.");
}

[Test]
public void Execute_ManifestPlaceholdersAreResolvedForRooting ()
{
var path = Path.Combine ("temp", TestName);
var outputDir = Path.Combine (Root, path, "typemap");
var javaDir = Path.Combine (Root, path, "java");
var manifestTemplate = Path.Combine (Root, path, "AndroidManifest.xml");
var mergedManifest = Path.Combine (Root, path, "obj", "android", "AndroidManifest.xml");
var applicationRegistration = Path.Combine (Root, path, "src", "net", "dot", "android", "ApplicationRegistration.java");
var warnings = new List<BuildWarningEventArgs> ();

var monoAndroidItem = FindMonoAndroidDll ();
if (monoAndroidItem is null) {
Assert.Ignore ("Mono.Android.dll not found; skipping.");
return;
}

var manifestDirectory = Path.GetDirectoryName (manifestTemplate);
if (manifestDirectory is null) {
Assert.Fail ("Could not determine manifest template directory.");
}
Directory.CreateDirectory (manifestDirectory);
File.WriteAllText (manifestTemplate, """
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="${applicationId}">
<application android:name=".Application" />
<instrumentation android:name=".Instrumentation" />
</manifest>
""");

var task = CreateTask (new [] { monoAndroidItem }, outputDir, javaDir, warnings: warnings);
task.ManifestTemplate = manifestTemplate;
task.MergedAndroidManifestOutput = mergedManifest;
task.ApplicationRegistrationOutputFile = applicationRegistration;
task.PackageName = "android.app";
task.AndroidApiLevel = "35";
task.SupportedOSPlatformVersion = "21";
task.RuntimeProviderJavaName = "mono.MonoRuntimeProvider";
task.ManifestPlaceholders = "applicationId=android.app";

Assert.IsTrue (task.Execute (), "Task should succeed.");
FileAssert.Exists (applicationRegistration);

var registrationText = File.ReadAllText (applicationRegistration);
StringAssert.Contains ("mono.android.Runtime.registerNatives (android.app.Application.class);", registrationText);
StringAssert.Contains ("mono.android.Runtime.registerNatives (android.app.Instrumentation.class);", registrationText);
Assert.IsFalse (warnings.Any (w => w.Code == "XA4250"), "Resolved placeholder-based manifest references should not log XA4250.");
}

GenerateTrimmableTypeMap CreateTask (ITaskItem [] assemblies, string outputDir, string javaDir,
IList<BuildMessageEventArgs>? messages = null, string tfv = "v11.0")
IList<BuildMessageEventArgs>? messages = null, IList<BuildWarningEventArgs>? warnings = null, string tfv = "v11.0")
{
return new GenerateTrimmableTypeMap {
BuildEngine = new MockBuildEngine (TestContext.Out, messages: messages),
BuildEngine = new MockBuildEngine (TestContext.Out, warnings: warnings, messages: messages),
ResolvedAssemblies = assemblies,
OutputDirectory = outputDir,
JavaSourceOutputDirectory = javaDir,
Expand Down
Loading