diff --git a/Documentation/guides/building-apps/build-properties.md b/Documentation/guides/building-apps/build-properties.md
index ee48b5653fe..113c87c3635 100644
--- a/Documentation/guides/building-apps/build-properties.md
+++ b/Documentation/guides/building-apps/build-properties.md
@@ -1364,6 +1364,35 @@ To suppress the default AOT profiles, set the property to `false`.
Added in Xamarin.Android 10.1.
+## AndroidUseDesignerAssembly
+
+A bool property which controls if the build system will generate an
+`_Microsoft.Android.Resource.Designer.dll` as apposed to a `Resource.Designer.cs` file. The benefits of this are smaller applications and
+faster startup time.
+
+The default value is `true` in .NET 8.
+
+This setting is not backward compatible with Classic Xamarin.Android.
+As a Nuget Author it is recommended that you ship three versions of
+the assembly if you want to maintain backward compatibility.
+One for MonoAndroid, one for net6.0-android and
+one for net8.0-android. You can do this by using [Xamarin.Legacy.Sdk](https://www.nuget.org/packages/Xamarin.Legacy.Sdk). This is only required if your Nuget Library
+project makes use of `AndroidResource` items in the project or via a dependency.
+
+```
+monoandroid90;net6.0-android;net8.0-android
+```
+
+Alternatively turn this setting off until such time as both Classic and
+net7.0-android have been deprecated.
+
+.NET 8 Projects which choose to turn this setting off will not be able to
+consume references which do use it. If you try to use an assembly
+which does have this feature enabled in a project that does not, you will
+get a `XA1034` build error.
+
+Added in .NET 8. Unsupported in Classic Xamarin.Android.
+
## AndroidUseInterpreter
A boolean property that causes the `.apk` to contain the mono
diff --git a/Documentation/guides/messages/xa1034.md b/Documentation/guides/messages/xa1034.md
new file mode 100644
index 00000000000..be1bffd43e7
--- /dev/null
+++ b/Documentation/guides/messages/xa1034.md
@@ -0,0 +1,16 @@
+---
+title: Xamarin.Android error XA1034
+description: XA1034 error code
+ms.date: 13/12/2022
+---
+# Xamarin.Android error XA1034
+
+## Example messages
+
+```
+Your project references 'Foo.dll' which uses the `_Microsoft.Android.Resource.Designer` assembly, but you do not have this feature enabled. Please set the `AndroidUseDesignerAssembly` MSBuild property to `true` in your project file.
+```
+
+## Solution
+
+Edit your csproj directly and change the 'AndroidUseDesignerAssembly' to `True`.
\ No newline at end of file
diff --git a/build-tools/installers/create-installers.targets b/build-tools/installers/create-installers.targets
index 51ff5bb9a1d..3e589147ceb 100644
--- a/build-tools/installers/create-installers.targets
+++ b/build-tools/installers/create-installers.targets
@@ -303,6 +303,7 @@
<_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)Xamarin.Android.CSharp.targets" ExcludeFromAndroidNETSdk="true" />
<_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)Xamarin.Android.D8.targets" />
<_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)Xamarin.Android.Designer.targets" />
+ <_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)Xamarin.Android.Resource.Designer.targets" />
<_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)Xamarin.Android.DesignTime.targets" />
<_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)Xamarin.Android.EmbeddedResource.targets" />
<_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)Xamarin.Android.FSharp.targets" ExcludeFromAndroidNETSdk="true" />
diff --git a/build-tools/xaprepare/xaprepare/ThirdPartyNotices/StrongNameSigner.cs b/build-tools/xaprepare/xaprepare/ThirdPartyNotices/StrongNameSigner.cs
new file mode 100644
index 00000000000..45ab44b28b4
--- /dev/null
+++ b/build-tools/xaprepare/xaprepare/ThirdPartyNotices/StrongNameSigner.cs
@@ -0,0 +1,33 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+
+namespace Xamarin.Android.Prepare
+{
+ [TPN]
+ class StrongNameSigner_TPN : ThirdPartyNotice
+ {
+ static readonly Uri url = new Uri ("https://github.com/brutaldev/StrongNameSigner/");
+
+ public override string LicenseFile => string.Empty;
+ public override string Name => "brutaldev/StrongNameSigner";
+ public override Uri SourceUrl => url;
+ public override string LicenseText => @"
+Copyright (c) Werner van Deventer (werner@brutaldev.com). All rights reserved.
+
+Licensed under the Apache License, Version 2.0 (the 'License'); you
+may not use this file except in compliance with the License. You may
+obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an 'AS IS' BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+implied. See the License for the specific language governing permissions
+and limitations under the License.
+";
+
+ public override bool Include (bool includeExternalDeps, bool includeBuildDeps) => includeExternalDeps;
+ }
+}
diff --git a/src-ThirdParty/Mono.Security.Cryptography/CryptoConvert.cs b/src-ThirdParty/Mono.Security.Cryptography/CryptoConvert.cs
new file mode 100644
index 00000000000..5a5eecc1468
--- /dev/null
+++ b/src-ThirdParty/Mono.Security.Cryptography/CryptoConvert.cs
@@ -0,0 +1,338 @@
+//
+// CryptoConvert.cs - Crypto Conversion Routines
+//
+// Author:
+// Sebastien Pouliot
+//
+// (C) 2003 Motus Technologies Inc. (http://www.motus.com)
+// Copyright (C) 2004-2006 Novell Inc. (http://www.novell.com)
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+// Original source can be found at
+// https://github.com/mono/mono/blob/e2c5f4b0ad1a6b21ca0735f0b35b8611d4ad87b3/mcs/class/Mono.Security/Mono.Security.Cryptography/CryptoConvert.cs#L4
+
+using System;
+using System.Security.Cryptography;
+
+namespace Mono.Security.Cryptography
+{
+ internal static class CryptoConvert
+ {
+ static private int ToInt32LE(byte[] bytes, int offset)
+ {
+ return (bytes[offset + 3] << 24) | (bytes[offset + 2] << 16) | (bytes[offset + 1] << 8) | bytes[offset];
+ }
+
+ static private uint ToUInt32LE(byte[] bytes, int offset)
+ {
+ return (uint)((bytes[offset + 3] << 24) | (bytes[offset + 2] << 16) | (bytes[offset + 1] << 8) | bytes[offset]);
+ }
+
+ static private byte[] GetBytesLE(int val)
+ {
+ return new byte[] {
+ (byte) (val & 0xff),
+ (byte) ((val >> 8) & 0xff),
+ (byte) ((val >> 16) & 0xff),
+ (byte) ((val >> 24) & 0xff)
+ };
+ }
+
+ static private byte[] Trim(byte[] array)
+ {
+ for (int i = 0; i < array.Length; i++)
+ {
+ if (array[i] != 0x00)
+ {
+ byte[] result = new byte[array.Length - i];
+ Buffer.BlockCopy(array, i, result, 0, result.Length);
+ return result;
+ }
+ }
+
+#pragma warning disable S1168 // Empty arrays and collections should be returned instead of null
+ return null;
+#pragma warning restore S1168 // Empty arrays and collections should be returned instead of null
+ }
+
+ private static RSA FromCapiPrivateKeyBlob(byte[] blob, int offset)
+ {
+ var rsap = new RSAParameters();
+
+ try
+ {
+ if ((blob[offset] != 0x07) || // PRIVATEKEYBLOB (0x07)
+ (blob[offset + 1] != 0x02) || // Version (0x02)
+ (blob[offset + 2] != 0x00) || // Reserved (word)
+ (blob[offset + 3] != 0x00) ||
+ (ToUInt32LE(blob, offset + 8) != 0x32415352)) // DWORD magic = RSA2
+ {
+ throw new CryptographicException("Invalid blob header");
+ }
+
+ //// ALGID (CALG_RSA_SIGN, CALG_RSA_KEYX, ...)
+ //// int algId = ToInt32LE (blob, offset+4);
+
+ //// DWORD bitlen
+ int bitLen = ToInt32LE(blob, offset + 12);
+
+ //// DWORD public exponent
+ byte[] exp = new byte[4];
+ Buffer.BlockCopy(blob, offset + 16, exp, 0, 4);
+ Array.Reverse(exp);
+ rsap.Exponent = Trim(exp);
+
+ int pos = offset + 20;
+ //// BYTE modulus[rsapubkey.bitlen/8];
+ int byteLen = (bitLen >> 3);
+ rsap.Modulus = new byte[byteLen];
+ Buffer.BlockCopy(blob, pos, rsap.Modulus, 0, byteLen);
+ Array.Reverse(rsap.Modulus);
+ pos += byteLen;
+
+ //// BYTE prime1[rsapubkey.bitlen/16];
+ int byteHalfLen = (byteLen >> 1);
+ rsap.P = new byte[byteHalfLen];
+ Buffer.BlockCopy(blob, pos, rsap.P, 0, byteHalfLen);
+ Array.Reverse(rsap.P);
+ pos += byteHalfLen;
+
+ //// BYTE prime2[rsapubkey.bitlen/16];
+ rsap.Q = new byte[byteHalfLen];
+ Buffer.BlockCopy(blob, pos, rsap.Q, 0, byteHalfLen);
+ Array.Reverse(rsap.Q);
+ pos += byteHalfLen;
+
+ //// BYTE exponent1[rsapubkey.bitlen/16];
+ rsap.DP = new byte[byteHalfLen];
+ Buffer.BlockCopy(blob, pos, rsap.DP, 0, byteHalfLen);
+ Array.Reverse(rsap.DP);
+ pos += byteHalfLen;
+
+ //// BYTE exponent2[rsapubkey.bitlen/16];
+ rsap.DQ = new byte[byteHalfLen];
+ Buffer.BlockCopy(blob, pos, rsap.DQ, 0, byteHalfLen);
+ Array.Reverse(rsap.DQ);
+ pos += byteHalfLen;
+
+ //// BYTE coefficient[rsapubkey.bitlen/16];
+ rsap.InverseQ = new byte[byteHalfLen];
+ Buffer.BlockCopy(blob, pos, rsap.InverseQ, 0, byteHalfLen);
+ Array.Reverse(rsap.InverseQ);
+ pos += byteHalfLen;
+
+ // ok, this is hackish but CryptoAPI support it so...
+ // note: only works because CRT is used by default
+ // http://bugzilla.ximian.com/show_bug.cgi?id=57941
+ rsap.D = new byte[byteLen]; // must be allocated
+ if (pos + byteLen + offset <= blob.Length)
+ {
+ //// BYTE privateExponent[rsapubkey.bitlen/8];
+ Buffer.BlockCopy(blob, pos, rsap.D, 0, byteLen);
+ Array.Reverse(rsap.D);
+ }
+ }
+ catch (Exception e)
+ {
+ throw new CryptographicException("Invalid blob.", e);
+ }
+
+ RSA rsa = null;
+ try
+ {
+ rsa = RSA.Create();
+ rsa.ImportParameters(rsap);
+ }
+ catch (CryptographicException)
+ {
+ // this may cause problem when this code is run under
+ // the SYSTEM identity on Windows (e.g. ASP.NET). See
+ // http://bugzilla.ximian.com/show_bug.cgi?id=77559
+ bool throws = false;
+ try
+ {
+ var csp = new CspParameters
+ {
+ Flags = CspProviderFlags.UseMachineKeyStore
+ };
+
+#pragma warning disable S4426 // Cryptographic keys should be robust
+ rsa = new RSACryptoServiceProvider(csp);
+#pragma warning restore S4426 // Cryptographic keys should be robust
+ rsa.ImportParameters(rsap);
+ }
+ catch
+ {
+ throws = true;
+ }
+
+ if (throws)
+ {
+ // rethrow original, not the latter, exception if this fails
+ throw;
+ }
+ }
+ return rsa;
+ }
+
+ private static RSA FromCapiPublicKeyBlob(byte[] blob, int offset)
+ {
+ try
+ {
+ if ((blob[offset] != 0x06) || // PUBLICKEYBLOB (0x06)
+ (blob[offset + 1] != 0x02) || // Version (0x02)
+ (blob[offset + 2] != 0x00) || // Reserved (word)
+ (blob[offset + 3] != 0x00) ||
+ (ToUInt32LE(blob, offset + 8) != 0x31415352)) // DWORD magic = RSA1
+ {
+ throw new CryptographicException("Invalid blob header");
+ }
+
+ //// ALGID (CALG_RSA_SIGN, CALG_RSA_KEYX, ...)
+ //// int algId = ToInt32LE (blob, offset+4);
+
+ // DWORD bitlen
+ int bitLen = ToInt32LE(blob, offset + 12);
+
+ //// DWORD public exponent
+ var rsap = new RSAParameters
+ {
+ Exponent = new byte[3]
+ };
+
+ rsap.Exponent[0] = blob[offset + 18];
+ rsap.Exponent[1] = blob[offset + 17];
+ rsap.Exponent[2] = blob[offset + 16];
+
+ int pos = offset + 20;
+ //// BYTE modulus[rsapubkey.bitlen/8];
+ int byteLen = bitLen >> 3;
+ rsap.Modulus = new byte[byteLen];
+ Buffer.BlockCopy(blob, pos, rsap.Modulus, 0, byteLen);
+ Array.Reverse(rsap.Modulus);
+
+ RSA rsa = null;
+ try
+ {
+ rsa = RSA.Create();
+ rsa.ImportParameters(rsap);
+ }
+ catch (CryptographicException)
+ {
+ // this may cause problem when this code is run under
+ // the SYSTEM identity on Windows (e.g. ASP.NET). See
+ // http://bugzilla.ximian.com/show_bug.cgi?id=77559
+ var csp = new CspParameters
+ {
+ Flags = CspProviderFlags.UseMachineKeyStore
+ };
+
+#pragma warning disable S4426 // Cryptographic keys should be robust
+ rsa = new RSACryptoServiceProvider(csp);
+#pragma warning restore S4426 // Cryptographic keys should be robust
+ rsa.ImportParameters(rsap);
+ }
+
+ return rsa;
+ }
+ catch (Exception e)
+ {
+ throw new CryptographicException("Invalid blob.", e);
+ }
+ }
+
+ // PRIVATEKEYBLOB
+ // PUBLICKEYBLOB
+ static public RSA FromCapiKeyBlob(byte[] blob)
+ {
+ return FromCapiKeyBlob(blob, 0);
+ }
+
+ static public RSA FromCapiKeyBlob(byte[] blob, int offset)
+ {
+ if (blob == null)
+ {
+ throw new ArgumentNullException(nameof(blob));
+ }
+
+ if (offset >= blob.Length)
+ {
+ throw new ArgumentException("blob is too small.");
+ }
+
+ switch (blob[offset])
+ {
+ case 0x00:
+ // this could be a public key inside an header
+ // like "sn -e" would produce
+ if (blob[offset + 12] == 0x06)
+ {
+ return FromCapiPublicKeyBlob(blob, offset + 12);
+ }
+ break;
+ case 0x06:
+ return FromCapiPublicKeyBlob(blob, offset);
+ case 0x07:
+ return FromCapiPrivateKeyBlob(blob, offset);
+ }
+ throw new CryptographicException("Unknown blob format.");
+ }
+
+ static public byte[] ToCapiPublicKeyBlob(RSA rsa)
+ {
+ var p = rsa.ExportParameters(false);
+ int keyLength = p.Modulus.Length; // in bytes
+ byte[] blob = new byte[20 + keyLength];
+
+ blob[0] = 0x06; // Type - PUBLICKEYBLOB (0x06)
+ blob[1] = 0x02; // Version - Always CUR_BLOB_VERSION (0x02)
+ // [2], [3] // RESERVED - Always 0
+ blob[5] = 0x24; // ALGID - Always 00 24 00 00 (for CALG_RSA_SIGN)
+ blob[8] = 0x52; // Magic - RSA1 (ASCII in hex)
+ blob[9] = 0x53;
+ blob[10] = 0x41;
+ blob[11] = 0x31;
+
+ byte[] bitlen = GetBytesLE(keyLength << 3);
+ blob[12] = bitlen[0]; // bitlen
+ blob[13] = bitlen[1];
+ blob[14] = bitlen[2];
+ blob[15] = bitlen[3];
+
+ // public exponent (DWORD)
+ int pos = 16;
+ int n = p.Exponent.Length;
+ while (n > 0)
+ {
+ blob[pos++] = p.Exponent[--n];
+ }
+
+ // modulus
+ pos = 20;
+ byte[] part = p.Modulus;
+ int len = part.Length;
+ Array.Reverse(part, 0, len);
+ Buffer.BlockCopy(part, 0, blob, pos, len);
+
+ return blob;
+ }
+ }
+}
diff --git a/src-ThirdParty/StrongNameSigner/SigningHelper.cs b/src-ThirdParty/StrongNameSigner/SigningHelper.cs
new file mode 100644
index 00000000000..260e1c92147
--- /dev/null
+++ b/src-ThirdParty/StrongNameSigner/SigningHelper.cs
@@ -0,0 +1,35 @@
+// Original source can be found at
+// https://github.com/brutaldev/StrongNameSigner/blob/c38d42ab8d1444504720a62736b310303236cd85/src/Brutal.Dev.StrongNameSigner/SigningHelper.cs#L437
+using System;
+using Mono.Security.Cryptography;
+
+
+namespace Brutal.Dev.StrongNameSigner {
+ ///
+ /// Static helper class for easily getting assembly information and strong-name signing .NET assemblies.
+ ///
+ public static class SigningHelper
+ {
+ internal static byte[] GetPublicKey (byte[] keyBlob)
+ {
+ using var rsa = CryptoConvert.FromCapiKeyBlob(keyBlob);
+ var cspBlob = CryptoConvert.ToCapiPublicKeyBlob(rsa);
+ var publicKey = new byte[12 + cspBlob.Length];
+ Buffer.BlockCopy(cspBlob, 0, publicKey, 12, cspBlob.Length);
+ // The first 12 bytes are documented at:
+ // http://msdn.microsoft.com/library/en-us/cprefadd/html/grfungethashfromfile.asp
+ // ALG_ID - Signature
+ publicKey[1] = 36;
+ // ALG_ID - Hash
+ publicKey[4] = 4;
+ publicKey[5] = 128;
+ // Length of Public Key (in bytes)
+ publicKey[8] = (byte)(cspBlob.Length >> 0);
+ publicKey[9] = (byte)(cspBlob.Length >> 8);
+ publicKey[10] = (byte)(cspBlob.Length >> 16);
+ publicKey[11] = (byte)(cspBlob.Length >> 24);
+
+ return publicKey;
+ }
+ }
+}
diff --git a/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/FixLegacyResourceDesignerStep.cs b/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/FixLegacyResourceDesignerStep.cs
new file mode 100644
index 00000000000..8a88408c42d
--- /dev/null
+++ b/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/FixLegacyResourceDesignerStep.cs
@@ -0,0 +1,163 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+using Mono.Cecil;
+using Mono.Cecil.Cil;
+
+using Java.Interop.Tools.Cecil;
+
+using Mono.Linker;
+using Mono.Linker.Steps;
+
+using Mono.Tuner;
+#if ILLINK
+using Microsoft.Android.Sdk.ILLink;
+#endif // ILLINK
+
+namespace MonoDroid.Tuner
+{
+ public class FixLegacyResourceDesignerStep : LinkDesignerBase
+ {
+ internal const string DesignerAssemblyName = "_Microsoft.Android.Resource.Designer";
+ internal const string DesignerAssemblyNamespace = "Microsoft.Android.Resource.Designer";
+#if ILLINK
+ protected override void Process ()
+ {
+ cache = Context;
+ }
+#else // ILLINK
+ public FixLegacyResourceDesignerStep (IMetadataResolver cache)
+ {
+ this.cache = cache;
+ }
+
+ readonly
+#endif // ILLINK
+ IMetadataResolver cache;
+ AssemblyDefinition designerAssembly = null;
+ TypeDefinition designerType = null;
+ Dictionary lookup;
+
+ protected override void EndProcess ()
+ {
+ if (designerAssembly != null) {
+ LogMessage ($" Setting Action on {designerAssembly.Name} to Link.");
+ Annotations.SetAction (designerAssembly, AssemblyAction.Link);
+ }
+ }
+
+ protected override void LoadDesigner ()
+ {
+ if (designerAssembly != null)
+ return;
+ var designerNameAssembly = AssemblyNameReference.Parse ($"{DesignerAssemblyName}, Version=1.0.0.0");
+ try {
+ designerAssembly = Resolve (designerNameAssembly);
+ } catch (Mono.Cecil.AssemblyResolutionException) {
+ LogMessage ($" Could not resolve assembly {DesignerAssemblyName}.");
+ } catch (System.IO.FileNotFoundException) {
+ LogMessage ($" Assembly {DesignerAssemblyName} did not exist.");
+ }
+ if (designerAssembly == null) {
+ return;
+ }
+ designerType = designerAssembly.MainModule.GetTypes ().FirstOrDefault (x => x.FullName == $"{DesignerAssemblyNamespace}.Resource");
+ if (designerType == null) {
+ LogMessage ($" Did not find {DesignerAssemblyNamespace}.Resource type. It was probably linked out.");
+ return;
+ }
+ lookup = BuildResourceDesignerPropertyLookup (designerType);
+ return;
+ }
+
+ internal override bool ProcessAssemblyDesigner (AssemblyDefinition assembly)
+ {
+ if (designerAssembly == null || designerType == null) {
+ LogMessage ($" Not using {DesignerAssemblyName}");
+ return false;
+ }
+
+ if (!FindResourceDesigner (assembly, mainApplication: false, out TypeDefinition designer, out CustomAttribute designerAttribute)) {
+ LogMessage ($" {assembly.Name.Name} has no designer. ");
+ return false;
+ }
+
+ LogMessage ($" {assembly.Name.Name} has a designer. ");
+ LogMessage ($" BaseType: {designer.BaseType.FullName}. ");
+ if (designer.BaseType.FullName == $"{DesignerAssemblyNamespace}.Resource") {
+ LogMessage ($" {assembly.Name.Name} has already been processed. ");
+ return false;
+ }
+
+ LogMessage ($" Adding reference {designerAssembly.Name.Name}.");
+ assembly.MainModule.AssemblyReferences.Add (designerAssembly.Name);
+ var importedDesignerType = assembly.MainModule.ImportReference (designerType.Resolve ());
+
+ LogMessage ($" FixupAssemblyTypes {assembly.Name.Name}.");
+ // now replace all ldsfld with a call to the property get_ method.
+ FixupAssemblyTypes (assembly, designer);
+
+ LogMessage ($" ClearDesignerClass {assembly.Name.Name}.");
+ // then clean out the designer.
+ ClearDesignerClass (designer, completely: true);
+ designer.BaseType = importedDesignerType;
+ return true;
+ }
+
+ Dictionary BuildResourceDesignerPropertyLookup (TypeDefinition type)
+ {
+ LogMessage ($" Building Designer Lookups for {type.FullName}");
+ var output = new Dictionary (StringComparer.Ordinal);
+ foreach (TypeDefinition definition in type.NestedTypes)
+ {
+ foreach (PropertyDefinition property in definition.Properties)
+ {
+ string key = $"{definition.Name}::{property.Name}";
+ if (!output.ContainsKey (key)) {
+ LogMessage ($" Adding {key}");
+ output.Add(key, property.GetMethod);
+ }
+ }
+ }
+ return output;
+ }
+
+ protected override void FixBody (MethodBody body, TypeDefinition designer)
+ {
+ // replace
+ // IL_0068: ldsfld int32 Xamarin.Forms.Platform.Android.Resource/Layout::Toolbar
+ // with
+ // call int32 Xamarin.Forms.Platform.Android.Resource/Layout::get_Toolbar()
+ string designerFullName = $"{designer.FullName}/";
+ var processor = body.GetILProcessor ();
+ Dictionary instructions = new Dictionary();
+ foreach (var i in body.Instructions)
+ {
+ if (i.OpCode != OpCodes.Ldsfld)
+ continue;
+ string line = i.ToString ();
+ int idx = line.IndexOf (designerFullName, StringComparison.Ordinal);
+ if (idx >= 0) {
+ string key = line.Substring (idx + designerFullName.Length);
+ LogMessage ($"Looking for {key}.");
+ if (lookup.TryGetValue (key, out MethodDefinition method)) {
+ var importedMethod = designer.Module.ImportReference (method);
+ var newIn = Instruction.Create (OpCodes.Call, importedMethod);
+ instructions.Add (i, newIn);
+ } else {
+ LogMessage ($"DEBUG! Failed to find {key}!");
+ }
+ }
+ }
+ if (instructions.Count > 0)
+ LogMessage ($" Fixing up {body.Method.FullName}");
+ foreach (var i in instructions)
+ {
+ LogMessage ($" Replacing {i.Key}");
+ LogMessage ($" With {i.Value}");
+ processor.Replace(i.Key, i.Value);
+ }
+ }
+ }
+}
diff --git a/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/LinkDesignerBase.cs b/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/LinkDesignerBase.cs
index 928fdfd6e4f..f03fa12d0fc 100644
--- a/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/LinkDesignerBase.cs
+++ b/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/LinkDesignerBase.cs
@@ -67,20 +67,24 @@ protected bool FindResourceDesigner (AssemblyDefinition assembly, bool mainAppli
return false;
}
- protected void ClearDesignerClass (TypeDefinition designer)
+ protected void ClearDesignerClass (TypeDefinition designer, bool completely = false)
{
LogMessage ($" TryRemoving {designer.FullName}");
// for each of the nested types clear all but the
// int[] fields.
- for (int i = designer.NestedTypes.Count -1; i >= 0; i--) {
- var nestedType = designer.NestedTypes [i];
- RemoveFieldsFromType (nestedType, designer.Module);
- if (nestedType.Fields.Count == 0) {
- // no fields we do not need this class at all.
- designer.NestedTypes.RemoveAt (i);
+ if (!completely) {
+ for (int i = designer.NestedTypes.Count -1; i >= 0; i--) {
+ var nestedType = designer.NestedTypes [i];
+ RemoveFieldsFromType (nestedType, designer.Module);
+ if (nestedType.Fields.Count == 0) {
+ // no fields we do not need this class at all.
+ designer.NestedTypes.RemoveAt (i);
+ }
}
+ RemoveUpdateIdValues (designer);
+ } else {
+ designer.NestedTypes.Clear ();
}
- RemoveUpdateIdValues (designer);
designer.Fields.Clear ();
designer.Properties.Clear ();
designer.CustomAttributes.Clear ();
diff --git a/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/Linker.cs b/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/Linker.cs
index cca2056cf35..45c3a3feeb3 100644
--- a/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/Linker.cs
+++ b/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/Linker.cs
@@ -111,6 +111,8 @@ static Pipeline CreatePipeline (LinkerOptions options)
pipeline.AppendStep (new RemoveResources (options.I18nAssemblies)); // remove collation tables
// end monodroid specific
+ if (options.UseDesignerAssembly)
+ pipeline.AppendStep (new FixLegacyResourceDesignerStep (cache));
pipeline.AppendStep (new FixAbstractMethodsStep (cache));
pipeline.AppendStep (new MonoDroidMarkStep (cache));
pipeline.AppendStep (new SweepStep ());
diff --git a/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/LinkerOptions.cs b/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/LinkerOptions.cs
index 0cd81bb8641..1886d50915e 100644
--- a/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/LinkerOptions.cs
+++ b/src/Xamarin.Android.Build.Tasks/Linker/MonoDroid.Tuner/LinkerOptions.cs
@@ -26,5 +26,6 @@ class LinkerOptions
public bool PreserveJniMarshalMethods { get; set; }
public bool DeterministicOutput { get; set; }
public bool LinkResources { get; set; }
+ public bool UseDesignerAssembly { get; set; }
}
}
diff --git a/src/Xamarin.Android.Build.Tasks/MSBuild/Xamarin/Android/Xamarin.Android.Aapt2.targets b/src/Xamarin.Android.Build.Tasks/MSBuild/Xamarin/Android/Xamarin.Android.Aapt2.targets
index 8827e1ceb51..d1bfde205de 100644
--- a/src/Xamarin.Android.Build.Tasks/MSBuild/Xamarin/Android/Xamarin.Android.Aapt2.targets
+++ b/src/Xamarin.Android.Build.Tasks/MSBuild/Xamarin/Android/Xamarin.Android.Aapt2.targets
@@ -154,7 +154,7 @@ Copyright (C) 2011-2012 Xamarin. All rights reserved.
--no-version-vectors $(AndroidAapt2LinkExtraArgs)
+
+
+
+
+
+
+
+
+
+ False
+
+
+
+
+ <_DesignerAssemblyName>_Microsoft.Android.Resource.Designer
+
+ <_DesignerIntermediateOutputPath Condition=" '$(_DesignerIntermediateOutputPath)' == '' And Exists ('$(IntermediateOutputPath)$(_DesignerAssemblyName).dll') ">$(IntermediateOutputPath)
+ <_DesignerIntermediateOutputPath Condition=" '$(_DesignerIntermediateOutputPath)' == '' And '$(_OuterIntermediateOutputPath)' != '' And Exists ('$(_OuterIntermediateOutputPath)$(_DesignerAssemblyName).dll') ">$(_OuterIntermediateOutputPath)
+
+ <_DesignerIntermediateOutputPath Condition=" '$(_DesignerIntermediateOutputPath)' == '' And '$(_OuterIntermediateOutputPath)' != '' ">$(_OuterIntermediateOutputPath)
+ <_DesignerIntermediateOutputPath Condition=" '$(_DesignerIntermediateOutputPath)' == '' ">$(IntermediateOutputPath)
+ <_GenerateResourceDesignerAssemblyOutput>$(_DesignerIntermediateOutputPath)$(_DesignerAssemblyName).dll
+ <_GenerateResourceDesignerClassFile Condition=" '$(Language)' == 'F#' ">$(_DesignerIntermediateOutputPath)_$(_DesignerAssemblyName).fs
+ <_GenerateResourceDesignerClassFile Condition=" '$(_GenerateResourceDesignerClassFile)' == '' ">$(_DesignerIntermediateOutputPath)_$(_DesignerAssemblyName).cs
+ <_GenerateResourceCaseMapFile>$(_DesignerIntermediateOutputPath)case_map.txt
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ <_ErrorItems Include="@(_MonoAndroidReferencePath)" Condition=" '%(_MonoAndroidReferencePath.HasResourceDesignerAssemblyReference)' == 'True' "/>
+ <_ErrorItems Include="@(_MonoAndroidReferenceDependencyPaths)" Condition=" '%(_MonoAndroidReferenceDependencyPaths.HasResourceDesignerAssemblyReference)' == 'True' "/>
+
+
+
+
+
+ <_BuildResourceDesignerDependsOn>
+ _SetupDesignerProperties;
+ _GenerateResourceCaseMap;
+ _GenerateRtxt;
+ _GenerateResourceDesignerIntermediateClass;
+ _GenerateResourceDesignerAssembly;
+ _AddResourceDesignerFiles;
+
+
+
+
+
+
+ PreserveNewest
+
+
+
+
+
+
+
+
+
+
+
+
+ $(_DesignerAssemblyName).dll
+ PreserveNewest
+ true
+ true
+
+
+
+
+
+
+
diff --git a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.AssemblyResolution.targets b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.AssemblyResolution.targets
index e15b10a264f..c4a7a91b861 100644
--- a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.AssemblyResolution.targets
+++ b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.AssemblyResolution.targets
@@ -67,6 +67,14 @@ _ResolveAssemblies MSBuild target.
+
+
+ True
+ PreserveNewest
+ false
+ android
+
+
<_DebugSymbolsIntermediatePath Remove="@(_DebugSymbolsIntermediatePath)" />
<_DebugSymbolsIntermediatePath Include="$([System.IO.Path]::ChangeExtension ($(_OuterIntermediateAssembly), '.pdb'))" />
diff --git a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.BuildOrder.targets b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.BuildOrder.targets
index b9305f91637..fa15917a912 100644
--- a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.BuildOrder.targets
+++ b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.BuildOrder.targets
@@ -107,6 +107,7 @@ projects, these properties are set in Xamarin.Android.Legacy.targets.
+ _CreatePropertiesCache;
_SeparateAppExtensionReferences;
$(ResolveReferencesDependsOn);
_ConvertAndroidMamMappingFileToXml;
@@ -116,17 +117,25 @@ projects, these properties are set in Xamarin.Android.Legacy.targets.
$(CoreResolveReferencesDependsOn);
- UpdateAndroidInterfaceProxies;
UpdateAndroidResources;
+ _BuildResourceDesigner;
+ UpdateAndroidInterfaceProxies;
_SetAndroidGenerateManagedBindings;
AddBindingsToCompile;
+ _CheckForInvalidDesignerConfig;
+
+ $(DesignTimeResolveAssemblyReferencesDependsOn);
+ _BuildResourceDesigner;
+
<_UpdateAndroidResourcesDependsOn>
$(CoreResolveReferencesDependsOn);
_CreatePropertiesCache;
_CheckForDeletedResourceFile;
_ComputeAndroidResourcePaths;
_UpdateAndroidResgen;
+ _CreateAar;
+ _BuildResourceDesigner;
_SetupMSBuildAllProjects;
@@ -134,6 +143,8 @@ projects, these properties are set in Xamarin.Android.Legacy.targets.
_AddAndroidDefines;
_IncludeLayoutBindingSources;
AddLibraryJarsToBind;
+ _BuildResourceDesigner;
+ _AddResourceDesignerFiles;
$(CompileDependsOn);
_CheckAndroidHttpClientHandlerType;
@@ -170,6 +181,7 @@ projects, these properties are set in Xamarin.Android.Legacy.targets.
_BeforeGetAndroidDependencies;
_SetLatestTargetFrameworkVersion;
_ResolveSdks;
+ _ResolveMonoAndroidSdks;
_ResolveAndroidTooling;
$(GetAndroidDependenciesDependsOn);
diff --git a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.DefaultProperties.targets b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.DefaultProperties.targets
index 687dc79e266..43ed246a334 100644
--- a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.DefaultProperties.targets
+++ b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.DefaultProperties.targets
@@ -10,6 +10,8 @@
true
Xamarin.Android.Net.AndroidMessageHandler
Xamarin.Android.Net.AndroidClientHandler
+ true
+ false
true
$(AndroidGenerateResourceDesigner)
false
diff --git a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.ILLink.targets b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.ILLink.targets
index f28c5b6d9d5..7400de192ff 100644
--- a/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.ILLink.targets
+++ b/src/Xamarin.Android.Build.Tasks/Microsoft.Android.Sdk/targets/Microsoft.Android.Sdk.ILLink.targets
@@ -88,7 +88,12 @@ This file contains the .NET 5-specific targets to customize ILLink
AfterStep="CleanStep"
Type="MonoDroid.Tuner.GetAssembliesStep"
/>
-
+ <_TrimmerCustomSteps
+ Condition=" '$(AndroidUseDesignerAssembly)' == 'true' "
+ Include="$(_AndroidLinkerCustomStepAssembly)"
+ BeforeStep="MarkStep"
+ Type="MonoDroid.Tuner.FixLegacyResourceDesignerStep"
+ />
<_PreserveLists Include="$(MSBuildThisFileDirectory)..\PreserveLists\*.xml" />
<_AndroidFilesToPublish Include="$(OutputPath)*.%(_AndroidPackageFormats.Identity)" />
<_AndroidFilesToPublish Include="$(AndroidProguardMappingFile)" Condition="Exists ('$(AndroidProguardMappingFile)')" />
+ <_AndroidFilesToPublish Include="$(_GenerateResourceDesignerAssemblyOutput)" Condition="Exists('$(_GenerateResourceDesignerAssemblyOutput)')" />
diff --git a/src/Xamarin.Android.Build.Tasks/Properties/Resources.Designer.cs b/src/Xamarin.Android.Build.Tasks/Properties/Resources.Designer.cs
index b2a969da7cc..c87254b93ce 100644
--- a/src/Xamarin.Android.Build.Tasks/Properties/Resources.Designer.cs
+++ b/src/Xamarin.Android.Build.Tasks/Properties/Resources.Designer.cs
@@ -1467,5 +1467,14 @@ public static string XA1033 {
return ResourceManager.GetString("XA1033", resourceCulture);
}
}
+
+ ///
+ /// Looks up a localized string similar to Your project references '{0}' which uses the `_Microsoft.Android.Resource.Designer` assembly, but you do not have this feature enabled. Please set the `AndroidUseDesignerAssembly` MSBuild property to `true` in your project file.
+ ///
+ public static string XA1034 {
+ get {
+ return ResourceManager.GetString("XA1034", resourceCulture);
+ }
+ }
}
}
diff --git a/src/Xamarin.Android.Build.Tasks/Properties/Resources.resx b/src/Xamarin.Android.Build.Tasks/Properties/Resources.resx
index ac48bbe6967..c36b272704b 100644
--- a/src/Xamarin.Android.Build.Tasks/Properties/Resources.resx
+++ b/src/Xamarin.Android.Build.Tasks/Properties/Resources.resx
@@ -487,6 +487,11 @@ Please change the value to an assembly-qualifed type name which inherits from '{
Could not resolve '{0}'. Please check your `AndroidHttpClientHandlerType` setting.
The following are literal names and should not be translated: 'AndroidHttpClientHandlerType',
{0} - The value of the property.
+
+
+ Your project references '{0}' which uses the `_Microsoft.Android.Resource.Designer` assembly, but you do not have this feature enabled. Please set the `AndroidUseDesignerAssembly` MSBuild property to `true` in your project file.
+ The following are literal names and should not be translated: '_Microsoft.Android.Resource.Desinger', 'AndroidUseDesignerAssembly', 'true'
+{0} - The name of the assembly
Use of AppDomain.CreateDomain() detected in assembly: {0}. .NET 6 and higher will only support a single AppDomain, so this API will no longer be available in Xamarin.Android once .NET 6 is released.
diff --git a/src/Xamarin.Android.Build.Tasks/Resources/Resource.Designer.snk b/src/Xamarin.Android.Build.Tasks/Resources/Resource.Designer.snk
new file mode 100644
index 00000000000..7f3d225c9d2
Binary files /dev/null and b/src/Xamarin.Android.Build.Tasks/Resources/Resource.Designer.snk differ
diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/Aapt2Link.cs b/src/Xamarin.Android.Build.Tasks/Tasks/Aapt2Link.cs
index c70cfb09fb7..4dd27c19321 100644
--- a/src/Xamarin.Android.Build.Tasks/Tasks/Aapt2Link.cs
+++ b/src/Xamarin.Android.Build.Tasks/Tasks/Aapt2Link.cs
@@ -82,6 +82,7 @@ public class Aapt2Link : Aapt2 {
List tempFiles = new List ();
SortedSet rulesFiles = new SortedSet ();
Dictionary apks = new Dictionary ();
+ string resourceSymbolsTextFileTemp;
protected override int GetRequiredDaemonInstances ()
{
@@ -93,6 +94,8 @@ public async override System.Threading.Tasks.Task RunTaskAsync ()
try {
assemblyMap.Load (Path.Combine (WorkingDirectory, AssemblyIdentityMapFile));
+ resourceSymbolsTextFileTemp = GetTempFile ();
+
await this.WhenAll (ManifestFiles, ProcessManifest);
ProcessOutput ();
@@ -132,6 +135,8 @@ public async override System.Threading.Tasks.Task RunTaskAsync ()
}
Files.CopyIfStringChanged (sb.ToString (), ProguardRuleOutput);
}
+ if (!string.IsNullOrEmpty (ResourceSymbolsTextFile))
+ Files.CopyIfChanged (resourceSymbolsTextFileTemp, GetFullPath (ResourceSymbolsTextFile));
} finally {
lock (tempFiles) {
foreach (var temp in tempFiles) {
@@ -253,7 +258,7 @@ string [] GenerateCommandLineCommands (string ManifestFile, string currentAbi, s
if (!string.IsNullOrEmpty (ResourceSymbolsTextFile)) {
cmd.Add ("--output-text-symbols");
- cmd.Add (GetFullPath (ResourceSymbolsTextFile));
+ cmd.Add (GetFullPath (resourceSymbolsTextFileTemp));
}
if (ProtobufFormat)
diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/AppendCustomMetadataToItemGroup.cs b/src/Xamarin.Android.Build.Tasks/Tasks/AppendCustomMetadataToItemGroup.cs
index ffd28e90201..9ab1d323596 100644
--- a/src/Xamarin.Android.Build.Tasks/Tasks/AppendCustomMetadataToItemGroup.cs
+++ b/src/Xamarin.Android.Build.Tasks/Tasks/AppendCustomMetadataToItemGroup.cs
@@ -35,6 +35,11 @@ public override bool RunTask ()
foreach (var item in Inputs) {
var fn = Path.GetFileNameWithoutExtension (item.ItemSpec);
output.Add (item);
+ var md = item.GetMetadata ("HasResourceDesignerAssemblyReference");
+ if (string.IsNullOrEmpty (md)) {
+ var b = MonoAndroidHelper.HasResourceDesignerAssemblyReference (item);
+ item.SetMetadata ("HasResourceDesignerAssemblyReference", MonoAndroidHelper.HasResourceDesignerAssemblyReference (item).ToString ());
+ }
List metaDataList;
if (!metaData.TryGetValue (fn, out metaDataList))
continue;
diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/ConvertCustomView.cs b/src/Xamarin.Android.Build.Tasks/Tasks/ConvertCustomView.cs
index ea7a65b7a57..9e88ff78d2f 100644
--- a/src/Xamarin.Android.Build.Tasks/Tasks/ConvertCustomView.cs
+++ b/src/Xamarin.Android.Build.Tasks/Tasks/ConvertCustomView.cs
@@ -31,7 +31,7 @@ public class ConvertCustomView : AndroidTask {
public override bool RunTask ()
{
- var acw_map = MonoAndroidHelper.LoadAcwMapFile (AcwMapFile);
+ var acw_map = MonoAndroidHelper.LoadMapFile (BuildEngine4, AcwMapFile, StringComparer.Ordinal);
var customViewMap = MonoAndroidHelper.LoadCustomViewMapFile (BuildEngine4, CustomViewMapFile);
var processed = new HashSet ();
@@ -119,8 +119,8 @@ bool TryFixCustomClassAttribute (XAttribute attr, Dictionary acw
bool TryFixFragment (XAttribute attr, Dictionary acwMap)
{
- // Looks for any:
- // ParseFile (StreamReader reader)
while (!reader.EndOfStream) {
var line = reader.ReadLine ();
var items = line.Split (Delimiter, 4);
- yield return items;
+ if (items.Length == 4)
+ yield return items;
}
}
}
diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GeneratePackageManagerJava.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GeneratePackageManagerJava.cs
index e8638b29761..a87f24a5832 100644
--- a/src/Xamarin.Android.Build.Tasks/Tasks/GeneratePackageManagerJava.cs
+++ b/src/Xamarin.Android.Build.Tasks/Tasks/GeneratePackageManagerJava.cs
@@ -471,7 +471,7 @@ void GetRequiredTokens (string assemblyFilePath, out int android_runtime_jnienv_
}
if (android_runtime_jnienv_class_token == -1 || jnienv_initialize_method_token == -1 || jnienv_registerjninatives_method_token == -1) {
- throw new InvalidOperationException ($"Unable to find the required Android.Runtime.JNIEnvInit method tokens");
+ throw new InvalidOperationException ($"Unable to find the required Android.Runtime.JNIEnvInit method tokens for {assemblyFilePath}");
}
}
diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateResourceCaseMap.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateResourceCaseMap.cs
new file mode 100644
index 00000000000..9717be99f74
--- /dev/null
+++ b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateResourceCaseMap.cs
@@ -0,0 +1,121 @@
+// Copyright (C) 2021 Microsoft, Inc. All rights reserved.
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Utilities;
+using Microsoft.Android.Build.Tasks;
+
+namespace Xamarin.Android.Tasks
+{
+ public class GenerateResourceCaseMap : AndroidTask
+ {
+ public override string TaskPrefix => "GRCM";
+ public ITaskItem[] Resources { get; set; }
+
+ [Required]
+ public string ResourceDirectory { get; set; }
+
+ [Required]
+ public string ProjectDir { get; set; }
+
+ public ITaskItem[] AdditionalResourceDirectories { get; set; }
+
+ [Required]
+ public ITaskItem OutputFile { get; set; }
+
+ private Dictionary resource_fixup = new Dictionary (StringComparer.OrdinalIgnoreCase);
+
+ public override bool RunTask ()
+ {
+ // ResourceDirectory may be a relative path, and
+ // we need to compare it to absolute paths
+ ResourceDirectory = Path.GetFullPath (ResourceDirectory);
+
+ // Create our capitalization maps so we can support mixed case resources
+ foreach (var item in Resources) {
+ var path = Path.GetFullPath (item.ItemSpec);
+ if (!path.StartsWith (ResourceDirectory, StringComparison.OrdinalIgnoreCase)) {
+ Log.LogDebugMessage ($"Skipping {item}. Path is not include the '{ResourceDirectory}'");
+ continue;
+ }
+
+ var name = path.Substring (ResourceDirectory.Length).TrimStart ('/', '\\');
+ var logical_name = item.GetMetadata ("LogicalName").Replace ('\\', '/');
+ if (string.IsNullOrEmpty (logical_name))
+ logical_name = Path.GetFileName (path);
+
+ AddRename (name.Replace ('/', Path.DirectorySeparatorChar), logical_name.Replace ('/', Path.DirectorySeparatorChar));
+ }
+ foreach (var additionalDir in AdditionalResourceDirectories ?? Array.Empty()) {
+ var dir = Path.Combine (ProjectDir, Path.GetDirectoryName (additionalDir.ItemSpec));
+ var file = Path.Combine (dir, "__res_name_case_map.txt");
+ if (!File.Exists (file)) {
+ // .NET 6 .aar files place the file in a sub-directory
+ file = Path.Combine (dir, ".net", "__res_name_case_map.txt");
+ if (!File.Exists (file))
+ continue;
+ }
+ foreach (var line in File.ReadLines (file)) {
+ if (string.IsNullOrEmpty (line))
+ continue;
+ string [] tok = line.Split (';');
+ AddRename (tok [1].Replace ('/', Path.DirectorySeparatorChar), tok [0].Replace ('/', Path.DirectorySeparatorChar));
+ }
+ }
+
+ if (MonoAndroidHelper.SaveMapFile (BuildEngine4, Path.GetFullPath (OutputFile.ItemSpec), resource_fixup)) {
+ Log.LogDebugMessage ($"Writing to: {OutputFile.ItemSpec}");
+ } else {
+ Log.LogDebugMessage ($"Up to date: {OutputFile.ItemSpec}");
+ }
+
+ return !Log.HasLoggedErrors;
+ }
+
+ private void AddRename (string android, string user)
+ {
+ var from = android;
+ var to = user;
+
+ if (from.Contains ('.'))
+ from = from.Substring (0, from.LastIndexOf ('.'));
+ if (to.Contains ('.'))
+ to = to.Substring (0, to.LastIndexOf ('.'));
+
+ from = NormalizeAlternative (from);
+ to = NormalizeAlternative (to);
+
+ string curTo;
+
+ if (resource_fixup.TryGetValue (from, out curTo)) {
+ if (string.Compare (to, curTo, StringComparison.OrdinalIgnoreCase) != 0) {
+ var ext = Path.GetExtension (android);
+ var dir = Path.GetDirectoryName (user);
+
+ Log.LogDebugMessage ("Resource target names differ; got '{0}', expected '{1}'.",
+ Path.Combine (dir, Path.GetFileName (to) + ext),
+ Path.Combine (dir, Path.GetFileName (curTo) + ext));
+ }
+ return;
+ }
+ Log.LogDebugMessage ($"Adding map from '{from}' to '{to}'.");
+ resource_fixup.Add (from, to);
+ }
+
+ static string NormalizeAlternative (string value)
+ {
+ int s = value.IndexOfAny (new[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar });
+
+ if (s < 0)
+ return value;
+
+ int a = value.IndexOf ('-');
+
+ return
+ ResourceParser.GetNestedTypeName (value.Substring (0, (a < 0 || a >= s) ? s : a)).ToLowerInvariant () +
+ value.Substring (s);
+ }
+ }
+}
diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateResourceDesigner.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateResourceDesigner.cs
index ba7fbe053a3..efb8fa465f5 100644
--- a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateResourceDesigner.cs
+++ b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateResourceDesigner.cs
@@ -53,6 +53,8 @@ public class GenerateResourceDesigner : AndroidTask
public string ResourceFlagFile { get; set; }
+ public string CaseMapFile { get; set; }
+
private Dictionary resource_fixup = new Dictionary (StringComparer.OrdinalIgnoreCase);
public override bool RunTask ()
@@ -73,35 +75,7 @@ public override bool RunTask ()
var javaPlatformDirectory = Path.GetDirectoryName (JavaPlatformJarPath);
- // Create our capitalization maps so we can support mixed case resources
- foreach (var item in Resources) {
- var path = Path.GetFullPath (item.ItemSpec);
- if (!path.StartsWith (ResourceDirectory, StringComparison.OrdinalIgnoreCase))
- continue;
-
- var name = path.Substring (ResourceDirectory.Length).TrimStart ('/', '\\');
- var logical_name = item.GetMetadata ("LogicalName").Replace ('\\', '/');
- if (string.IsNullOrEmpty (logical_name))
- logical_name = Path.GetFileName (path);
-
- AddRename (name.Replace ('/', Path.DirectorySeparatorChar), logical_name.Replace ('/', Path.DirectorySeparatorChar));
- }
- if (AdditionalResourceDirectories != null) {
- foreach (var additionalDir in AdditionalResourceDirectories) {
- var dir = Path.Combine (ProjectDir, Path.GetDirectoryName (additionalDir.ItemSpec));
- var file = Path.Combine (dir, "__res_name_case_map.txt");
- if (!File.Exists (file)) {
- // .NET 6 .aar files place the file in a sub-directory
- file = Path.Combine (dir, ".net", "__res_name_case_map.txt");
- if (!File.Exists (file))
- continue;
- }
- foreach (var line in File.ReadAllLines (file).Where (l => !string.IsNullOrEmpty (l))) {
- string [] tok = line.Split (';');
- AddRename (tok [1].Replace ('/', Path.DirectorySeparatorChar), tok [0].Replace ('/', Path.DirectorySeparatorChar));
- }
- }
- }
+ resource_fixup = MonoAndroidHelper.LoadMapFile (BuildEngine4, CaseMapFile, StringComparer.OrdinalIgnoreCase);
// Parse out the resources from the R.java file
CodeTypeDeclaration resources;
diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateResourceDesignerAssembly.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateResourceDesignerAssembly.cs
new file mode 100644
index 00000000000..ba009724f7b
--- /dev/null
+++ b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateResourceDesignerAssembly.cs
@@ -0,0 +1,350 @@
+// Copyright (C) 2011 Xamarin, Inc. All rights reserved.
+using System;
+using System.Collections.Generic;
+using System.Runtime.CompilerServices;
+using System.Runtime.Versioning;
+using System.IO;
+using System.Linq;
+using System.Text;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Utilities;
+using Microsoft.Android.Build.Tasks;
+using Mono.Cecil;
+using Mono.Cecil.Cil;
+using Java.Interop.Tools.Cecil;
+using Brutal.Dev.StrongNameSigner;
+using MonoDroid.Tuner;
+
+namespace Xamarin.Android.Tasks
+{
+ public class GenerateResourceDesignerAssembly : AndroidTask
+ {
+ public override string TaskPrefix => "GRDA";
+
+ [Required]
+ public ITaskItem RTxtFile { get; set; }
+
+ public ITaskItem ResourceMap { get; set; }
+
+ [Required]
+ public bool IsApplication { get; set; }
+
+ [Required]
+ public bool DesignTimeBuild { get; set; }
+
+ [Required]
+ public ITaskItem OutputFile { get; set; }
+
+ [Required]
+ public string TargetFrameworkVersion { get; set; }
+
+ [Required]
+ public string TargetFrameworkIdentifier { get; set; }
+
+ [Required]
+ public string ProjectDir { get; set; }
+
+ [Required]
+ public ITaskItem[] Resources { get; set; }
+
+ [Required]
+ public string ResourceDirectory { get; set; }
+ public string CaseMapFile { get; set; }
+ public ITaskItem[] AdditionalResourceDirectories { get; set; }
+ public ITaskItem[] FrameworkDirectories { get; set; }
+ public bool Deterministic { get; set; }
+ public string AssemblyName { get; set; }
+ TypeReference intArray;
+ TypeReference intRef;
+ TypeReference objectRef;
+ Dictionary resource_fixup = new Dictionary (StringComparer.OrdinalIgnoreCase);
+
+ public override bool RunTask ()
+ {
+ using (var res = new DirectoryAssemblyResolver (this.CreateTaskLogger (), loadDebugSymbols: false)) {
+ Run(res);
+ }
+ return !Log.HasLoggedErrors;
+ }
+
+ bool Run(DirectoryAssemblyResolver res)
+ {
+ foreach (var dir in FrameworkDirectories) {
+ if (Directory.Exists (dir.ItemSpec))
+ res.SearchDirectories.Add (dir.ItemSpec);
+ }
+ // ResourceDirectory may be a relative path, and
+ // we need to compare it to absolute paths
+ ResourceDirectory = Path.GetFullPath (ResourceDirectory);
+
+ string assemblyName = Path.GetFileNameWithoutExtension (OutputFile.ItemSpec);
+
+ resource_fixup = MonoAndroidHelper.LoadMapFile (BuildEngine4, CaseMapFile, StringComparer.OrdinalIgnoreCase);
+ // Generate an assembly which contains all the values in the provided
+ // R.txt file.
+ var mp = new ModuleParameters ();
+ mp.AssemblyResolver = res;
+ mp.Kind = ModuleKind.Dll;
+ var assembly = AssemblyDefinition.CreateAssembly (
+ new AssemblyNameDefinition (assemblyName, new Version (1, 0)),
+ assemblyName,
+ mp);
+
+ var module = assembly.MainModule;
+
+ module.AssemblyReferences.Clear ();
+ var netstandardAsm = AssemblyNameReference.Parse ("netstandard, Version=2.1.0.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51");
+ module.AssemblyReferences.Add(netstandardAsm);
+ var netstandardDef = module.AssemblyResolver.Resolve(netstandardAsm);
+
+ if (!IsApplication) {
+ MethodReference referenceAssemblyConstructor = ImportCustomAttributeConstructor ("System.Runtime.CompilerServices.ReferenceAssemblyAttribute", module, netstandardDef.MainModule);
+ module.Assembly.CustomAttributes.Add (new CustomAttribute (referenceAssemblyConstructor));
+ } else {
+ // Add the InternalsVisibleToAttribute so the app can access ResourceConstant
+ if (!string.IsNullOrEmpty (AssemblyName)) {
+ MethodReference internalsVisibleToAttributeConstructor = ImportCustomAttributeConstructor ("System.Runtime.CompilerServices.InternalsVisibleToAttribute", module, netstandardDef.MainModule);
+ var ar = new CustomAttribute (internalsVisibleToAttributeConstructor);
+ ar.ConstructorArguments.Add (new CustomAttributeArgument (module.TypeSystem.String, AssemblyName));
+ module.Assembly.CustomAttributes.Add (ar);
+ }
+ }
+
+ MethodReference targetFrameworkConstructor = ImportCustomAttributeConstructor ("System.Runtime.Versioning.TargetFrameworkAttribute", module, netstandardDef.MainModule);
+
+ var attr = new CustomAttribute (targetFrameworkConstructor);
+ attr.ConstructorArguments.Add (new CustomAttributeArgument (module.TypeSystem.String, $".NETStandard,Version=v2.1"));
+ attr.Properties.Add (new CustomAttributeNamedArgument ("FrameworkDisplayName", new CustomAttributeArgument (module.TypeSystem.String, "")));
+ module.Assembly.CustomAttributes.Add (attr);
+
+ var att = TypeAttributes.AutoClass | TypeAttributes.AnsiClass | TypeAttributes.Public | TypeAttributes.BeforeFieldInit;
+
+ intArray = new ArrayType (module.TypeSystem.Int32);
+ intRef = module.TypeSystem.Int32;
+ objectRef = module.TypeSystem.Object;
+
+ // The Property Based class.
+ var resourceDesigner = new TypeDefinition (
+ FixLegacyResourceDesignerStep.DesignerAssemblyNamespace,
+ "Resource",
+ att,
+ objectRef
+ );
+ CreateCtor (resourceDesigner, module);
+ module.Types.Add (resourceDesigner);
+ TypeDefinition constDesigner = null;
+ if (IsApplication) {
+ // The Constant based class
+ TypeAttributes attrib = string.IsNullOrEmpty (AssemblyName) ? TypeAttributes.Public : TypeAttributes.Public;
+ constDesigner = new TypeDefinition (
+ FixLegacyResourceDesignerStep.DesignerAssemblyNamespace,
+ "ResourceConstant",
+ attrib | TypeAttributes.AutoClass | TypeAttributes.AnsiClass | TypeAttributes.BeforeFieldInit,
+ objectRef
+ );
+ CreateCtor (constDesigner, module);
+ module.Types.Add (constDesigner);
+ }
+
+ DateTime lastWriteTimeUtc = DateTime.MinValue;
+ if (File.Exists (OutputFile.ItemSpec))
+ lastWriteTimeUtc = File.GetLastWriteTimeUtc (OutputFile.ItemSpec);
+
+ if (File.Exists (RTxtFile.ItemSpec)) {
+ if (File.GetLastWriteTimeUtc (RTxtFile.ItemSpec) < lastWriteTimeUtc) {
+ Log.LogDebugMessage ($"{RTxtFile.ItemSpec} has not changed since {OutputFile.ItemSpec} was generated.");
+ return !Log.HasLoggedErrors;
+ }
+ var parser = new RtxtParser ();
+ var resources = parser.Parse (RTxtFile.ItemSpec, Log, resource_fixup);
+ foreach (var r in resources) {
+ switch (r.Type) {
+ case RType.Integer:
+ if (IsApplication)
+ CreateIntField (r.ResourceTypeName, r.Identifier, r.Id, constDesigner, module);
+ CreateIntProperty (r.ResourceTypeName, r.Identifier, r.Id, resourceDesigner, module);
+ break;
+ case RType.Array:
+ if (IsApplication)
+ CreateIntArrayField (r.ResourceTypeName, r.Identifier, r.Ids, constDesigner, module);
+ CreateIntArrayProperty (r.ResourceTypeName, r.Identifier, r.Ids, resourceDesigner, module);
+ break;
+ }
+ }
+ }
+ // Add a return to each of the static constructor
+ foreach(var c in staticConstructors) {
+ var il = c.Value.Body.GetILProcessor ();
+ il.Emit(OpCodes.Ret);
+ }
+ StrongNameAssembly (assembly.Name);
+ var wp = new WriterParameters () {
+ DeterministicMvid = Deterministic,
+ };
+ var s = MemoryStreamPool.Shared.Rent ();
+ try {
+ assembly.Write (s, wp);
+ s.Position = 0;
+ if (Files.CopyIfStreamChanged (s, OutputFile.ItemSpec)) {
+ Log.LogDebugMessage ($"Updated '{OutputFile.ItemSpec}'.");
+ } else {
+ Log.LogDebugMessage ($"'{OutputFile.ItemSpec}' was up to date.");
+ }
+ } finally {
+ MemoryStreamPool.Shared.Return (s);
+ }
+ return !Log.HasLoggedErrors;
+ }
+
+ MethodReference ImportCustomAttributeConstructor (string type, ModuleDefinition module, ModuleDefinition sourceModule = null)
+ {
+ var tr = module.ImportReference ((sourceModule ?? module).ExportedTypes.First(x => x.FullName == type).Resolve ());
+ var tv = tr.Resolve();
+ return module.ImportReference (tv.Methods.First(x => x.IsConstructor));
+ }
+
+ void CreateIntProperty (string resourceClass, string propertyName, int value, TypeDefinition resourceDesigner, ModuleDefinition module,
+ MethodAttributes attributes = MethodAttributes.Public, TypeAttributes typeAttributes = TypeAttributes.NestedPublic)
+ {
+ TypeDefinition nestedType = CreateResourceClass (resourceDesigner, resourceClass, module, typeAttributes);
+ PropertyDefinition p = CreateProperty (propertyName, value, module, attributes);
+ nestedType.Properties.Add (p);
+ nestedType.Methods.Insert (Math.Max(0, nestedType.Methods.Count () - 1), p.GetMethod);
+ }
+
+ void CreateIntField (string resourceClass, string fieldName, int value, TypeDefinition resourceDesigner, ModuleDefinition module,
+ FieldAttributes attributes = FieldAttributes.Public, TypeAttributes typeAttributes = TypeAttributes.NestedPublic)
+ {
+ TypeDefinition nestedType = CreateResourceClass (resourceDesigner, resourceClass, module, typeAttributes);
+ FieldDefinition p = CreateField (fieldName, value, module, attributes);
+ nestedType.Fields.Add (p);
+ }
+
+ void CreateIntArrayProperty (string resourceClass, string propertyName, int[] values, TypeDefinition resourceDesigner, ModuleDefinition module,
+ MethodAttributes attributes = MethodAttributes.Public, TypeAttributes typeAttributes = TypeAttributes.NestedPublic)
+ {
+ TypeDefinition nestedType = CreateResourceClass (resourceDesigner, resourceClass, module, typeAttributes);
+ PropertyDefinition p = CreateArrayProperty (propertyName, values, module, attributes);
+ nestedType.Properties.Add (p);
+ nestedType.Methods.Insert (Math.Max(0, nestedType.Methods.Count () - 1), p.GetMethod);
+ }
+
+ void CreateIntArrayField (string resourceClass, string fieldName, int[] values, TypeDefinition resourceDesigner, ModuleDefinition module,
+ FieldAttributes attributes = FieldAttributes.Public, TypeAttributes typeAttributes = TypeAttributes.NestedPublic)
+ {
+ TypeDefinition nestedType = CreateResourceClass (resourceDesigner, resourceClass, module, typeAttributes);
+ FieldDefinition p = CreateArrayField (fieldName, values, module, attributes);
+ nestedType.Fields.Add (p);
+ MethodDefinition ctor = GetOrCreateStaticCtor (nestedType, module);
+ ILProcessor il = ctor.Body.GetILProcessor ();
+ il.Emit (OpCodes.Ldc_I4, values.Length); // store array size
+ il.Emit (OpCodes.Newarr, intRef); //create a new array
+ il.Emit (OpCodes.Stsfld, p);
+ int index = 0;
+ foreach (int value in values) {
+ il.Emit (OpCodes.Ldsfld, p);
+ il.Emit (OpCodes.Ldc_I4, index++); // index
+ il.Emit (OpCodes.Ldc_I4, value); // value
+ il.Emit (OpCodes.Stelem_I4);
+ }
+ }
+
+ Dictionary resourceClasses = new Dictionary (StringComparer.OrdinalIgnoreCase);
+ Dictionary staticConstructors = new Dictionary ();
+
+ void CreateCtor (TypeDefinition type, ModuleDefinition module)
+ {
+ var ctor = new MethodDefinition (".ctor", MethodAttributes.SpecialName | MethodAttributes.RTSpecialName | MethodAttributes.HideBySig | MethodAttributes.Public, module.TypeSystem.Void);
+ var ctoril = ctor.Body.GetILProcessor ();
+ ctoril.Emit (OpCodes.Ldarg_0);
+ var o = module.TypeSystem.Object.Resolve ();
+ ctoril.Emit (OpCodes.Call, module.ImportReference (o.Methods.First (x => x.IsConstructor)));
+ ctoril.Emit (OpCodes.Ret);
+ type.Methods.Add (ctor);
+ }
+
+ MethodDefinition GetOrCreateStaticCtor (TypeDefinition type, ModuleDefinition module)
+ {
+ string key = type.FullName + ".cctor";
+ if (staticConstructors.ContainsKey (key))
+ return staticConstructors[key];
+ var ctor = new MethodDefinition (".cctor", MethodAttributes.SpecialName | MethodAttributes.RTSpecialName | MethodAttributes.HideBySig | MethodAttributes.Static, module.TypeSystem.Void);
+ type.Methods.Add (ctor);
+ type.IsBeforeFieldInit = false;
+ staticConstructors.Add (key, ctor);
+ return ctor;
+ }
+
+ TypeDefinition CreateResourceClass (TypeDefinition resourceDesigner, string className, ModuleDefinition module, TypeAttributes attributes = TypeAttributes.NestedPublic)
+ {
+ string name = ResourceParser.GetNestedTypeName (className);
+ string key = resourceDesigner.Name + name;
+ if (resourceClasses.ContainsKey (key)) {
+ return resourceClasses[key];
+ }
+ var resourceClass = new TypeDefinition (string.Empty, name, attributes | TypeAttributes.AutoClass | TypeAttributes.AnsiClass | TypeAttributes.BeforeFieldInit | TypeAttributes.Sealed, objectRef);
+ CreateCtor (resourceClass, module);
+ resourceDesigner.NestedTypes.Add (resourceClass);
+ resourceClasses[key] = resourceClass;
+ return resourceClass;
+ }
+
+ PropertyDefinition CreateProperty (string propertyName, int value, ModuleDefinition module, MethodAttributes attributes = MethodAttributes.Public)
+ {
+ var p = new PropertyDefinition (propertyName, PropertyAttributes.None, intRef);
+ var getter = new MethodDefinition ($"get_{propertyName}", attributes | MethodAttributes.SpecialName | MethodAttributes.HideBySig | MethodAttributes.Static, intRef);
+ p.GetMethod = getter;
+ p.SetMethod = null;
+ var il = p.GetMethod.Body.GetILProcessor ();
+ il.Emit (OpCodes.Ldc_I4, value);
+ il.Emit (OpCodes.Ret);
+ return p;
+ }
+
+ FieldDefinition CreateField (string fieldName, int value, ModuleDefinition module, FieldAttributes attributes = FieldAttributes.Public)
+ {
+ var f = new FieldDefinition (fieldName, attributes | FieldAttributes.Literal | FieldAttributes.Static | FieldAttributes.HasDefault, intRef);
+ f.Constant = value;
+ return f;
+ }
+
+ FieldDefinition CreateArrayField (string fieldName, int[] values, ModuleDefinition module, FieldAttributes attributes = FieldAttributes.Public)
+ {
+ var f = new FieldDefinition (fieldName, attributes | FieldAttributes.Static | FieldAttributes.HasDefault, intArray);
+ f.Constant = values;
+ return f;
+ }
+
+ PropertyDefinition CreateArrayProperty (string propertyName, int[] values, ModuleDefinition module, MethodAttributes attributes = MethodAttributes.Public)
+ {
+ var p = new PropertyDefinition (propertyName, PropertyAttributes.None, intArray);
+ var getter = new MethodDefinition ($"get_{propertyName}", attributes | MethodAttributes.Static, intArray);
+ p.GetMethod = getter;
+ p.SetMethod = null;
+ var il = p.GetMethod.Body.GetILProcessor ();
+ il.Emit (OpCodes.Ldc_I4, values.Length);
+ il.Emit (OpCodes.Newarr, intRef);
+ int index = 0;
+ foreach (int value in values) {
+ il.Emit (OpCodes.Dup);
+ il.Emit (OpCodes.Ldc_I4, index++);
+ il.Emit (OpCodes.Ldc_I4, value);
+ il.Emit (OpCodes.Stelem_I4);
+ }
+ il.Emit (OpCodes.Ret);
+ return p;
+ }
+
+ void StrongNameAssembly (AssemblyNameDefinition name)
+ {
+ using (Stream stream = typeof (GenerateResourceDesignerAssembly).Assembly.GetManifestResourceStream ("Resource.Designer.snk")) {
+ byte[] publicKey = new byte[stream.Length];
+ stream.Read (publicKey, 0, publicKey.Length);
+ name.HashAlgorithm = AssemblyHashAlgorithm.SHA1;
+ name.PublicKey = SigningHelper.GetPublicKey (publicKey);
+ name.HasPublicKey = true;
+ name.Attributes |= AssemblyAttributes.PublicKey;
+ }
+ }
+ }
+}
diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateResourceDesignerIntermediateClass.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateResourceDesignerIntermediateClass.cs
new file mode 100644
index 00000000000..9247993a554
--- /dev/null
+++ b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateResourceDesignerIntermediateClass.cs
@@ -0,0 +1,53 @@
+using System;
+using System.IO;
+using System.CodeDom.Compiler;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Utilities;
+using Microsoft.Android.Build.Tasks;
+using MonoDroid.Tuner;
+
+namespace Xamarin.Android.Tasks
+{
+ public class GenerateResourceDesignerIntermediateClass : AndroidTask
+ {
+ public override string TaskPrefix => "GRDIC";
+
+ private const string ResourceDesigner = $"{FixLegacyResourceDesignerStep.DesignerAssemblyNamespace}.Resource";
+ private const string ResourceDesignerConstants = $"{FixLegacyResourceDesignerStep.DesignerAssemblyNamespace}.ResourceConstant";
+
+ private const string CSharpTemplate = @"// This is an Auto Generated file DO NOT EDIT
+using System;
+
+namespace %NAMESPACE% {
+ public partial class Resource : %BASECLASS% {
+ }
+}
+";
+ private const string FSharpTemplate = @"// This is an Auto Generated file DO NOT EDIT
+namespace %NAMESPACE%
+
+type Resource = %BASECLASS%
+";
+
+ public string Namespace { get; set; }
+ public bool IsApplication { get; set; } = false;
+ public ITaskItem OutputFile { get; set; }
+ public override bool RunTask ()
+ {
+ string ns = IsApplication ? ResourceDesignerConstants : ResourceDesigner;
+ var extension = Path.GetExtension (OutputFile.ItemSpec);
+ var language = string.Compare (extension, ".fs", StringComparison.OrdinalIgnoreCase) == 0 ? "F#" : CodeDomProvider.GetLanguageFromExtension (extension);
+ //bool isVB = string.Equals (extension, ".vb", StringComparison.OrdinalIgnoreCase);
+ bool isFSharp = string.Equals (language, "F#", StringComparison.OrdinalIgnoreCase);
+ bool isCSharp = string.Equals (language, "C#", StringComparison.OrdinalIgnoreCase);
+ string template = "";
+ if (isCSharp)
+ template = CSharpTemplate.Replace ("%NAMESPACE%", Namespace).Replace ("%BASECLASS%", ns);
+ else if (isFSharp)
+ template = FSharpTemplate.Replace ("%NAMESPACE%", Namespace).Replace ("%BASECLASS%", ns);
+
+ Files.CopyIfStringChanged (template, OutputFile.ItemSpec);
+ return !Log.HasLoggedErrors;
+ }
+ }
+}
diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/GenerateRtxt.cs b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateRtxt.cs
new file mode 100644
index 00000000000..a9927347626
--- /dev/null
+++ b/src/Xamarin.Android.Build.Tasks/Tasks/GenerateRtxt.cs
@@ -0,0 +1,48 @@
+// Copyright (C) 2022 Microsoft Ltd, Inc. All rights reserved.
+using System;
+using System.CodeDom;
+using System.CodeDom.Compiler;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Utilities;
+using Microsoft.Android.Build.Tasks;
+
+namespace Xamarin.Android.Tasks
+{
+ public class GenerateRtxt : AndroidTask
+ {
+ public override string TaskPrefix => "GR";
+
+ [Required]
+ public string RTxtFile { get; set; }
+
+ [Required]
+ public string ResourceDirectory { get; set; }
+ public string[] AdditionalResourceDirectories { get; set; }
+
+ [Required]
+ public string JavaPlatformJarPath { get; set; }
+
+ public string ResourceFlagFile { get; set; }
+ public string CaseMapFile { get; set; }
+
+ public override bool RunTask ()
+ {
+ // Parse the Resource files and then generate an R.txt file
+ var writer = new RtxtWriter ();
+
+ var resource_fixup = MonoAndroidHelper.LoadMapFile (BuildEngine4, CaseMapFile, StringComparer.OrdinalIgnoreCase);
+
+ var javaPlatformDirectory = Path.GetDirectoryName (JavaPlatformJarPath);
+ var parser = new FileResourceParser () { Log = Log, JavaPlatformDirectory = javaPlatformDirectory, ResourceFlagFile = ResourceFlagFile};
+ var resources = parser.Parse (ResourceDirectory, AdditionalResourceDirectories, resource_fixup);
+
+ // only update if it changed.
+ writer.Write (RTxtFile, resources);
+
+ return !Log.HasLoggedErrors;
+ }
+ }
+}
diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/LinkAssemblies.cs b/src/Xamarin.Android.Build.Tasks/Tasks/LinkAssemblies.cs
index 55c7bf7d804..67c72c382da 100644
--- a/src/Xamarin.Android.Build.Tasks/Tasks/LinkAssemblies.cs
+++ b/src/Xamarin.Android.Build.Tasks/Tasks/LinkAssemblies.cs
@@ -58,6 +58,8 @@ public class LinkAssemblies : AndroidTask, ML.ILogger
public bool LinkResources { get; set; }
+ public bool UseDesignerAssembly { get; set; }
+
IEnumerable GetRetainAssemblies (DirectoryAssemblyResolver res)
{
List retainList = null;
@@ -109,6 +111,7 @@ bool Execute (DirectoryAssemblyResolver res)
options.PreserveJniMarshalMethods = PreserveJniMarshalMethods;
options.DeterministicOutput = Deterministic;
options.LinkResources = LinkResources;
+ options.UseDesignerAssembly = UseDesignerAssembly;
var skiplist = new List ();
diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/LinkAssembliesNoShrink.cs b/src/Xamarin.Android.Build.Tasks/Tasks/LinkAssembliesNoShrink.cs
index 25da6cbde08..5d4c65c8509 100644
--- a/src/Xamarin.Android.Build.Tasks/Tasks/LinkAssembliesNoShrink.cs
+++ b/src/Xamarin.Android.Build.Tasks/Tasks/LinkAssembliesNoShrink.cs
@@ -38,6 +38,8 @@ public class LinkAssembliesNoShrink : AndroidTask
public bool AddKeepAlives { get; set; }
+ public bool UseDesignerAssembly { get; set; }
+
public bool Deterministic { get; set; }
public override bool RunTask ()
@@ -64,6 +66,9 @@ public override bool RunTask ()
var cache = new TypeDefinitionCache ();
var fixAbstractMethodsStep = new FixAbstractMethodsStep (resolver, cache, Log);
var addKeepAliveStep = new AddKeepAlivesStep (resolver, cache, Log, UsingAndroidNETSdk);
+ var fixLegacyResourceDesignerStep = new FixLegacyResourceDesignerStep (resolver, cache, Log);
+ if (UseDesignerAssembly)
+ fixLegacyResourceDesignerStep.Load ();
for (int i = 0; i < SourceFiles.Length; i++) {
var source = SourceFiles [i];
var destination = DestinationFiles [i];
@@ -91,8 +96,12 @@ public override bool RunTask ()
if (assemblyDefinition == null)
assemblyDefinition = resolver.GetAssembly (source.ItemSpec);
- if (fixAbstractMethodsStep.FixAbstractMethods (assemblyDefinition) ||
- (AddKeepAlives && addKeepAliveStep.AddKeepAlives (assemblyDefinition))) {
+ bool save = fixAbstractMethodsStep.FixAbstractMethods (assemblyDefinition);
+ if (UseDesignerAssembly)
+ save |= fixLegacyResourceDesignerStep.ProcessAssemblyDesigner (assemblyDefinition);
+ if (AddKeepAlives)
+ save |= addKeepAliveStep.AddKeepAlives (assemblyDefinition);
+ if (save) {
Log.LogDebugMessage ($"Saving modified assembly: {destination.ItemSpec}");
writerParameters.WriteSymbols = assemblyDefinition.MainModule.HasSymbols;
assemblyDefinition.Write (destination.ItemSpec, writerParameters);
@@ -119,6 +128,33 @@ void CopyIfChanged (ITaskItem source, ITaskItem destination)
}
}
+ class FixLegacyResourceDesignerStep : MonoDroid.Tuner.FixLegacyResourceDesignerStep
+ {
+ readonly DirectoryAssemblyResolver resolver;
+ readonly TaskLoggingHelper logger;
+
+ public FixLegacyResourceDesignerStep (DirectoryAssemblyResolver resolver, TypeDefinitionCache cache, TaskLoggingHelper logger)
+ : base (cache)
+ {
+ this.resolver = resolver;
+ this.logger = logger;
+ }
+
+ public void Load () {
+ LoadDesigner ();
+ }
+
+ public override void LogMessage (string message)
+ {
+ logger.LogDebugMessage ("{0}", message);
+ }
+
+ public override AssemblyDefinition Resolve (AssemblyNameReference name)
+ {
+ return resolver.Resolve (name);
+ }
+ }
+
class FixAbstractMethodsStep : MonoDroid.Tuner.FixAbstractMethodsStep
{
readonly DirectoryAssemblyResolver resolver;
diff --git a/src/Xamarin.Android.Build.Tasks/Tasks/ResolveLibraryProjectImports.cs b/src/Xamarin.Android.Build.Tasks/Tasks/ResolveLibraryProjectImports.cs
index f675f9f1bc9..f55838abac1 100644
--- a/src/Xamarin.Android.Build.Tasks/Tasks/ResolveLibraryProjectImports.cs
+++ b/src/Xamarin.Android.Build.Tasks/Tasks/ResolveLibraryProjectImports.cs
@@ -352,6 +352,7 @@ void Extract (
string importsDir = Path.Combine (outDirForDll, ImportsDirectory);
string resDir = Path.Combine (importsDir, "res");
string resDirArchive = Path.Combine (resDir, "..", "res.zip");
+ string rTxt = Path.Combine (importsDir, "R.txt");
string assetsDir = Path.Combine (importsDir, "assets");
string proguardFile = Path.Combine (importsDir, "proguard.txt");
@@ -367,7 +368,7 @@ void Extract (
AddJar (jars, Path.GetFullPath (file));
}
}
- if (Directory.Exists (resDir)) {
+ if (Directory.Exists (resDir) || File.Exists (rTxt)) {
var skipProcessing = aarFile.GetMetadata (AndroidSkipResourceProcessing);
if (string.IsNullOrEmpty (skipProcessing)) {
skipProcessing = "True";
@@ -424,8 +425,9 @@ void Extract (
Log.LogErrorFromException (new PathTooLongException ($"Error extracting resources from \"{aarFile.ItemSpec}\"", ex));
}
}
- if (Directory.Exists (resDir)) {
- CreateResourceArchive (resDir, resDirArchive);
+ if (Directory.Exists (resDir) || File.Exists (rTxt)) {
+ if (Directory.Exists (resDir))
+ CreateResourceArchive (resDir, resDirArchive);
var skipProcessing = aarFile.GetMetadata (AndroidSkipResourceProcessing);
if (string.IsNullOrEmpty (skipProcessing)) {
skipProcessing = "True";
diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/AndroidUpdateResourcesTest.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/AndroidUpdateResourcesTest.cs
index 6858b962cae..35b5bf82aed 100644
--- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/AndroidUpdateResourcesTest.cs
+++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/AndroidUpdateResourcesTest.cs
@@ -18,8 +18,11 @@ namespace Xamarin.Android.Build.Tests
public class AndroidUpdateResourcesTest : BaseTest
{
[Test]
- public void CheckMultipleLibraryProjectReferenceAlias ([Values (true, false)] bool withGlobal)
+ public void CheckMultipleLibraryProjectReferenceAlias ([Values (true, false)] bool withGlobal, [Values (true, false)] bool useDesignerAssembly)
{
+ if (useDesignerAssembly && !Builder.UseDotNet) {
+ Assert.Ignore ($"Skipping, {useDesignerAssembly} not supported in Legacy.");
+ }
var path = Path.Combine (Root, "temp", TestName);
var library1 = new XamarinAndroidLibraryProject () {
ProjectName = "Library1",
@@ -38,6 +41,9 @@ public void CheckMultipleLibraryProjectReferenceAlias ([Values (true, false)] bo
},
},
};
+ library1.SetProperty ("AndroidUseDesignerAssembly", "false");
+ library2.SetProperty ("AndroidUseDesignerAssembly", useDesignerAssembly.ToString ());
+ proj.SetProperty ("AndroidUseDesignerAssembly", useDesignerAssembly.ToString ());
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.");
@@ -47,10 +53,12 @@ public void CheckMultipleLibraryProjectReferenceAlias ([Values (true, false)] bo
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 resource_designer_cs = GetResourceDesignerPath (b, proj);
- string [] text = File.ReadAllLines (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.");
+ if (!useDesignerAssembly) {
+ string resource_designer_cs = GetResourceDesignerPath (b, proj);
+ string [] text = GetResourceDesignerLines (proj, 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.");
+ }
}
}
}
@@ -92,6 +100,7 @@ public void DesignTimeBuild ([Values(false, true)] bool isRelease, [Values (fals
IsRelease = isRelease,
};
lib.SetProperty ("AndroidUseManagedDesignTimeResourceGenerator", useManagedParser.ToString ());
+ lib.SetProperty ("AndroidUseDesignerAssembly", "false");
lib.AndroidUseAapt2 = useAapt2;
var proj = new XamarinAndroidApplicationProject () {
IsRelease = isRelease,
@@ -101,6 +110,7 @@ public void DesignTimeBuild ([Values(false, true)] bool isRelease, [Values (fals
};
var intermediateOutputPath = Path.Combine (path, proj.ProjectName, proj.IntermediateOutputPath);
proj.SetProperty ("AndroidUseManagedDesignTimeResourceGenerator", useManagedParser.ToString ());
+ proj.SetProperty ("AndroidUseDesignerAssembly", "false");
proj.AndroidUseAapt2 = useAapt2;
using (var l = CreateDllBuilder (Path.Combine (path, lib.ProjectName), false, false)) {
using (var b = CreateApkBuilder (Path.Combine (path, proj.ProjectName), false, false)) {
@@ -528,13 +538,20 @@ public void CheckResourceDesignerIsCreated (bool isRelease, ProjectLanguage lang
using (var b = CreateApkBuilder ()) {
Assert.IsTrue (b.Build (proj), "Build should have succeeded.");
// Intermediate designer file support is not compatible with F# projects using Xamarin.Android.FSharp.ResourceProvider.
- var outputFile = isFSharp ? Path.Combine (Root, b.ProjectDirectory, "Resources", "Resource.designer" + proj.Language.DefaultDesignerExtension)
- : Path.Combine (Root, b.ProjectDirectory, proj.IntermediateOutputPath, "Resource.designer" + proj.Language.DefaultDesignerExtension);
- Assert.IsTrue (File.Exists (outputFile), "Resource.designer{1} should have been created in {0}",
- isFSharp ? Path.Combine (Root, b.ProjectDirectory, "Resources") : proj.IntermediateOutputPath,
- proj.Language.DefaultDesignerExtension);
+ string outputFile;
+ if (Builder.UseDotNet) {
+ outputFile = Path.Combine (Root, b.ProjectDirectory, proj.IntermediateOutputPath, "__Microsoft.Android.Resource.Designer" + proj.Language.DefaultDesignerExtension);
+ Assert.IsTrue (File.Exists (outputFile), $"{outputFile} should have been created in {proj.IntermediateOutputPath}");
+ } else {
+ outputFile = isFSharp ? Path.Combine (Root, b.ProjectDirectory, "Resources", "Resource.designer" + proj.Language.DefaultDesignerExtension)
+ : Path.Combine (Root, b.ProjectDirectory, proj.IntermediateOutputPath, "Resource.designer" + proj.Language.DefaultDesignerExtension);
+ Assert.IsTrue (File.Exists (outputFile), "Resource.designer{1} should have been created in {0}",
+ isFSharp ? Path.Combine (Root, b.ProjectDirectory, "Resources") : proj.IntermediateOutputPath,
+ proj.Language.DefaultDesignerExtension);
+ }
+
Assert.IsTrue (b.Clean (proj), "Clean should have succeeded.");
- if (!isFSharp) {
+ if (!isFSharp || Builder.UseDotNet) {
Assert.IsFalse (File.Exists (outputFile), "Resource.designer{1} should have been cleaned in {0}",
proj.IntermediateOutputPath, proj.Language.DefaultDesignerExtension);
}
@@ -597,13 +614,12 @@ public void CheckOldResourceDesignerIsNotUsed ([Values (true, false)] bool isRel
var fi = new FileInfo (Path.Combine (Root, b.ProjectDirectory, designer));
Assert.IsFalse (fi.Length > new [] { 0xef, 0xbb, 0xbf, 0x0d, 0x0a }.Length,
"{0} should not contain anything.", designer);
+ var designerFile = Builder.UseDotNet ? "__Microsoft.Android.Resource.Designer" : "Resource.designer" ;
var outputFile = Path.Combine (Root, b.ProjectDirectory, proj.IntermediateOutputPath,
- "Resource.designer" + proj.Language.DefaultDesignerExtension);
- Assert.IsTrue (File.Exists (outputFile), "Resource.designer{1} should have been created in {0}",
- proj.IntermediateOutputPath, proj.Language.DefaultDesignerExtension);
+ designerFile + proj.Language.DefaultDesignerExtension);
+ Assert.IsTrue (File.Exists (outputFile), $"{designerFile}{proj.Language.DefaultDesignerExtension} should have been created in {proj.IntermediateOutputPath}");
Assert.IsTrue (b.Clean (proj), "Clean should have succeeded.");
- Assert.IsFalse (File.Exists (outputFile), "Resource.designer{1} should have been cleaned in {0}",
- proj.IntermediateOutputPath, proj.Language.DefaultDesignerExtension);
+ Assert.IsFalse (File.Exists (outputFile), $"{designerFile}{proj.Language.DefaultDesignerExtension} should have been cleaned in {proj.IntermediateOutputPath}");
}
}
@@ -625,13 +641,56 @@ public void CheckOldResourceDesignerWithWrongCasingIsRemoved ([Values (true, fal
Assert.IsFalse (File.Exists (Path.Combine (Root, b.ProjectDirectory, "Resources",
"Resource.designer" + proj.Language.DefaultDesignerExtension)),
"{0} should not exists", designer.Include ());
+ var designerFile = Builder.UseDotNet ? "__Microsoft.Android.Resource.Designer" : "Resource.designer" ;
var outputFile = Path.Combine (Root, b.ProjectDirectory, proj.IntermediateOutputPath,
- "Resource.designer" + proj.Language.DefaultDesignerExtension);
- Assert.IsTrue (File.Exists (outputFile), "Resource.designer{1} should have been created in {0}",
- proj.IntermediateOutputPath, proj.Language.DefaultDesignerExtension);
+ designerFile + proj.Language.DefaultDesignerExtension);
+ Assert.IsTrue (File.Exists (outputFile), $"{designerFile}{proj.Language.DefaultDesignerExtension} should have been created in {proj.IntermediateOutputPath}");
Assert.IsTrue (b.Clean (proj), "Clean should have succeeded.");
- Assert.IsFalse (File.Exists (outputFile), "Resource.designer{1} should have been cleaned in {0}",
- proj.IntermediateOutputPath, proj.Language.DefaultDesignerExtension);
+ Assert.IsFalse (File.Exists (outputFile), $"{designerFile}{proj.Language.DefaultDesignerExtension} should have been cleaned in {proj.IntermediateOutputPath}");
+ }
+ }
+
+ [Test]
+ public void CheckThatXA1034IsRaisedForInvalidConfiguration ([Values (true, false)] bool isRelease)
+ {
+ if (!Builder.UseDotNet)
+ Assert.Ignore ("Test uses designer assembly which does not work on Legacy projects.");
+ string path = Path.Combine (Root, "temp", TestName);
+ var foo = new BuildItem.Source ("Foo.cs") {
+ TextContent = () => @"using System;
+namespace Lib1 {
+ public class Foo {
+ public static string GetFoo () {
+ return ""Foo"";
+ }
+ }
+}"
+ };
+ var library = new XamarinAndroidLibraryProject () {
+ IsRelease = isRelease,
+ ProjectName = "Lib1",
+ Sources = { foo },
+ };
+ library.SetProperty ("AndroidUseDesignerAssembly", "True");
+
+ var proj = new XamarinAndroidApplicationProject () {
+ IsRelease = isRelease,
+ ProjectName = "App1",
+ References = {
+ new BuildItem.ProjectReference ($"..\\{library.ProjectName}\\{library.ProjectName}.csproj", library.ProjectName, library.ProjectGuid),
+ },
+ };
+ proj.SetProperty ("AndroidUseDesignerAssembly", "False");
+ proj.MainActivity = proj.DefaultMainActivity.Replace ("//${AFTER_ONCREATE}", "Console.WriteLine (Lib1.Foo.GetFoo ());");
+ using (var lb = CreateDllBuilder (Path.Combine (path, library.ProjectName))) {
+ lb.ThrowOnBuildFailure = false;
+ Assert.IsTrue (lb.Build (library), "Library project should have built.");
+ using (var pb = CreateApkBuilder (Path.Combine (path, proj.ProjectName))) {
+ pb.ThrowOnBuildFailure = false;
+ Assert.IsFalse (pb.Build (proj), "Application project build should have failed.");
+ StringAssertEx.ContainsText (pb.LastBuildOutput, "XA1034: ");
+ StringAssertEx.ContainsText (pb.LastBuildOutput, "1 Error(s)");
+ }
}
}
@@ -785,8 +844,10 @@ public void CheckFilesAreRemoved () {
}
[Test]
- public void CheckDontUpdateResourceIfNotNeeded ()
+ public void CheckDontUpdateResourceIfNotNeeded ([Values (true, false)] bool useDesignerAssembly)
{
+ if (!Builder.UseDotNet && useDesignerAssembly)
+ Assert.Ignore ("Test uses designer assembly which does not work on Legacy projects.");
var path = Path.Combine ("temp", TestName);
var target = Builder.UseDotNet ? "_CreateAar" : "_CreateManagedLibraryResourceArchive";
var foo = new BuildItem.Source ("Foo.cs") {
@@ -824,6 +885,7 @@ public string GetFoo () {
},
};
libProj.SetProperty ("Deterministic", "true");
+ libProj.SetProperty ("AndroidUseDesignerAssembly", useDesignerAssembly.ToString ());
var appProj = new XamarinAndroidApplicationProject () {
IsRelease = true,
ProjectName = "App1",
@@ -831,6 +893,7 @@ public string GetFoo () {
new BuildItem.ProjectReference (@"..\Lib1\Lib1.csproj", libProj.ProjectName, libProj.ProjectGuid),
},
};
+ appProj.SetProperty ("AndroidUseDesignerAssembly", useDesignerAssembly.ToString ());
using (var libBuilder = CreateDllBuilder (Path.Combine (path, libProj.ProjectName), false, false)) {
Assert.IsTrue (libBuilder.Build (libProj), "Library project should have built");
using (var appBuilder = CreateApkBuilder (Path.Combine (path, appProj.ProjectName), false, false)) {
@@ -864,7 +927,7 @@ public string GetFoo () {
appBuilder.BuildLogFile = "build2.log";
Assert.IsTrue (appBuilder.Build (appProj, doNotCleanupOnUpdate: true, saveProject: false), "Application Build should have succeeded.");
string resource_designer_cs = GetResourceDesignerPath (appBuilder, appProj);
- string text = File.ReadAllText (resource_designer_cs);
+ string text = GetResourceDesignerText (appProj, resource_designer_cs);
StringAssert.Contains ("theme_devicedefault_background2", text, "Resource.designer.cs was not updated.");
appBuilder.Output.AssertTargetIsNotSkipped ("_UpdateAndroidResgen");
appBuilder.Output.AssertTargetIsNotSkipped ("_CreateBaseApk");
@@ -904,6 +967,7 @@ public void BuildAppWithManagedResourceParser()
ProjectName = "App1",
};
appProj.SetProperty ("AndroidUseManagedDesignTimeResourceGenerator", "True");
+ appProj.SetProperty ("AndroidUseDesignerAssembly", "false");
using (var appBuilder = CreateApkBuilder (Path.Combine (path, appProj.ProjectName))) {
Assert.IsTrue (appBuilder.DesignTimeBuild (appProj), "DesignTime Application Build should have succeeded.");
Assert.IsFalse (appProj.CreateBuildOutput (appBuilder).IsTargetSkipped ("_ManagedUpdateAndroidResgen"),
@@ -961,6 +1025,7 @@ public void BuildAppWithManagedResourceParserAndLibraries ()
},
};
libProj.SetProperty ("AndroidUseManagedDesignTimeResourceGenerator", "True");
+ libProj.SetProperty ("AndroidUseDesignerAssembly", "false");
var appProj = new XamarinAndroidApplicationProject () {
IsRelease = true,
ProjectName = "App1",
@@ -978,6 +1043,7 @@ public void BuildAppWithManagedResourceParserAndLibraries ()
},
};
appProj.SetProperty ("AndroidUseManagedDesignTimeResourceGenerator", "True");
+ appProj.SetProperty ("AndroidUseDesignerAssembly", "false");
using (var libBuilder = CreateDllBuilder (Path.Combine (path, libProj.ProjectName), false, false)) {
libBuilder.AutomaticNuGetRestore = false;
Assert.IsTrue (libBuilder.RunTarget (libProj, "Restore"), "Library project should have restored.");
diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/AotTests.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/AotTests.cs
index f9641bff999..3f2ee58ed3a 100644
--- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/AotTests.cs
+++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/AotTests.cs
@@ -276,6 +276,14 @@ public void BuildAMassiveApp ()
AotAssemblies = true,
IsRelease = true,
};
+ if (Builder.UseDotNet) {
+ //TODO Re-enable if this test fails.
+ // app1.PackageReferences.Clear ();
+ // app1.PackageReferences.Add (KnownPackages.XamarinForms_5_0_0_2515);
+ // app1.PackageReferences.Add (KnownPackages.XamarinFormsMaps_5_0_0_2515);
+ // app1.PackageReferences.Add (KnownPackages.Xamarin_Build_Download_0_11_3);
+
+ }
//NOTE: BuildingInsideVisualStudio prevents the projects from being built as dependencies
sb.BuildingInsideVisualStudio = false;
app1.Imports.Add (new Import ("foo.targets") {
diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/BuildTest.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/BuildTest.cs
index 3e777babfde..e5a7d05a09c 100644
--- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/BuildTest.cs
+++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/BuildTest.cs
@@ -477,8 +477,11 @@ public void CheckItemMetadata ([Values (true, false)] bool isRelease)
// Context https://bugzilla.xamarin.com/show_bug.cgi?id=29706
[Test]
- public void CheckLogicalNamePathSeperators ([Values (false, true)] bool isRelease)
+ public void CheckLogicalNamePathSeperators ([Values (false, true)] bool isRelease, [Values (false, true)] bool useDesignerAssembly)
{
+ if (useDesignerAssembly && !Builder.UseDotNet) {
+ Assert.Ignore ($"Skipping, {useDesignerAssembly} not supported in Legacy.");
+ }
var illegalSeperator = IsWindows ? "/" : @"\";
var dll = new XamarinAndroidLibraryProject () {
ProjectName = "Library1",
@@ -503,20 +506,18 @@ public void CheckLogicalNamePathSeperators ([Values (false, true)] bool isReleas
new BuildItem ("ProjectReference","..\\Library1\\Library1.csproj"),
},
};
+ if (!useDesignerAssembly)
+ dll.SetProperty ("AndroidUseDesignerAssembly", useDesignerAssembly.ToString ());
+ proj.SetProperty ("AndroidUseDesignerAssembly", useDesignerAssembly.ToString ());
var path = Path.Combine ("temp", TestName);
using (var b = CreateDllBuilder (Path.Combine (path, dll.ProjectName))) {
Assert.IsTrue (b.Build (dll), "Build should have succeeded.");
using (var builder = CreateApkBuilder (Path.Combine (path, proj.ProjectName), isRelease)) {
Assert.IsTrue (builder.Build (proj), "Build should have succeeded");
- string resource_designer_cs;
- if (Builder.UseDotNet) {
- resource_designer_cs = Path.Combine (Root, builder.ProjectDirectory, proj.IntermediateOutputPath, "Resource.designer.cs");
- } else {
- resource_designer_cs = Path.Combine (Root, builder.ProjectDirectory, "Resources", "Resource.designer.cs");
- }
- var contents = File.ReadAllText (resource_designer_cs);
- StringAssert.Contains ("public const int foo = ", contents);
- StringAssert.Contains ("public const int foo2 = ", contents);
+ string resource_designer_cs = GetResourceDesignerPath (builder, proj);
+ var contents = GetResourceDesignerText (proj, resource_designer_cs);
+ StringAssert.Contains ("public const int foo =", contents);
+ StringAssert.Contains ("public const int foo2 =", contents);
}
}
}
@@ -2020,8 +2021,11 @@ public void LibraryReferenceWithHigherTFVShouldDisplayWarning ([Values (true, fa
}
[Test]
- public void AllResourcesInClassLibrary ([Values (true, false)] bool useAapt2)
+ public void AllResourcesInClassLibrary ([Values (true, false)] bool useAapt2, [Values (false, true)] bool useDesignerAssembly)
{
+ if (useDesignerAssembly && !Builder.UseDotNet) {
+ Assert.Ignore ($"Skipping, {useDesignerAssembly} not supported in Legacy.");
+ }
AssertAaptSupported (useAapt2);
var path = Path.Combine ("temp", TestName);
@@ -2035,6 +2039,7 @@ public void AllResourcesInClassLibrary ([Values (true, false)] bool useAapt2)
}
};
lib.SetProperty ("AndroidApplication", "False");
+ lib.SetProperty ("AndroidUseDesignerAssembly", useDesignerAssembly.ToString ());
lib.AndroidUseAapt2 = useAapt2;
if (Builder.UseDotNet) {
lib.RemoveProperty ("OutputType");
@@ -2054,6 +2059,7 @@ public void AllResourcesInClassLibrary ([Values (true, false)] bool useAapt2)
},
}
};
+ app.SetProperty ("AndroidUseDesignerAssembly", useDesignerAssembly.ToString ());
app.AndroidResources.Clear (); // No Resources
if (Builder.UseDotNet) {
app.SetProperty (KnownProperties.OutputType, "Exe");
@@ -2075,7 +2081,7 @@ public void AllResourcesInClassLibrary ([Values (true, false)] bool useAapt2)
var resource_designer_cs = GetResourceDesignerPath (appBuilder, app);
FileAssert.Exists (resource_designer_cs);
- var contents = File.ReadAllText (resource_designer_cs);
+ var contents = GetResourceDesignerText (app, resource_designer_cs);
Assert.AreNotEqual ("", contents);
}
}
diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/DesignerTests.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/DesignerTests.cs
index 925e80472c4..63c6603fed7 100644
--- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/DesignerTests.cs
+++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/DesignerTests.cs
@@ -93,10 +93,14 @@ public CustomTextView(Context context, IAttributeSet attributes) : base(context,
"
});
+ lib.SetProperty ("AndroidUseDesignerAssembly", "False");
+ proj.SetProperty ("AndroidUseDesignerAssembly", "False");
+
using (var libb = CreateDllBuilder (Path.Combine (path, lib.ProjectName), false, false))
using (var appb = CreateApkBuilder (Path.Combine (path, proj.ProjectName), false, false)) {
// Save the library project, but don't build it yet
libb.Save (lib);
+ appb.BuildLogFile = "build1.log";
appb.Target = target;
Assert.IsTrue (appb.Build (proj, parameters: DesignerParameters), $"build should have succeeded for target `{target}`");
Assert.IsTrue (appb.Output.AreTargetsAllBuilt ("_UpdateAndroidResgen"), "_UpdateAndroidResgen should have run completely.");
@@ -111,6 +115,7 @@ public CustomTextView(Context context, IAttributeSet attributes) : base(context,
// Build the library project now
Assert.IsTrue (libb.Build (lib, doNotCleanupOnUpdate: true), "library build should have succeeded.");
appb.Target = "Build";
+ appb.BuildLogFile = "build2.log";
Assert.IsTrue (appb.Build (proj, doNotCleanupOnUpdate: true), "app build should have succeeded.");
Assert.IsTrue (appb.Output.AreTargetsAllBuilt ("_UpdateAndroidResgen"), "_UpdateAndroidResgen should have run completely.");
Assert.IsTrue (appb.Output.AreTargetsAllBuilt ("_Foo"), "_Foo should have run completely");
@@ -120,6 +125,7 @@ public CustomTextView(Context context, IAttributeSet attributes) : base(context,
Assert.IsNull (doc.Element ("LinearLayout").Element ("unnamedproject.CustomTextView"),
"unnamedproject.CustomTextView should have been replaced with a $(Hash).CustomTextView");
appb.Target = target;
+ appb.BuildLogFile = "build3.log";
Assert.IsTrue (appb.Build (proj, parameters: DesignerParameters, doNotCleanupOnUpdate: true), $"build should have succeeded for target `{target}`");
Assert.IsTrue (appb.Output.AreTargetsAllSkipped ("_UpdateAndroidResgen"), "_UpdateAndroidResgen should have been skipped.");
Assert.IsTrue (appb.Output.AreTargetsAllBuilt ("_Foo"), "_Foo should have run completely");
diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/IncrementalBuildTest.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/IncrementalBuildTest.cs
index 3aa68042cdd..24fe06606b7 100644
--- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/IncrementalBuildTest.cs
+++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/IncrementalBuildTest.cs
@@ -156,6 +156,7 @@ public void IncrementalCleanDuringClean ()
IsRelease = true,
};
proj.SetProperty ("AndroidUseManagedDesignTimeResourceGenerator", "True");
+ proj.SetProperty ("AndroidUseDesignerAssembly", "False");
using (var b = CreateApkBuilder (path)) {
b.Target = "Compile";
Assert.IsTrue(b.Build (proj), "DesignTime Build should have succeeded");
@@ -830,6 +831,7 @@ public void ResolveLibraryProjectImports ([Values (true, false)] bool useAapt2)
FileAssert.Exists (stamp);
File.Delete (stamp);
+ b.BuildLogFile = "build2.log";
Assert.IsTrue (b.Build (proj), "second build should have succeeded.");
var actual = ReadCache (cacheFile);
CollectionAssert.AreEqual (actual.Jars.Select (j => j.ItemSpec),
@@ -843,6 +845,7 @@ public void ResolveLibraryProjectImports ([Values (true, false)] bool useAapt2)
};
proj.OtherBuildItems.Add (aar);
+ b.BuildLogFile = "build3.log";
Assert.IsTrue (b.Build (proj), "third build should have succeeded.");
actual = ReadCache (cacheFile);
Assert.AreEqual (expected.Jars.Length + 1, actual.Jars.Length,
@@ -856,6 +859,7 @@ public void ResolveLibraryProjectImports ([Values (true, false)] bool useAapt2)
}
// Build with no changes, checking we are skipping targets appropriately
+ b.BuildLogFile = "build4.log";
Assert.IsTrue (b.Build (proj), "fourth build should have succeeded.");
var targets = new List {
"_UpdateAndroidResgen",
@@ -1274,6 +1278,7 @@ public void AndroidResourceChange ()
// AndroidResource change
proj.LayoutMain += $"{Environment.NewLine}";
proj.Touch ("Resources\\layout\\Main.axml");
+ builder.BuildLogFile = "build2.log";
Assert.IsTrue (builder.Build (proj), "second build should succeed");
builder.Output.AssertTargetIsSkipped ("_ResolveLibraryProjectImports");
diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/PackagingTest.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/PackagingTest.cs
index 3601da8a073..f69eeb8b5c1 100644
--- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/PackagingTest.cs
+++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/PackagingTest.cs
@@ -95,6 +95,7 @@ public void CheckIncludedAssemblies ([Values (false, true)] bool usesAssemblySto
"System.Runtime.InteropServices.dll",
"System.Linq.dll",
"UnnamedProject.dll",
+ "_Microsoft.Android.Resource.Designer.dll",
} :
new [] {
"Java.Interop.dll",
diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/GenerateResourceCaseMapTests.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/GenerateResourceCaseMapTests.cs
new file mode 100644
index 00000000000..8cfd159b009
--- /dev/null
+++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/GenerateResourceCaseMapTests.cs
@@ -0,0 +1,51 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using Microsoft.Build.Utilities;
+using NUnit.Framework;
+using Xamarin.Android.Tasks;
+using Xamarin.ProjectTools;
+
+namespace Xamarin.Android.Build.Tests {
+ [TestFixture]
+ [Category ("Node-5")]
+ [Parallelizable (ParallelScope.Children)]
+ public class GenerateResourceCaseMapTests : BaseTest {
+
+ public void CreateResourceDirectory (string path)
+ {
+ Directory.CreateDirectory (Path.Combine (Root, path));
+ Directory.CreateDirectory (Path.Combine (Root, path, "res", "drawable"));
+ Directory.CreateDirectory (Path.Combine (Root, path, "res", "values"));
+ using (var stream = typeof (XamarinAndroidCommonProject).Assembly.GetManifestResourceStream ("Xamarin.ProjectTools.Resources.Base.Icon.png")) {
+ var icon_binary_mdpi = new byte [stream.Length];
+ stream.Read (icon_binary_mdpi, 0, (int)stream.Length);
+ File.WriteAllBytes (Path.Combine (Root, path, "res", "drawable", "IMALLCAPS.png"), icon_binary_mdpi);
+ }
+ }
+
+ [Test]
+ public void CaseMapAllCapsWorks ()
+ {
+ var path = Path.Combine ("temp", TestName + " Some Space");
+ CreateResourceDirectory (path);
+ var task = new GenerateResourceCaseMap () {
+ BuildEngine = new MockBuildEngine (TestContext.Out)
+ };
+ task.ProjectDir = Path.Combine (Root, path);
+ task.ResourceDirectory = Path.Combine (Root, path, "res") + Path.DirectorySeparatorChar;
+ task.Resources = new TaskItem [] {
+ new TaskItem (Path.Combine (Root, path, "res", "values", "strings.xml"), new Dictionary () {
+ { "LogicalName", "values\\strings.xml" },
+ }),
+ new TaskItem (Path.Combine (Root, path, "res", "drawable", "IMALLCAPS.png")),
+ };
+ task.OutputFile = new TaskItem (Path.Combine (Root, path, "case_map.txt"));
+
+ Assert.IsTrue (task.Execute (), "Task should have run successfully.");
+ FileAssert.Exists (task.OutputFile.ItemSpec, $"'{task.OutputFile}' should have been created.");
+ var content1 = File.ReadAllText (task.OutputFile.ItemSpec);
+ StringAssert.Contains ($"drawable{Path.DirectorySeparatorChar}IMALLCAPS;IMALLCAPS", content1, "File should contain 'IMALLCAPS'");
+ }
+ }
+}
diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/ManagedResourceParserTests.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/ManagedResourceParserTests.cs
index 55737bd0610..7a6f89c90b2 100644
--- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/ManagedResourceParserTests.cs
+++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Tasks/ManagedResourceParserTests.cs
@@ -230,7 +230,7 @@ int string app_name 0x7f110000
int string foo 0x7f110002
int string hello 0x7f110003
int string menu_settings 0x7f110004
-int[] styleable CustomFonts { 0x10100D2, 0x7F040000, 0x7F040000, 0x7F040001 }
+int[] styleable CustomFonts { 0x010100d2, 0x7f040000, 0x7f040000, 0x7f040001 }
int styleable CustomFonts_android_scrollX 0
int styleable CustomFonts_customFont 1
int styleable CustomFonts_customFont 2
@@ -309,6 +309,7 @@ void BuildLibraryWithResources (string path)
var libraryStrings = library.AndroidResources.FirstOrDefault (r => r.Include () == @"Resources\values\Strings.xml");
+ library.SetProperty ("AndroidUseDesignerAssembly", "false");
library.AndroidResources.Clear ();
library.AndroidResources.Add (libraryStrings);
library.AndroidResources.Add (new AndroidItem.AndroidResource (Path.Combine ("Resources", "animator", "slide_in_bottom.xml")) { TextContent = () => Animator });
@@ -355,6 +356,25 @@ void CompareFilesIgnoreRuntimeInfoString (string file1, string file2)
}
}
+ GenerateResourceCaseMap CreateCaseMapTask (string path)
+ {
+ var task = new GenerateResourceCaseMap () {
+ BuildEngine = new MockBuildEngine (TestContext.Out)
+ };
+ task.ProjectDir = Path.Combine (Root, path);
+ task.ResourceDirectory = Path.Combine (Root, path, "res") + Path.DirectorySeparatorChar;
+ task.Resources = new TaskItem [] {
+ new TaskItem (Path.Combine (Root, path, "res", "values", "strings.xml"), new Dictionary () {
+ { "LogicalName", "values\\strings.xml" },
+ }),
+ };
+ task.AdditionalResourceDirectories = new TaskItem [] {
+ new TaskItem (Path.Combine (Root, path, "lp", "res")),
+ };
+ task.OutputFile = new TaskItem (Path.Combine (Root, path, "case_map.txt"));
+ return task;
+ }
+
GenerateResourceDesigner CreateTask (string path)
{
var task = new GenerateResourceDesigner {
@@ -375,6 +395,7 @@ GenerateResourceDesigner CreateTask (string path)
task.AdditionalResourceDirectories = new TaskItem [] {
new TaskItem (Path.Combine (Root, path, "lp", "res")),
};
+ task.CaseMapFile = Path.Combine (Root, path, "case_map.txt");
task.IsApplication = true;
task.JavaPlatformJarPath = Path.Combine (AndroidSdkDirectory, "platforms", "android-27", "android.jar");
return task;
@@ -400,6 +421,8 @@ public void GenerateDesignerFileWithÜmläüts ()
{
var path = Path.Combine ("temp", TestName + " Some Space");
CreateResourceDirectory (path);
+ var mapTask = CreateCaseMapTask (path);
+ Assert.IsTrue (mapTask.Execute (), "Map Task should have executed successfully.");
var task = CreateTask (path);
Assert.IsTrue (task.Execute (), "Task should have executed successfully.");
AssertResourceDesigner (task, "GenerateDesignerFileExpected.cs");
@@ -411,6 +434,8 @@ public void GenerateDesignerFileFromRtxt ([Values (false, true)] bool withLibrar
{
var path = Path.Combine ("temp", TestName + " Some Space");
CreateResourceDirectory (path);
+ var mapTask = CreateCaseMapTask (path);
+ Assert.IsTrue (mapTask.Execute (), "Map Task should have executed successfully.");
var task = CreateTask (path);
task.RTxtFile = Path.Combine (Root, path, "R.txt");
File.WriteAllText (task.RTxtFile, Rtxt);
@@ -444,6 +469,8 @@ public void UpdateLayoutIdIsIncludedInDesigner ([Values(true, false)] bool useRt
{
var path = Path.Combine ("temp", TestName + " Some Space");
CreateResourceDirectory (path);
+ var mapTask = CreateCaseMapTask (path);
+ Assert.IsTrue (mapTask.Execute (), "Map Task should have executed successfully.");
if (useRtxt)
File.WriteAllText (Path.Combine (Root, path, "R.txt"), Rtxt);
IBuildEngine engine = new MockBuildEngine (TestContext.Out);
@@ -465,6 +492,7 @@ public void UpdateLayoutIdIsIncludedInDesigner ([Values(true, false)] bool useRt
new TaskItem (Path.Combine (Root, path, "lp", "res")),
};
task.ResourceFlagFile = Path.Combine (Root, path, "AndroidResgen.flag");
+ task.CaseMapFile = Path.Combine (Root, path, "case_map.txt");
File.WriteAllText (task.ResourceFlagFile, string.Empty);
task.IsApplication = true;
task.JavaPlatformJarPath = Path.Combine (AndroidSdkDirectory, "platforms", "android-27", "android.jar");
@@ -484,6 +512,38 @@ public void UpdateLayoutIdIsIncludedInDesigner ([Values(true, false)] bool useRt
Directory.Delete (Path.Combine (Root, path), recursive: true);
}
+ [Test]
+ [Category ("SmokeTests")]
+ public void RtxtGeneratorOutput ()
+ {
+ var path = Path.Combine ("temp", TestName);
+ int platform = AndroidSdkResolver.GetMaxInstalledPlatform ();
+ string resPath = Path.Combine (Root, path, "res");
+ string rTxt = Path.Combine (Root, path, "R.txt");
+ string expectedrTxt = Path.Combine (Root, path, "expectedR.txt");
+ CreateResourceDirectory (path);
+ File.WriteAllText (expectedrTxt, Rtxt);
+ List errors = new List ();
+ List messages = new List ();
+ IBuildEngine engine = new MockBuildEngine (TestContext.Out, errors: errors, messages: messages);
+ var generateRtxt = new GenerateRtxt () {
+ BuildEngine = engine,
+ RTxtFile = rTxt,
+ ResourceDirectory = resPath,
+ JavaPlatformJarPath = Path.Combine (AndroidSdkDirectory, "platforms", $"android-{platform}", "android.jar"),
+ ResourceFlagFile = Path.Combine (Root, path, "res.flag"),
+ AdditionalResourceDirectories = new string[] {
+ Path.Combine (Root, path, "lp", "res"),
+ },
+ };
+ Assert.IsTrue (generateRtxt.Execute (), "Task should have succeeded.");
+ FileAssert.Exists (rTxt, $"{rTxt} should have been created.");
+
+ CompareFilesIgnoreRuntimeInfoString (rTxt, expectedrTxt);
+
+ Directory.Delete (Path.Combine (Root, path), recursive: true);
+ }
+
[Test]
[Category ("SmokeTests")]
public void CompareAapt2AndManagedParserOutput ()
diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Utilities/BaseTest.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Utilities/BaseTest.cs
index d773e523ceb..a8f678a4d9a 100644
--- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Utilities/BaseTest.cs
+++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Utilities/BaseTest.cs
@@ -12,6 +12,8 @@
using Xamarin.ProjectTools;
using Microsoft.Android.Build.Tasks;
using System.Runtime.CompilerServices;
+using ICSharpCode.Decompiler;
+using ICSharpCode.Decompiler.CSharp;
namespace Xamarin.Android.Build.Tests
{
@@ -556,12 +558,37 @@ protected string GetResourceDesignerPath (ProjectBuilder builder, XamarinAndroid
string path;
if (Builder.UseDotNet) {
path = Path.Combine (Root, builder.ProjectDirectory, project.IntermediateOutputPath);
+ if (string.Compare (project.GetProperty ("AndroidUseDesignerAssembly"), "True", ignoreCase: true) == 0) {
+ return Path.Combine (path, "_Microsoft.Android.Resource.Designer.dll");
+ }
} else {
path = Path.Combine (Root, builder.ProjectDirectory, "Resources");
}
return Path.Combine (path, "Resource.designer" + project.Language.DefaultDesignerExtension);
}
+ protected string GetResourceDesignerText (XamarinAndroidProject project, string path)
+ {
+ if (Builder.UseDotNet) {
+ if (string.Compare (project.GetProperty ("AndroidUseDesignerAssembly"), "True", ignoreCase: true) == 0) {
+ var decompiler = new CSharpDecompiler (path, new DecompilerSettings () { });
+ return decompiler.DecompileWholeModuleAsString ();
+ }
+ }
+ return File.ReadAllText (path);
+ }
+
+ protected string[] GetResourceDesignerLines (XamarinAndroidProject project, string path)
+ {
+ if (Builder.UseDotNet) {
+ if (string.Compare (project.GetProperty ("AndroidUseDesignerAssembly"), "True", ignoreCase: true) == 0) {
+ var decompiler = new CSharpDecompiler (path, new DecompilerSettings () { });
+ return decompiler.DecompileWholeModuleAsString ().Split (Environment.NewLine[0]);
+ }
+ }
+ return File.ReadAllLines (path);
+ }
+
///
/// Asserts that a AndroidManifest.xml file contains the expected //application/@android:extractNativeLibs value.
///
diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/XASdkTests.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/XASdkTests.cs
index 81f094ee126..462dcc7228b 100644
--- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/XASdkTests.cs
+++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/XASdkTests.cs
@@ -28,23 +28,41 @@ public class XASdkTests : BaseTest
static readonly object [] DotNetBuildLibrarySource = new object [] {
new object [] {
- /* isRelease */ false,
- /* duplicateAar */ false,
+ /* isRelease */ false,
+ /* duplicateAar */ false,
+ /* useDesignerAssembly */ false,
},
new object [] {
- /* isRelease */ false,
- /* duplicateAar */ true,
+ /* isRelease */ false,
+ /* duplicateAar */ true,
+ /* useDesignerAssembly */ false,
},
new object [] {
- /* isRelease */ true,
- /* duplicateAar */ false,
+ /* isRelease */ true,
+ /* duplicateAar */ false,
+ /* useDesignerAssembly */ false,
+ },
+ new object [] {
+ /* isRelease */ false,
+ /* duplicateAar */ false,
+ /* useDesignerAssembly */ true,
+ },
+ new object [] {
+ /* isRelease */ false,
+ /* duplicateAar */ true,
+ /* useDesignerAssembly */ true,
+ },
+ new object [] {
+ /* isRelease */ true,
+ /* duplicateAar */ false,
+ /* useDesignerAssembly */ true,
},
};
[Test]
[Category ("SmokeTests")]
[TestCaseSource (nameof (DotNetBuildLibrarySource))]
- public void DotNetBuildLibrary (bool isRelease, bool duplicateAar)
+ public void DotNetBuildLibrary (bool isRelease, bool duplicateAar, bool useDesignerAssembly)
{
var path = Path.Combine ("temp", TestName);
var env_var = "MY_ENVIRONMENT_VAR";
@@ -70,6 +88,7 @@ public void DotNetBuildLibrary (bool isRelease, bool duplicateAar)
libC.OtherBuildItems.Add (new AndroidItem.AndroidAsset ("Assets\\bar\\bar.txt") {
BinaryContent = () => Array.Empty (),
});
+ libC.SetProperty ("AndroidUseDesignerAssembly", useDesignerAssembly.ToString ());
var activity = libC.Sources.FirstOrDefault (s => s.Include () == "MainActivity.cs");
if (activity != null)
libC.Sources.Remove (activity);
@@ -141,6 +160,7 @@ public Foo ()
BinaryContent = () => Array.Empty (),
});
libB.AddReference (libC);
+ libB.SetProperty ("AndroidUseDesignerAssembly", useDesignerAssembly.ToString ());
activity = libB.Sources.FirstOrDefault (s => s.Include () == "MainActivity.cs");
if (activity != null)
@@ -190,6 +210,7 @@ public Foo ()
// Test a duplicate @(AndroidLibrary) item with the same path of LibraryB.aar
appA.OtherBuildItems.Add (new AndroidItem.AndroidLibrary (aarPath));
}
+ appA.SetProperty ("AndroidUseDesignerAssembly", useDesignerAssembly.ToString ());
var appBuilder = CreateDotNetBuilder (appA, Path.Combine (path, appA.ProjectName));
Assert.IsTrue (appBuilder.Build (), $"{appA.ProjectName} should succeed");
@@ -223,11 +244,13 @@ public Foo ()
Assert.AreEqual (env_val, actual, $"{env_var} should be {env_val}");
// Check Resource.designer.cs
- var resource_designer_cs = Path.Combine (intermediate, "Resource.designer.cs");
- FileAssert.Exists (resource_designer_cs);
- var resource_designer_text = File.ReadAllText (resource_designer_cs);
- StringAssert.Contains ("public const int MyLayout", resource_designer_text);
- StringAssert.Contains ("global::LibraryB.Resource.Drawable.IMALLCAPS = global::AppA.Resource.Drawable.IMALLCAPS", resource_designer_text);
+ if (!useDesignerAssembly) {
+ var resource_designer_cs = Path.Combine (intermediate, "Resource.designer.cs");
+ FileAssert.Exists (resource_designer_cs);
+ var resource_designer_text = File.ReadAllText (resource_designer_cs);
+ StringAssert.Contains ("public const int MyLayout", resource_designer_text);
+ StringAssert.Contains ("global::LibraryB.Resource.Drawable.IMALLCAPS = global::AppA.Resource.Drawable.IMALLCAPS", resource_designer_text);
+ }
}
[Test]
@@ -443,6 +466,7 @@ public void GenerateResourceDesigner_false()
};
// Turn off Resource.designer.cs and remove usage of it
proj.SetProperty ("AndroidGenerateResourceDesigner", "false");
+ proj.SetProperty ("AndroidUseDesignerAssembly", "false");
proj.MainActivity = proj.DefaultMainActivity
.Replace ("Resource.Layout.Main", "0")
.Replace ("Resource.Id.myButton", "0");
@@ -1037,16 +1061,18 @@ public void DotNetIncremental ([Values (true, false)] bool isRelease, [Values ("
};
appA.AddReference (libB);
var appBuilder = CreateDotNetBuilder (appA, Path.Combine (path, appA.ProjectName));
+ appBuilder.BuildLogFile = Path.Combine (Root, path, appA.ProjectName, "build1.log");
Assert.IsTrue (appBuilder.Build (runtimeIdentifier: runtimeIdentifier), $"{appA.ProjectName} should succeed");
- appBuilder.AssertTargetIsNotSkipped ("CoreCompile");
+ appBuilder.AssertTargetIsNotSkipped ("CoreCompile", occurrence: 1);
if (isRelease) {
appBuilder.AssertTargetIsNotSkipped ("_RemoveRegisterAttribute");
appBuilder.AssertTargetIsNotSkipped ("_AndroidAot");
}
// Build again, no changes
+ appBuilder.BuildLogFile = Path.Combine (Root, path, appA.ProjectName, "build2.log");
Assert.IsTrue (appBuilder.Build (runtimeIdentifier: runtimeIdentifier), $"{appA.ProjectName} should succeed");
- appBuilder.AssertTargetIsSkipped ("CoreCompile");
+ appBuilder.AssertTargetIsSkipped ("CoreCompile", occurrence: 2);
if (isRelease) {
appBuilder.AssertTargetIsSkipped ("_RemoveRegisterAttribute");
appBuilder.AssertTargetIsSkipped ("_AndroidAotCompilation");
@@ -1114,6 +1140,34 @@ public Foo () {
helper.AssertContainsEntry ($"assemblies/{libC.ProjectName}.dll");
}
+ [Test]
+ public void DotNetDesignTimeBuild ()
+ {
+ var proj = new XASdkProject ();
+ proj.SetProperty ("AndroidUseDesignerAssembly", "true");
+ var builder = CreateDotNetBuilder (proj);
+ var parameters = new [] { "BuildingInsideVisualStudio=true"};
+ builder.BuildLogFile = "update.log";
+ Assert.IsTrue (builder.Build ("Compile", parameters: parameters), $"{proj.ProjectName} should succeed");
+ builder.AssertTargetIsNotSkipped ("_GenerateResourceCaseMap", occurrence: 1);
+ builder.AssertTargetIsNotSkipped ("_GenerateRtxt");
+ builder.AssertTargetIsNotSkipped ("_GenerateResourceDesignerIntermediateClass");
+ builder.AssertTargetIsNotSkipped ("_GenerateResourceDesignerAssembly", occurrence: 1);
+ parameters = new [] { "BuildingInsideVisualStudio=true" };
+ builder.BuildLogFile = "build1.log";
+ Assert.IsTrue (builder.Build ("SignAndroidPackage", parameters: parameters), $"{proj.ProjectName} should succeed");
+ builder.AssertTargetIsNotSkipped ("_GenerateResourceCaseMap", occurrence: 2);
+ builder.AssertTargetIsSkipped ("_GenerateRtxt", occurrence: 1);
+ builder.AssertTargetIsSkipped ("_GenerateResourceDesignerIntermediateClass", occurrence: 1);
+ builder.AssertTargetIsSkipped ("_GenerateResourceDesignerAssembly", occurrence: 2);
+ builder.BuildLogFile = "build2.log";
+ Assert.IsTrue (builder.Build ("SignAndroidPackage", parameters: parameters), $"{proj.ProjectName} should succeed 2");
+ builder.AssertTargetIsNotSkipped ("_GenerateResourceCaseMap", occurrence: 3);
+ builder.AssertTargetIsSkipped ("_GenerateRtxt", occurrence: 2);
+ builder.AssertTargetIsSkipped ("_GenerateResourceDesignerIntermediateClass", occurrence: 2);
+ builder.AssertTargetIsSkipped ("_GenerateResourceDesignerAssembly");
+ }
+
[Test]
public void SignAndroidPackage ()
{
diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Xamarin.Android.Build.Tests.csproj b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Xamarin.Android.Build.Tests.csproj
index be74e205c2d..8fab24d3966 100644
--- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Xamarin.Android.Build.Tests.csproj
+++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Xamarin.Android.Build.Tests.csproj
@@ -59,6 +59,7 @@
+
diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Android/KnownPackages.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Android/KnownPackages.cs
index 3cac7b400be..6fb9b090e2e 100644
--- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Android/KnownPackages.cs
+++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Android/KnownPackages.cs
@@ -203,11 +203,21 @@ public static class KnownPackages
Version = "4.7.0.1142",
TargetFramework = "MonoAndroid10.0",
};
+ public static Package XamarinForms_5_0_0_2515 = new Package {
+ Id = "Xamarin.Forms",
+ Version = "5.0.0.2515",
+ TargetFramework = "MonoAndroid10.0",
+ };
public static Package XamarinFormsMaps_4_7_0_1142 = new Package {
Id = "Xamarin.Forms.Maps",
Version = "4.7.0.1142",
TargetFramework = "MonoAndroid10.0",
};
+ public static Package XamarinFormsMaps_5_0_0_2515 = new Package {
+ Id = "Xamarin.Forms.Maps",
+ Version = "5.0.0.2515",
+ TargetFramework = "MonoAndroid10.0",
+ };
public static Package XamarinFormsMaps_4_0_0_425677 = new Package {
Id = "Xamarin.Forms.Maps",
Version = "4.0.0.425677",
@@ -474,6 +484,11 @@ public static class KnownPackages
Id = "Xamarin.Build.Download",
Version = "0.11.2",
};
+
+ public static Package Xamarin_Build_Download_0_11_3 = new Package {
+ Id = "Xamarin.Build.Download",
+ Version = "0.11.3",
+ };
// NOTE: old version required for some tests
public static Package Xamarin_Build_Download_0_4_11 = new Package {
Id = "Xamarin.Build.Download",
diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Android/XamarinAndroidCommonProject.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Android/XamarinAndroidCommonProject.cs
index cb964c0963b..51a4772a395 100644
--- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Android/XamarinAndroidCommonProject.cs
+++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Android/XamarinAndroidCommonProject.cs
@@ -54,8 +54,12 @@ protected XamarinAndroidCommonProject (string debugConfigurationName = "Debug",
AndroidResources.Add (new AndroidItem.AndroidResource ("Resources\\drawable-xxhdpi\\Icon.png") { BinaryContent = () => icon_binary_xxhdpi });
AndroidResources.Add (new AndroidItem.AndroidResource ("Resources\\drawable-xxxhdpi\\Icon.png") { BinaryContent = () => icon_binary_xxxhdpi });
//AndroidResources.Add (new AndroidItem.AndroidResource ("Resources\\drawable-nodpi\\Icon.png") { BinaryContent = () => icon_binary });
+ if (Builder.UseDotNet) {
+ // set our default
+ SetProperty ("AndroidUseDesignerAssembly", "True");
+ }
}
-
+
public override string ProjectTypeGuid {
get { return "FAE04EC0-301F-11D3-BF4B-00C04F79EFBC"; }
}
diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Android/XamarinAndroidProjectLanguage.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Android/XamarinAndroidProjectLanguage.cs
index 7c77da35bbc..4365ce6bab1 100644
--- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Android/XamarinAndroidProjectLanguage.cs
+++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Android/XamarinAndroidProjectLanguage.cs
@@ -34,7 +34,7 @@ public override string DefaultExtension {
get { return ".fs"; }
}
public override string DefaultDesignerExtension {
- get { return ".cs"; }
+ get { return Builder.UseDotNet ? ".fs" : ".cs"; }
}
public override string DefaultProjectExtension {
get { return ".fsproj"; }
diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Common/DotNetCLI.cs b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Common/DotNetCLI.cs
index b2f383bcedd..9b64f3d7922 100644
--- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Common/DotNetCLI.cs
+++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Common/DotNetCLI.cs
@@ -147,13 +147,15 @@ List GetDefaultCommandLineArgs (string verb, string target = null, strin
if (string.IsNullOrEmpty (BuildLogFile))
BuildLogFile = Path.Combine (testDir, "build.log");
- var binlog = string.IsNullOrEmpty (target) ? "msbuild" : target;
+ var binlog = string.IsNullOrEmpty (target) ? Path.GetFileNameWithoutExtension (string.IsNullOrEmpty (BuildLogFile) ? "msbuild" : BuildLogFile) : target;
var arguments = new List {
verb,
$"\"{projectOrSolution}\"",
"/noconsolelogger",
$"/flp1:LogFile=\"{BuildLogFile}\";Encoding=UTF-8;Verbosity={Verbosity}",
$"/bl:\"{Path.Combine (testDir, $"{binlog}.binlog")}\"",
+ "-m:1",
+ "-nr:false",
"/p:_DisableParallelAot=true",
};
if (!string.IsNullOrEmpty (target)) {
diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64SimpleDotNet.apkdesc b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64SimpleDotNet.apkdesc
index 7ca259a29f0..f5a8ad55bfe 100644
--- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64SimpleDotNet.apkdesc
+++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64SimpleDotNet.apkdesc
@@ -4,14 +4,17 @@
"AndroidManifest.xml": {
"Size": 3032
},
+ "assemblies/_Microsoft.Android.Resource.Designer.dll": {
+ "Size": 1028
+ },
"assemblies/Java.Interop.dll": {
- "Size": 58924
+ "Size": 58926
},
"assemblies/Mono.Android.dll": {
- "Size": 87710
+ "Size": 87074
},
"assemblies/Mono.Android.Runtime.dll": {
- "Size": 5924
+ "Size": 5833
},
"assemblies/rc.bin": {
"Size": 1182
@@ -23,19 +26,19 @@
"Size": 9253
},
"assemblies/System.Private.CoreLib.dll": {
- "Size": 484001
+ "Size": 483854
},
"assemblies/System.Runtime.dll": {
- "Size": 2629
+ "Size": 2608
},
"assemblies/System.Runtime.InteropServices.dll": {
"Size": 2269
},
"assemblies/UnnamedProject.dll": {
- "Size": 3628
+ "Size": 3276
},
"classes.dex": {
- "Size": 18968
+ "Size": 19020
},
"lib/arm64-v8a/libmono-component-marshal-ilgen.so": {
"Size": 93552
@@ -56,16 +59,16 @@
"Size": 150584
},
"lib/arm64-v8a/libxamarin-app.so": {
- "Size": 16632
+ "Size": 16728
},
"META-INF/BNDLTOOL.RSA": {
"Size": 1213
},
"META-INF/BNDLTOOL.SF": {
- "Size": 2914
+ "Size": 3037
},
"META-INF/MANIFEST.MF": {
- "Size": 2787
+ "Size": 2910
},
"res/drawable-hdpi-v4/icon.png": {
"Size": 2178
@@ -92,5 +95,5 @@
"Size": 1904
}
},
- "PackageSize": 2603241
+ "PackageSize": 2603338
}
\ No newline at end of file
diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64SimpleLegacy.apkdesc b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64SimpleLegacy.apkdesc
index cb7562922f1..adfe30f0e3b 100644
--- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64SimpleLegacy.apkdesc
+++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64SimpleLegacy.apkdesc
@@ -5,22 +5,22 @@
"Size": 2604
},
"assemblies/Java.Interop.dll": {
- "Size": 68913
+ "Size": 68923
},
"assemblies/Mono.Android.dll": {
- "Size": 265169
+ "Size": 265140
},
"assemblies/mscorlib.dll": {
- "Size": 769018
+ "Size": 769036
},
"assemblies/System.Core.dll": {
- "Size": 28199
+ "Size": 28216
},
"assemblies/System.dll": {
- "Size": 9180
+ "Size": 9192
},
"assemblies/UnnamedProject.dll": {
- "Size": 2882
+ "Size": 2897
},
"classes.dex": {
"Size": 370828
@@ -32,7 +32,7 @@
"Size": 750976
},
"lib/arm64-v8a/libmonodroid.so": {
- "Size": 332936
+ "Size": 333128
},
"lib/arm64-v8a/libmonosgen-2.0.so": {
"Size": 4039176
@@ -41,7 +41,7 @@
"Size": 66184
},
"lib/arm64-v8a/libxamarin-app.so": {
- "Size": 21256
+ "Size": 21264
},
"META-INF/ANDROIDD.RSA": {
"Size": 1213
diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64XFormsDotNet.apkdesc b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64XFormsDotNet.apkdesc
index 87875ce958d..efc6347e949 100644
--- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64XFormsDotNet.apkdesc
+++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64XFormsDotNet.apkdesc
@@ -4,17 +4,20 @@
"AndroidManifest.xml": {
"Size": 3568
},
+ "assemblies/_Microsoft.Android.Resource.Designer.dll": {
+ "Size": 1945
+ },
"assemblies/FormsViewGroup.dll": {
"Size": 7314
},
"assemblies/Java.Interop.dll": {
- "Size": 66797
+ "Size": 66801
},
"assemblies/Mono.Android.dll": {
- "Size": 444767
+ "Size": 444662
},
"assemblies/Mono.Android.Runtime.dll": {
- "Size": 5924
+ "Size": 5833
},
"assemblies/mscorlib.dll": {
"Size": 3856
@@ -101,7 +104,7 @@
"Size": 16805
},
"assemblies/System.Runtime.dll": {
- "Size": 2791
+ "Size": 2756
},
"assemblies/System.Runtime.InteropServices.dll": {
"Size": 2269
@@ -128,7 +131,7 @@
"Size": 1858
},
"assemblies/UnnamedProject.dll": {
- "Size": 117399
+ "Size": 5347
},
"assemblies/Xamarin.AndroidX.Activity.dll": {
"Size": 5872
@@ -185,10 +188,10 @@
"Size": 528450
},
"assemblies/Xamarin.Forms.Platform.Android.dll": {
- "Size": 384799
+ "Size": 337827
},
"assemblies/Xamarin.Forms.Platform.dll": {
- "Size": 56878
+ "Size": 11087
},
"assemblies/Xamarin.Forms.Xaml.dll": {
"Size": 60774
@@ -197,7 +200,7 @@
"Size": 40159
},
"classes.dex": {
- "Size": 3090508
+ "Size": 3141008
},
"lib/arm64-v8a/libmono-component-marshal-ilgen.so": {
"Size": 93552
@@ -218,7 +221,7 @@
"Size": 150584
},
"lib/arm64-v8a/libxamarin-app.so": {
- "Size": 333632
+ "Size": 333728
},
"META-INF/android.support.design_material.version": {
"Size": 12
@@ -332,13 +335,13 @@
"Size": 1213
},
"META-INF/BNDLTOOL.SF": {
- "Size": 79203
+ "Size": 79326
},
"META-INF/com.google.android.material_material.version": {
"Size": 10
},
"META-INF/MANIFEST.MF": {
- "Size": 79076
+ "Size": 79199
},
"META-INF/proguard/androidx-annotations.pro": {
"Size": 339
@@ -1973,5 +1976,5 @@
"Size": 341228
}
},
- "PackageSize": 7991971
+ "PackageSize": 7803652
}
\ No newline at end of file
diff --git a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64XFormsLegacy.apkdesc b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64XFormsLegacy.apkdesc
index ac496de717d..00393868d67 100644
--- a/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64XFormsLegacy.apkdesc
+++ b/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.ProjectTools/Resources/Base/BuildReleaseArm64XFormsLegacy.apkdesc
@@ -5,115 +5,115 @@
"Size": 3140
},
"assemblies/FormsViewGroup.dll": {
- "Size": 7215
+ "Size": 7230
},
"assemblies/Java.Interop.dll": {
- "Size": 69956
+ "Size": 69966
},
"assemblies/Mono.Android.dll": {
- "Size": 572709
+ "Size": 572670
},
"assemblies/Mono.Security.dll": {
- "Size": 68432
+ "Size": 68449
},
"assemblies/mscorlib.dll": {
- "Size": 915408
+ "Size": 915425
},
"assemblies/System.Core.dll": {
- "Size": 164046
+ "Size": 164059
},
"assemblies/System.dll": {
- "Size": 388864
+ "Size": 388883
},
"assemblies/System.Drawing.Common.dll": {
- "Size": 12365
+ "Size": 12370
},
"assemblies/System.Net.Http.dll": {
- "Size": 110693
+ "Size": 110718
},
"assemblies/System.Numerics.dll": {
- "Size": 15683
+ "Size": 15706
},
"assemblies/System.Runtime.Serialization.dll": {
- "Size": 186660
+ "Size": 186683
},
"assemblies/System.ServiceModel.Internals.dll": {
- "Size": 26594
+ "Size": 26604
},
"assemblies/System.Xml.dll": {
- "Size": 395656
+ "Size": 395668
},
"assemblies/UnnamedProject.dll": {
- "Size": 116899
+ "Size": 116997
},
"assemblies/Xamarin.AndroidX.Activity.dll": {
- "Size": 7697
+ "Size": 7711
},
"assemblies/Xamarin.AndroidX.AppCompat.AppCompatResources.dll": {
- "Size": 6648
+ "Size": 6664
},
"assemblies/Xamarin.AndroidX.AppCompat.dll": {
- "Size": 125328
+ "Size": 125346
},
"assemblies/Xamarin.AndroidX.CardView.dll": {
- "Size": 7367
+ "Size": 7380
},
"assemblies/Xamarin.AndroidX.CoordinatorLayout.dll": {
- "Size": 18272
+ "Size": 18289
},
"assemblies/Xamarin.AndroidX.Core.dll": {
- "Size": 131930
+ "Size": 131944
},
"assemblies/Xamarin.AndroidX.DrawerLayout.dll": {
- "Size": 15426
+ "Size": 15443
},
"assemblies/Xamarin.AndroidX.Fragment.dll": {
- "Size": 43135
+ "Size": 43150
},
"assemblies/Xamarin.AndroidX.Legacy.Support.Core.UI.dll": {
- "Size": 6715
+ "Size": 6727
},
"assemblies/Xamarin.AndroidX.Lifecycle.Common.dll": {
- "Size": 7062
+ "Size": 7078
},
"assemblies/Xamarin.AndroidX.Lifecycle.LiveData.Core.dll": {
- "Size": 7193
+ "Size": 7208
},
"assemblies/Xamarin.AndroidX.Lifecycle.ViewModel.dll": {
- "Size": 4873
+ "Size": 4886
},
"assemblies/Xamarin.AndroidX.Loader.dll": {
- "Size": 13585
+ "Size": 13596
},
"assemblies/Xamarin.AndroidX.RecyclerView.dll": {
- "Size": 102327
+ "Size": 102349
},
"assemblies/Xamarin.AndroidX.SavedState.dll": {
- "Size": 6268
+ "Size": 6294
},
"assemblies/Xamarin.AndroidX.SwipeRefreshLayout.dll": {
- "Size": 11271
+ "Size": 11284
},
"assemblies/Xamarin.AndroidX.ViewPager.dll": {
- "Size": 19424
+ "Size": 19438
},
"assemblies/Xamarin.Forms.Core.dll": {
- "Size": 524736
+ "Size": 524743
},
"assemblies/Xamarin.Forms.Platform.Android.dll": {
- "Size": 384872
+ "Size": 384885
},
"assemblies/Xamarin.Forms.Platform.dll": {
"Size": 56878
},
"assemblies/Xamarin.Forms.Xaml.dll": {
- "Size": 55801
+ "Size": 55807
},
"assemblies/Xamarin.Google.Android.Material.dll": {
- "Size": 43497
+ "Size": 43514
},
"classes.dex": {
- "Size": 3482812
+ "Size": 3533252
},
"lib/arm64-v8a/libmono-btls-shared.so": {
"Size": 1613872
@@ -122,7 +122,7 @@
"Size": 750976
},
"lib/arm64-v8a/libmonodroid.so": {
- "Size": 332936
+ "Size": 333128
},
"lib/arm64-v8a/libmonosgen-2.0.so": {
"Size": 4039176
@@ -131,7 +131,7 @@
"Size": 66184
},
"lib/arm64-v8a/libxamarin-app.so": {
- "Size": 107024
+ "Size": 107032
},
"META-INF/android.support.design_material.version": {
"Size": 12
@@ -1883,5 +1883,5 @@
"Size": 341040
}
},
- "PackageSize": 9521310
+ "PackageSize": 9537694
}
\ No newline at end of file
diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/FileResourceParser.cs b/src/Xamarin.Android.Build.Tasks/Utilities/FileResourceParser.cs
new file mode 100644
index 00000000000..96f48253931
--- /dev/null
+++ b/src/Xamarin.Android.Build.Tasks/Utilities/FileResourceParser.cs
@@ -0,0 +1,389 @@
+using System;
+using System.CodeDom;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+using System.Xml;
+using System.Xml.Linq;
+using System.Xml.XPath;
+using System.Text;
+using System.Text.RegularExpressions;
+using Microsoft.Build.Utilities;
+using Microsoft.Android.Build.Tasks;
+
+namespace Xamarin.Android.Tasks
+{
+ class FileResourceParser : ResourceParser
+ {
+ public string JavaPlatformDirectory { get; set; }
+
+ public string ResourceFlagFile { get; set; }
+
+ Dictionary arrayMapping = new Dictionary ();
+ Dictionary> foofoo = new Dictionary> ();
+ List custom_types = new List ();
+ XDocument publicXml;
+
+ string[] publicXmlFiles = new string[] {
+ "public.xml",
+ "public-final.xml",
+ "public-staging.xml",
+ };
+
+ protected XDocument LoadPublicXml () {
+ string publicXmlPath = Path.Combine (JavaPlatformDirectory, "data", "res", "values");
+ foreach (var file in publicXmlFiles) {
+ if (File.Exists (Path.Combine (publicXmlPath, file))) {
+ return XDocument.Load (Path.Combine (publicXmlPath, file));
+ }
+ }
+ return null;
+ }
+
+ public IList Parse (string resourceDirectory, IEnumerable additionalResourceDirectories, Dictionary resourceMap)
+ {
+ Log.LogDebugMessage ($"Parsing Directory {resourceDirectory}");
+ string publicXmlPath = Path.Combine (JavaPlatformDirectory, "data", "res", "values");
+ publicXml = LoadPublicXml ();
+ var result = new List ();
+ Dictionary> resources = new Dictionary> ();
+ foreach (var knownType in RtxtParser.knownTypes) {
+ if (knownType == "styleable") {
+ resources.Add (knownType, new List ());
+ continue;
+ }
+ resources.Add (knownType, new SortedSet (new RComparer ()));
+ }
+ foreach (var dir in Directory.EnumerateDirectories (resourceDirectory, "*", SearchOption.TopDirectoryOnly)) {
+ foreach (var file in Directory.EnumerateFiles (dir, "*.*", SearchOption.AllDirectories)) {
+ ProcessResourceFile (file, resources);
+ }
+ }
+ foreach (var dir in additionalResourceDirectories ?? Array.Empty()) {
+ Log.LogDebugMessage ($"Processing Directory {dir}");
+ if (Directory.Exists (dir)) {
+ foreach (var file in Directory.EnumerateFiles (dir, "*.*", SearchOption.AllDirectories)) {
+ ProcessResourceFile (file, resources);
+ }
+ } else {
+ Log.LogDebugMessage ($"Skipping non-existent directory: {dir}");
+ }
+ }
+
+ // now generate the Id's we need in a specific order
+ List declarationIds = new List ();
+ declarationIds.Add ("attr");
+ declarationIds.Add ("drawable");
+ declarationIds.Add ("mipmap");
+ declarationIds.Add ("font");
+ declarationIds.Add ("layout");
+ declarationIds.Add ("anim");
+ declarationIds.Add ("animator");
+ declarationIds.Add ("transition");
+ declarationIds.Add ("xml");
+ declarationIds.Add ("raw");
+ declarationIds.Add ("dimen");
+ declarationIds.Add ("string");
+ declarationIds.Add ("array");
+ declarationIds.Add ("plurals");
+ declarationIds.Add ("bool");
+ declarationIds.Add ("color");
+ declarationIds.Add ("integer");
+ declarationIds.Add ("menu");
+ declarationIds.Add ("id");
+ // custom types
+ foreach (var customClass in custom_types) {
+ declarationIds.Add (customClass);
+ }
+
+ declarationIds.Add ("interpolator");
+ declarationIds.Add ("style");
+ declarationIds.Add ("styleable");
+
+ declarationIds.Sort ((a, b) => {
+ return string.Compare (a, b, StringComparison.OrdinalIgnoreCase);
+ });
+
+ string itemPackageId = "0x7f";
+ int typeid = 1;
+
+ foreach (var t in declarationIds) {
+ int itemid = 0;
+ if (!resources.ContainsKey(t)) {
+ continue;
+ }
+ if (resources[t].Count == 0) {
+ continue;
+ }
+ foreach (R r in resources[t].OrderBy(x => x.ToSortedString(), StringComparer.Ordinal)) {
+
+ int id = Convert.ToInt32 (itemPackageId + typeid.ToString ("X2") + itemid.ToString ("X4"), fromBase: 16);
+ if (r.Type == RType.Integer && r.Id == -1) {
+ itemid++;
+ r.UpdateId (id);
+ } else {
+ if (foofoo.ContainsKey (r.Identifier)) {
+ var items = foofoo[r.Identifier];
+ if (r.Ids != null) {
+ // do something special cos its an array we need to replace *some* its.
+ int[] newIds = new int[r.Ids.Length];
+ for (int i = 0; i < r.Ids.Length; i++) {
+ // we need to lookup the ID's for these from the ones generated.
+ newIds[i] = r.Ids[i];
+ if (r.Ids[i] == -1)
+ newIds[i] = GetId (result, items[i]);
+ }
+ r.UpdateIds (newIds);
+ }
+ }
+ }
+ result.Add (r);
+ }
+ typeid++;
+ }
+
+ result.Sort (new RComparer ());
+
+ return result;
+ }
+
+ class RComparer : IComparer {
+ public int Compare(R a, R b) {
+ return string.Compare (a.ToSortedString (), b.ToSortedString (), StringComparison.Ordinal);
+ }
+ }
+
+ HashSet resourceNamesToUseDirectly = new HashSet () {
+ "integer-array",
+ "string-array",
+ "declare-styleable",
+ "add-resource",
+ };
+
+ int GetId (ICollection resources, string identifier)
+ {
+ foreach (R r in resources) {
+ if (r.Identifier == identifier) {
+ return r.Id;
+ }
+ }
+ return -1;
+ }
+
+ void ProcessResourceFile (string file, Dictionary> resources)
+ {
+ Log.LogDebugMessage ($"{nameof(ProcessResourceFile)} {file}");
+ var fileName = Path.GetFileNameWithoutExtension (file);
+ if (string.IsNullOrEmpty (fileName))
+ return;
+ if (fileName.EndsWith (".9", StringComparison.OrdinalIgnoreCase))
+ fileName = Path.GetFileNameWithoutExtension (fileName);
+ var path = Directory.GetParent (file).Name;
+ var ext = Path.GetExtension (file);
+ switch (ext) {
+ case ".xml":
+ case ".axml":
+ if (string.Compare (path, "raw", StringComparison.OrdinalIgnoreCase) == 0)
+ goto default;
+ try {
+ ProcessXmlFile (file, resources);
+ } catch (XmlException ex) {
+ Log.LogCodedWarning ("XA1000", Properties.Resources.XA1000, file, ex);
+ }
+ break;
+ default:
+ break;
+ }
+ CreateResourceField (path, fileName, resources);
+ }
+
+ void CreateResourceField (string root, string id, Dictionary> resources) {
+ var i = root.IndexOf ('-');
+ var item = i < 0 ? root : root.Substring (0, i);
+ item = resourceNamesToUseDirectly.Contains (root) ? root : item;
+ switch (item.ToLowerInvariant ()) {
+ case "animation":
+ item = "anim";
+ break;
+ case "array":
+ case "string-array":
+ case "integer-array":
+ item = "array";
+ break;
+ case "enum":
+ case "flag":
+ item = "id";
+ break;
+ }
+ var r = new R () {
+ ResourceTypeName = item,
+ Identifier = id,
+ Id = -1,
+ };
+ if (!resources.ContainsKey (item)) {
+ Log.LogDebugMessage ($"Ignoring path:{item}");
+ return;
+ }
+ resources[item].Add (r);
+ }
+
+ void ProcessStyleable (XmlReader reader, Dictionary> resources)
+ {
+ Log.LogDebugMessage ($"{nameof(ProcessStyleable)}");
+ string topName = null;
+ int fieldCount = 0;
+ List fields = new List ();
+ List attribs = new List ();
+ if (reader.HasAttributes) {
+ while (reader.MoveToNextAttribute ()) {
+ if (reader.Name.Replace ("android:", "") == "name")
+ topName = reader.Value;
+ }
+ }
+ while (reader.Read ()) {
+ if (reader.NodeType == XmlNodeType.Whitespace || reader.NodeType == XmlNodeType.Comment)
+ continue;
+ string name = null;
+ if (string.IsNullOrEmpty (topName)) {
+ if (reader.HasAttributes) {
+ while (reader.MoveToNextAttribute ()) {
+ if (reader.Name.Replace ("android:", "") == "name")
+ topName = reader.Value;
+ }
+ }
+ }
+ if (!reader.IsStartElement ())
+ continue;
+ if (reader.HasAttributes) {
+ while (reader.MoveToNextAttribute ()) {
+ if (reader.Name.Replace ("android:", "") == "name")
+ name = reader.Value;
+ }
+ }
+ reader.MoveToElement ();
+ if (reader.LocalName == "attr") {
+ attribs.Add (name);
+ }
+ }
+ var field = new R () {
+ ResourceTypeName = "styleable",
+ Identifier = topName,
+ Type = RType.Array,
+ };
+ if (!arrayMapping.ContainsKey (field)) {
+ foofoo.Add (field.Identifier, new List ());
+ attribs.Sort (StringComparer.OrdinalIgnoreCase);
+ for (int i = 0; i < attribs.Count; i++) {
+ string name = attribs [i];
+ if (!name.StartsWith ("android:", StringComparison.OrdinalIgnoreCase)) {
+ var r = new R () {
+ ResourceTypeName = "attr",
+ Identifier = $"{name}",
+ Id = -1,
+ };
+ resources [r.ResourceTypeName].Add (r);
+ fields.Add (r);
+ } else {
+ // this is an android:xxx resource, we should not calculate the id
+ // we should get it from "somewhere" maybe the pubic.xml
+ name = name.Replace ("android:", string.Empty);
+ var element = publicXml?.XPathSelectElement ($"/resources/public[@name='{name}']") ?? null;
+ int value = Convert.ToInt32 (element?.Attribute ("id")?.Value ?? "0x0", fromBase: 16);
+ var r = new R () {
+ ResourceTypeName = "attr",
+ Identifier = $"{name}",
+ Id = value,
+ };
+ fields.Add (r);
+ }
+ }
+ if (field.Type != RType.Array)
+ return;
+ arrayMapping.Add (field, fields.ToArray ());
+
+ field.Ids = new int [attribs.Count];
+ for (int idx =0; idx < field.Ids.Length; idx++)
+ field.Ids[idx] = fields[idx].Id;
+ resources [field.ResourceTypeName].Add (field);
+ int id = 0;
+ foreach (string r in attribs) {
+ foofoo[field.Identifier].Add (r.Replace (":", "_"));
+ resources [field.ResourceTypeName].Add (new R () {
+ ResourceTypeName = field.ResourceTypeName,
+ Identifier = $"{field.Identifier}_{r.Replace (":", "_")}",
+ Id = id++,
+ });
+ }
+ }
+ }
+
+ void ProcessXmlFile (string file, Dictionary> resources)
+ {
+ Log.LogDebugMessage ($"{nameof(ProcessXmlFile)}");
+ using (var reader = XmlReader.Create (file)) {
+ while (reader.Read ()) {
+ if (reader.NodeType == XmlNodeType.Whitespace || reader.NodeType == XmlNodeType.Comment)
+ continue;
+ if (reader.IsStartElement ()) {
+ var elementName = reader.Name;
+ if (elementName == "declare-styleable" || elementName == "configVarying" || elementName == "add-resource") {
+ ProcessStyleable (reader.ReadSubtree (), resources);
+ continue;
+ }
+ if (reader.HasAttributes) {
+ string name = null;
+ string type = null;
+ string id = null;
+ string custom_id = null;
+ while (reader.MoveToNextAttribute ()) {
+ if (reader.LocalName == "name")
+ name = reader.Value;
+ if (reader.LocalName == "type")
+ type = reader.Value;
+ if (reader.LocalName == "id") {
+ string[] values = reader.Value.Split ('/');
+ if (values.Length != 2) {
+ id = reader.Value.Replace ("@+id/", "").Replace ("@id/", "");
+ } else {
+ if (values [0] != "@+id" && values [0] != "@id" && !values [0].Contains ("android:")) {
+ custom_id = values [0].Replace ("@", "").Replace ("+", "");
+ }
+ id = values [1];
+ }
+
+ }
+ if (reader.LocalName == "inflatedId") {
+ string inflateId = reader.Value.Replace ("@+id/", "").Replace ("@id/", "");
+ var r = new R () {
+ ResourceTypeName = "id",
+ Identifier = inflateId,
+ Id = -1,
+ };
+ Log.LogDebugMessage ($"Adding 1 {r}");
+ resources[r.ResourceTypeName].Add (r);
+ }
+ }
+ if (name?.Contains ("android:") ?? false)
+ continue;
+ if (id?.Contains ("android:") ?? false)
+ continue;
+ // Move the reader back to the element node.
+ reader.MoveToElement ();
+ if (!string.IsNullOrEmpty (name)) {
+ CreateResourceField (type ?? elementName, name, resources);
+ }
+ if (!string.IsNullOrEmpty (custom_id) && !resources.ContainsKey (custom_id)) {
+ resources.Add (custom_id, new SortedSet (new RComparer ()));
+ custom_types.Add (custom_id);
+ }
+ if (!string.IsNullOrEmpty (id)) {
+ CreateResourceField (custom_id ?? "id", id.Replace ("-", "_").Replace (".", "_"), resources);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/JavaResourceParser.cs b/src/Xamarin.Android.Build.Tasks/Utilities/JavaResourceParser.cs
index bb705dfbcdd..bf92442a733 100644
--- a/src/Xamarin.Android.Build.Tasks/Utilities/JavaResourceParser.cs
+++ b/src/Xamarin.Android.Build.Tasks/Utilities/JavaResourceParser.cs
@@ -23,7 +23,7 @@ public CodeTypeDeclaration Parse (string file, bool isApp, Dictionary new { Match = p.Key.Match (line), Handler = p.Value }).FirstOrDefault (x => x.Match.Success);
-
+
if (info == null)
continue;
@@ -84,7 +84,7 @@ public JavaResourceParser ()
Parse (@"^ public static final int ([^ =]+)\s*=\s*([^;]+);$",
(m, app, g, map) => {
var name = ((CodeTypeDeclaration) g.Members [g.Members.Count-1]).Name;
- var f = new CodeMemberField (typeof (int), GetResourceName (name, m.Groups[1].Value, map)) {
+ var f = new CodeMemberField (typeof (int), ResourceIdentifier.GetResourceName (name, m.Groups[1].Value, map, Log)) {
Attributes = app ? MemberAttributes.Const | MemberAttributes.Public : MemberAttributes.Static | MemberAttributes.Public,
InitExpression = new CodePrimitiveExpression (ToInt32 (m.Groups [2].Value, m.Groups [2].Value.IndexOf ("0x", StringComparison.Ordinal) == 0 ? 16 : 10)),
Comments = {
@@ -97,7 +97,7 @@ public JavaResourceParser ()
Parse (@"^ public static final int\[\] ([^ =]+) = {",
(m, app, g, map) => {
var name = ((CodeTypeDeclaration) g.Members [g.Members.Count-1]).Name;
- var f = new CodeMemberField (typeof (int[]), GetResourceName (name, m.Groups[1].Value, map)) {
+ var f = new CodeMemberField (typeof (int[]), ResourceIdentifier.GetResourceName (name, m.Groups[1].Value, map, Log)) {
// pity I can't make the member readonly...
Attributes = MemberAttributes.Public | MemberAttributes.Static,
};
diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/ManagedResourceParser.cs b/src/Xamarin.Android.Build.Tasks/Utilities/ManagedResourceParser.cs
index fa735737a36..07c4b1537c5 100644
--- a/src/Xamarin.Android.Build.Tasks/Utilities/ManagedResourceParser.cs
+++ b/src/Xamarin.Android.Build.Tasks/Utilities/ManagedResourceParser.cs
@@ -14,7 +14,7 @@
namespace Xamarin.Android.Tasks
{
- class ManagedResourceParser : ResourceParser
+ class ManagedResourceParser : FileResourceParser
{
class CompareTuple : IComparer<(int Key, CodeMemberField Value)>
{
@@ -37,10 +37,6 @@ public int Compare((int Key, CodeMemberField Value) x, (int Key, CodeMemberField
XDocument publicXml;
- public string JavaPlatformDirectory { get; set; }
-
- public string ResourceFlagFile { get; set; }
-
void SortMembers (CodeTypeDeclaration decl, StringComparison stringComparison = StringComparison.OrdinalIgnoreCase)
{
CodeTypeMember [] members = new CodeTypeMember [decl.Members.Count];
@@ -87,10 +83,7 @@ public CodeTypeDeclaration Parse (string resourceDirectory, string rTxtFile, IEn
transition = CreateClass ("Transition");
xml = CreateClass ("Xml");
- string publicXmlPath = Path.Combine (JavaPlatformDirectory, "data", "res", "values", "public.xml");
- if (File.Exists (publicXmlPath)) {
- publicXml = XDocument.Load (publicXmlPath);
- }
+ publicXml = LoadPublicXml ();
var resModifiedDate = !string.IsNullOrEmpty (ResourceFlagFile) && File.Exists (ResourceFlagFile)
? File.GetLastWriteTimeUtc (ResourceFlagFile)
@@ -296,104 +289,17 @@ public CodeTypeDeclaration Parse (string resourceDirectory, string rTxtFile, IEn
void ProcessRtxtFile (string file)
{
- var lines = System.IO.File.ReadLines (file);
- int lineNumber = 0;
- foreach (var line in lines) {
- lineNumber++;
- var items = line.Split (new char [] { ' ' }, 4);
- if (items.Length < 4) {
- Log.LogDebugMessage ($"'{file}:{lineNumber}' ignoring contents '{line}', it does not have the correct number of elements.");
- continue;
- }
- int value = items [1] != "styleable" ? Convert.ToInt32 (items [3], 16) : -1;
- string itemName = items [2];
- switch (items [1]) {
- case "anim":
- CreateIntField (animation, itemName, value);
- break;
- case "animator":
- CreateIntField (animator, itemName, value);
- break;
- case "attr":
- CreateIntField (attrib, itemName, value);
- break;
- case "array":
- CreateIntField (arrays, itemName, value);
- break;
- case "bool":
- CreateIntField (boolean, itemName, value);
- break;
- case "color":
- CreateIntField (colors, itemName, value);
- break;
- case "dimen":
- CreateIntField (dimension, itemName, value);
- break;
- case "drawable":
- CreateIntField (drawable, itemName, value);
- break;
- case "font":
- CreateIntField (font, itemName, value);
- break;
- case "id":
- CreateIntField (ids, itemName, value);
- break;
- case "integer":
- CreateIntField (ints, itemName, value);
- break;
- case "interpolator":
- CreateIntField (interpolators, itemName, value);
- break;
- case "layout":
- CreateIntField (layout, itemName, value);
- break;
- case "menu":
- CreateIntField (menu, itemName, value);
- break;
- case "mipmap":
- CreateIntField (mipmaps, itemName, value);
- break;
- case "plurals":
- CreateIntField (plurals, itemName, value);
- break;
- case "raw":
- CreateIntField (raw, itemName, value);
- break;
- case "string":
- CreateIntField (strings, itemName, value);
- break;
- case "style":
- CreateIntField (style, itemName, value);
- break;
- case "styleable":
- switch (items [0]) {
- case "int":
- CreateIntField (styleable, itemName, Convert.ToInt32 (items [3], 10));
+ var parser = new RtxtParser ();
+ var resources = parser.Parse (file, Log, map);
+ foreach (var r in resources) {
+ var cl = CreateClass (r.ResourceTypeName);
+ switch (r.Type) {
+ case RType.Integer:
+ CreateIntField (cl, r.Identifier, r.Id);
break;
- case "int[]":
- var arrayValues = items [3].Trim (new char [] { '{', '}' })
- .Replace (" ", "")
- .Split (new char [] { ',' });
- CreateIntArrayField (styleable, itemName, arrayValues.Length,
- arrayValues.Select (x => string.IsNullOrEmpty (x) ? -1 : Convert.ToInt32 (x, 16)).ToArray ());
+ case RType.Array:
+ CreateIntArrayField (cl, r.Identifier, r.Ids.Length, r.Ids);
break;
- }
- break;
- case "transition":
- CreateIntField (transition, itemName, value);
- break;
- case "xml":
- CreateIntField (xml, itemName, value);
- break;
- // for custom views
- default:
- CodeTypeDeclaration customClass;
- if (!custom_types.TryGetValue (items [1], out customClass)) {
- customClass = CreateClass (items [1]);
- custom_types.Add (items [1], customClass);
- }
- CreateIntField (customClass, itemName, value);
- break;
}
}
}
@@ -441,15 +347,22 @@ CodeTypeDeclaration CreateResourceClass ()
return decl;
}
+ Dictionary classMapping = new Dictionary (StringComparer.OrdinalIgnoreCase);
+
CodeTypeDeclaration CreateClass (string type)
{
- var t = new CodeTypeDeclaration (ResourceParser.GetNestedTypeName (type)) {
+ var typeName = ResourceParser.GetNestedTypeName (type);
+ if (classMapping.ContainsKey (typeName)) {
+ return classMapping [typeName];
+ }
+ var t = new CodeTypeDeclaration (typeName) {
IsPartial = true,
TypeAttributes = TypeAttributes.Public,
};
t.Members.Add (new CodeConstructor () {
Attributes = MemberAttributes.Private,
});
+ classMapping.Add (typeName, t);
return t;
}
@@ -464,7 +377,7 @@ void CreateField (CodeTypeDeclaration parentType, string name, Type type)
CodeMemberField CreateIntField (CodeTypeDeclaration parentType, string name, int value = -1)
{
- string mappedName = GetResourceName (parentType.Name, name, map);
+ string mappedName = ResourceIdentifier.GetResourceName (parentType.Name, name, map, Log);
CodeMemberField f = (CodeMemberField)parentType.Members.OfType ().FirstOrDefault (x => string.Compare (x.Name, mappedName, StringComparison.Ordinal) == 0);
if (f != null)
return f;
@@ -484,7 +397,7 @@ CodeMemberField CreateIntField (CodeTypeDeclaration parentType, string name, int
CodeMemberField CreateIntArrayField (CodeTypeDeclaration parentType, string name, int count, params int[] values)
{
- string mappedName = GetResourceName (parentType.Name, name, map);
+ string mappedName = ResourceIdentifier.GetResourceName (parentType.Name, name, map, Log);
CodeMemberField f = (CodeMemberField)parentType.Members.OfType ().FirstOrDefault (x => string.Compare (x.Name, mappedName, StringComparison.Ordinal) == 0);
if (f != null)
return f;
@@ -608,7 +521,6 @@ void CreateResourceField (string root, string fieldName, XmlReader element = nul
void ProcessStyleable (XmlReader reader)
{
string topName = null;
- int fieldCount = 0;
List fields = new List ();
List attribs = new List ();
while (reader.Read ()) {
diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/MonoAndroidHelper.cs b/src/Xamarin.Android.Build.Tasks/Utilities/MonoAndroidHelper.cs
index 5e41b62be37..19088a10f6b 100644
--- a/src/Xamarin.Android.Build.Tasks/Utilities/MonoAndroidHelper.cs
+++ b/src/Xamarin.Android.Build.Tasks/Utilities/MonoAndroidHelper.cs
@@ -325,6 +325,28 @@ public static bool HasMonoAndroidReference (MetadataReader reader)
return false;
}
+ public static bool HasResourceDesignerAssemblyReference (ITaskItem assembly)
+ {
+ if (!File.Exists (assembly.ItemSpec)) {
+ return false;
+ }
+ using var pe = new PEReader (File.OpenRead (assembly.ItemSpec));
+ var reader = pe.GetMetadataReader ();
+ return HasResourceDesignerAssemblyReference (reader);
+ }
+
+ public static bool HasResourceDesignerAssemblyReference (MetadataReader reader)
+ {
+ foreach (var handle in reader.AssemblyReferences) {
+ var reference = reader.GetAssemblyReference (handle);
+ var name = reader.GetString (reference.Name);
+ if (string.CompareOrdinal (name, "_Microsoft.Android.Resource.Designer") == 0) {
+ return true;
+ }
+ }
+ return false;
+ }
+
public static bool IsReferenceAssembly (string assembly)
{
using (var stream = File.OpenRead (assembly))
@@ -388,12 +410,26 @@ internal static IEnumerable GetFrameworkAssembliesToTreatAsUserAssemb
}
#endif
- public static Dictionary LoadAcwMapFile (string acwPath)
+ public static bool SaveMapFile (IBuildEngine4 engine, string mapFile, Dictionary map)
{
- var acw_map = new Dictionary ();
- if (!File.Exists (acwPath))
+ engine?.RegisterTaskObjectAssemblyLocal (mapFile, map, RegisteredTaskObjectLifetime.Build);
+ using (var writer = MemoryStreamPool.Shared.CreateStreamWriter ()) {
+ foreach (var i in map.OrderBy (x => x.Key)) {
+ writer.WriteLine ($"{i.Key};{i.Value}");
+ }
+ writer.Flush ();
+ return Files.CopyIfStreamChanged (writer.BaseStream, mapFile);
+ }
+ }
+ public static Dictionary LoadMapFile (IBuildEngine4 engine, string mapFile, StringComparer comparer)
+ {
+ var cachedMap = engine?.GetRegisteredTaskObjectAssemblyLocal> (mapFile, RegisteredTaskObjectLifetime.Build);
+ if (cachedMap != null)
+ return cachedMap;
+ var acw_map = new Dictionary (comparer);
+ if (!File.Exists (mapFile))
return acw_map;
- foreach (var s in File.ReadLines (acwPath)) {
+ foreach (var s in File.ReadLines (mapFile)) {
var items = s.Split (new char[] { ';' }, count: 2);
if (!acw_map.ContainsKey (items [0]))
acw_map.Add (items [0], items [1]);
diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/ResourceDesignerImportGenerator.cs b/src/Xamarin.Android.Build.Tasks/Utilities/ResourceDesignerImportGenerator.cs
index 8e7ddacfd3e..d9bcf2ecf69 100644
--- a/src/Xamarin.Android.Build.Tasks/Utilities/ResourceDesignerImportGenerator.cs
+++ b/src/Xamarin.Android.Build.Tasks/Utilities/ResourceDesignerImportGenerator.cs
@@ -47,6 +47,7 @@ public void CreateImportMethods (IEnumerable libraries)
var reader = pe.GetMetadataReader ();
var resourceDesignerName = GetResourceDesignerClass (reader);
if (string.IsNullOrEmpty (resourceDesignerName)) {
+ Log.LogDebugMessage ($"Could not find 'ResourceDesignerAttribute' in {assemblyPath}");
continue;
}
string aliasMetaData = assemblyPath.GetMetadata ("Aliases");
diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/ResourceIdentifier.cs b/src/Xamarin.Android.Build.Tasks/Utilities/ResourceIdentifier.cs
index eb96d4f5aa0..7edfc38faf5 100644
--- a/src/Xamarin.Android.Build.Tasks/Utilities/ResourceIdentifier.cs
+++ b/src/Xamarin.Android.Build.Tasks/Utilities/ResourceIdentifier.cs
@@ -6,6 +6,7 @@
using System.Linq;
using System.Reflection;
using System.Text.RegularExpressions;
+using Microsoft.Android.Build.Tasks;
using Microsoft.Build.Utilities;
namespace Xamarin.Android.Tasks
@@ -42,5 +43,20 @@ public static string CreateValidIdentifier (string identifier)
return result;
}
+
+ internal static string GetResourceName (string type, string name, Dictionary map, TaskLoggingHelper log)
+ {
+ string mappedValue;
+ string key = string.Format ("{0}{1}{2}", type, Path.DirectorySeparatorChar, name).ToLowerInvariant ();
+
+ if (map.TryGetValue (key, out mappedValue)) {
+ log.LogDebugMessage (" - Remapping resource: {0}.{1} -> {2}", type, name, mappedValue);
+ return ResourceIdentifier.CreateValidIdentifier (mappedValue.Substring (mappedValue.LastIndexOf (Path.DirectorySeparatorChar) + 1));
+ }
+
+ log.LogDebugMessage (" - Not remapping resource: {0}.{1}", type, name);
+
+ return ResourceIdentifier.CreateValidIdentifier (name);
+ }
}
-}
\ No newline at end of file
+}
diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/ResourceParser.cs b/src/Xamarin.Android.Build.Tasks/Utilities/ResourceParser.cs
index d85b97299e9..3becd20d1ec 100644
--- a/src/Xamarin.Android.Build.Tasks/Utilities/ResourceParser.cs
+++ b/src/Xamarin.Android.Build.Tasks/Utilities/ResourceParser.cs
@@ -37,21 +37,5 @@ internal static string GetNestedTypeName (string name)
default: return char.ToUpperInvariant (name[0]) + name.Substring (1);
}
}
-
- internal string GetResourceName (string type, string name, Dictionary map)
- {
- string mappedValue;
- string key = string.Format ("{0}{1}{2}", type, Path.DirectorySeparatorChar, name).ToLowerInvariant ();
-
- if (map.TryGetValue (key, out mappedValue)) {
- Log.LogDebugMessage (" - Remapping resource: {0}.{1} -> {2}", type, name, mappedValue);
- return ResourceIdentifier.CreateValidIdentifier (mappedValue.Substring (mappedValue.LastIndexOf (Path.DirectorySeparatorChar) + 1));
- }
-
- Log.LogDebugMessage (" - Not remapping resource: {0}.{1}", type, name);
-
- return ResourceIdentifier.CreateValidIdentifier (name);
- }
-
}
}
diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/RtxtParser.cs b/src/Xamarin.Android.Build.Tasks/Utilities/RtxtParser.cs
new file mode 100644
index 00000000000..644df0e2e31
--- /dev/null
+++ b/src/Xamarin.Android.Build.Tasks/Utilities/RtxtParser.cs
@@ -0,0 +1,158 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using Microsoft.Android.Build.Tasks;
+using Microsoft.Build.Utilities;
+
+namespace Xamarin.Android.Tasks
+{
+ public enum RType {
+ Integer,
+ Array,
+ }
+
+ public enum ResourceType {
+ System,
+ Custom,
+ }
+
+ public struct R : IComparable {
+ public RType Type;
+ public int Id;
+ public int [] Ids;
+ public string Identifier;
+ public string ResourceTypeName;
+ public ResourceType ResourceType;
+
+ public string Key => $"{ResourceTypeName}:{Identifier}";
+
+ public override string ToString ()
+ {
+ if (Type == RType.Integer) {
+ if (ResourceTypeName == "styleable")
+ return $"int {ResourceTypeName} {Identifier} {Id}";
+ return $"int {ResourceTypeName} {Identifier} 0x{Id.ToString ("x8")}";
+ }
+ return $"int[] {ResourceTypeName} {Identifier} {{ {String.Join (", ", Ids.Select (x => $"0x{x.ToString ("x8")}"))} }}";
+ }
+
+ public string ToSortedString ()
+ {
+ return $"{ResourceTypeName}_{Identifier}";
+ }
+
+ public int CompareTo(R other)
+ {
+ return String.Compare (ToSortedString (), other.ToSortedString (), StringComparison.OrdinalIgnoreCase);
+ }
+
+ public void UpdateId (int newId)
+ {
+ Id = newId;
+ }
+
+ public void UpdateIds (int [] newIds)
+ {
+ Ids = newIds;
+ }
+ }
+
+ public class RtxtParser {
+
+ static readonly char[] EmptyChar = new char [] { ' ' };
+ static readonly char[] CurlyBracketsChar = new char [] { '{', '}' };
+ static readonly char[] CommaChar = new char [] { ',' };
+
+ TaskLoggingHelper log;
+ Dictionary map;
+
+ public static HashSet knownTypes = new HashSet () {
+ "anim",
+ "animator",
+ "attr",
+ "array",
+ "bool",
+ "color",
+ "dimen",
+ "drawable",
+ "font",
+ "id",
+ "integer",
+ "interpolator",
+ "layout",
+ "menu",
+ "mipmap",
+ "plurals",
+ "raw",
+ "string",
+ "style",
+ "styleable",
+ "transition",
+ "xml",
+ };
+
+ public IEnumerable Parse (string file, TaskLoggingHelper logger, Dictionary mapping)
+ {
+ log = logger;
+ map = mapping;
+ var result = new List ();
+ if (File.Exists (file))
+ ProcessRtxtFile (file, result);
+ return result;
+ }
+
+ void ProcessRtxtFile (string file, IList result)
+ {
+ int lineNumber = 0;
+ foreach (var line in File.ReadLines (file)) {
+ lineNumber++;
+ var items = line.Split (EmptyChar, 4);
+ if (items.Length < 4) {
+ log.LogDebugMessage ($"'{file}:{lineNumber}' ignoring contents '{line}', it does not have the correct number of elements.");
+ continue;
+ }
+ int value = items [1] != "styleable" ? Convert.ToInt32 (items [3], 16) : -1;
+ string itemName = ResourceIdentifier.GetResourceName(items [1], items [2], map, log);
+ if (knownTypes.Contains (items [1])) {
+ if (items [1] != "styleable") {
+ result.Add (new R () {
+ ResourceTypeName = items [1],
+ Identifier = itemName,
+ Id = value,
+ });
+ continue;
+ }
+ switch (items [0]) {
+ case "int":
+ result.Add (new R () {
+ ResourceTypeName = items [1],
+ Identifier = itemName,
+ Id = Convert.ToInt32 (items [3], 10),
+ });
+ break;
+ case "int[]":
+ var arrayValues = items [3].Trim (CurlyBracketsChar)
+ .Replace (" ", "")
+ .Split (CommaChar);
+
+ result.Add (new R () {
+ ResourceTypeName = items [1],
+ Type = RType.Array,
+ Identifier = itemName,
+ Ids = arrayValues.Select (x => string.IsNullOrEmpty (x) ? -1 : Convert.ToInt32 (x, 16)).ToArray (),
+ });
+ break;
+ }
+ continue;
+ }
+ result.Add (new R () {
+ ResourceTypeName = items[1],
+ ResourceType = ResourceType.Custom,
+ Identifier = itemName,
+ Id = value,
+ });
+ }
+ }
+ }
+}
diff --git a/src/Xamarin.Android.Build.Tasks/Utilities/RtxtWriter.cs b/src/Xamarin.Android.Build.Tasks/Utilities/RtxtWriter.cs
new file mode 100644
index 00000000000..eb3a8abf038
--- /dev/null
+++ b/src/Xamarin.Android.Build.Tasks/Utilities/RtxtWriter.cs
@@ -0,0 +1,25 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+using System.Linq;
+using Microsoft.Android.Build.Tasks;
+using Microsoft.Build.Utilities;
+
+namespace Xamarin.Android.Tasks
+{
+ /// Write a list of Item to a file
+ ///
+ public class RtxtWriter {
+ public void Write (string file, IList items)
+ {
+ using (var sw = MemoryStreamPool.Shared.CreateStreamWriter ()) {
+ foreach (var item in items) {
+ sw.WriteLine (item.ToString ());
+ }
+ sw.Flush ();
+ Files.CopyIfStreamChanged (sw.BaseStream, file);
+ }
+ }
+ }
+}
diff --git a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.csproj b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.csproj
index ee9e29a5c39..dd902240461 100644
--- a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.csproj
+++ b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.csproj
@@ -348,6 +348,12 @@
Utilities\%(Filename)%(Extension)
+
+ Utilities\%(Filename)%(Extension)
+
+
+ Utilities\%(Filename)%(Extension)
+
@@ -407,6 +413,9 @@
JavaInteropTypeManager.java
+
+ Resource.Designer.snk
+
diff --git a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.targets b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.targets
index 7cb21493b26..cd4782e7bba 100644
--- a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.targets
+++ b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.targets
@@ -88,6 +88,10 @@
PreserveNewest
Xamarin.Android.Designer.targets
+
+ PreserveNewest
+ Xamarin.Android.Resource.Designer.targets
+
PreserveNewest
Xamarin.Android.Aapt.targets
diff --git a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets
index af4d1456749..14418bb0e23 100644
--- a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets
+++ b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets
@@ -57,6 +57,7 @@ Copyright (C) 2011-2012 Xamarin. All rights reserved.
+
@@ -375,6 +376,7 @@ Copyright (C) 2011-2012 Xamarin. All rights reserved.
+
@@ -574,11 +576,13 @@ Copyright (C) 2011-2012 Xamarin. All rights reserved.
+ Condition=" '$(Language)' == 'C#' and '$(ManagedDesignTimeBuild)' != 'true' ">
-
-
-
+
+
+
+
+
@@ -998,6 +1002,7 @@ because xbuild doesn't support framework reference assemblies.
<_PropertyCacheItems Include="AndroidSupportedAbis=$(AndroidSupportedAbis)" />
<_PropertyCacheItems Include="AndroidManifestPlaceholders=$(AndroidManifestPlaceholders)" />
<_PropertyCacheItems Include="ProjectFullPath=$(MSBuildProjectFullPath)" />
+ <_PropertyCacheItems Include="AndroidUseDesignerAssembly=$(AndroidUseDesignerAssembly)" />
-
+ DependsOnTargets="_CreatePropertiesCache;_ExtractLibraryProjectImports;_ValidateAndroidPackageProperties;_GenerateResourceCaseMap;_BeforeManagedUpdateAndroidResgen">
@@ -1080,11 +1086,11 @@ because xbuild doesn't support framework reference assemblies.
-
+
@@ -1244,6 +1250,7 @@ because xbuild doesn't support framework reference assemblies.
_GenerateAndroidResourceDir;
_IncludeLayoutBindingSources;
_DefineBuildTargetAbis;
+ _GenerateResourceCaseMap;
<_UpdateAndroidResgenInputs>
@(_AndroidMSBuildAllProjects);
@@ -1265,7 +1272,7 @@ because xbuild doesn't support framework reference assemblies.
+
+
+
+
+
+
-
-
+
+
-
-
-
-
+
+
+
+
@@ -1460,6 +1491,7 @@ because xbuild doesn't support framework reference assemblies.
DestinationFiles="@(ResolvedAssemblies->'$(MonoAndroidIntermediateAssemblyDir)%(DestinationSubPath)')"
TargetName="$(TargetName)"
AddKeepAlives="$(AndroidAddKeepAlives)"
+ UseDesignerAssembly="$(AndroidUseDesignerAssembly)"
Deterministic="$(Deterministic)"
UsingAndroidNETSdk="$(UsingAndroidNETSdk)"
/>
diff --git a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.DesignTime.targets b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.DesignTime.targets
index 2102d915d28..00f3f2cfdd5 100644
--- a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.DesignTime.targets
+++ b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.DesignTime.targets
@@ -42,6 +42,7 @@ This file is used by all project types, including binding projects.
true
true
+ True
False
<_AndroidLibraryImportsCache Condition=" '$(DesignTimeBuild)' == 'true' And !Exists ('$(_AndroidLibraryImportsCache)') ">$(_AndroidLibraryImportsDesignTimeCache)
<_AndroidLibraryProjectImportsCache Condition=" '$(DesignTimeBuild)' == 'true' And !Exists ('$(_AndroidLibraryProjectImportsCache)') ">$(_AndroidLibraryProjectImportsDesignTimeCache)
diff --git a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Legacy.targets b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Legacy.targets
index b08a0d94736..800cabe865b 100644
--- a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Legacy.targets
+++ b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Legacy.targets
@@ -108,8 +108,8 @@ projects. .NET 5 projects will not import this file.
$(CoreResolveReferencesDependsOn);
- UpdateAndroidInterfaceProxies;
UpdateAndroidResources;
+ UpdateAndroidInterfaceProxies;
$(DeferredBuildDependsOn);
@@ -300,6 +300,10 @@ projects. .NET 5 projects will not import this file.
Include="$(OutDir)$(TargetFileName)"
Condition="Exists ('$(OutDir)$(TargetFileName)')"
/>
+
+
diff --git a/tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs b/tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs
index f79b85b3222..7f780147517 100644
--- a/tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs
+++ b/tests/MSBuildDeviceIntegration/Tests/InstallAndRunTests.cs
@@ -706,6 +706,63 @@ public void RunWithLLVMEnabled ()
Path.Combine (Root, builder.ProjectDirectory, "startup-logcat.log")));
}
+ [Test]
+ public void ResourceDesignerWithNuGetReference ([Values ("net8.0-android33.0")] string dotnetTargetFramework)
+ {
+ AssertHasDevices ();
+
+ string path = Path.Combine (Root, "temp", TestName);
+
+ if (!Builder.UseDotNet) {
+ Assert.Ignore ("Skipping. Test not relevant under Classic.");
+ }
+ // Build a NuGet Package
+ var nuget = new XASdkProject (outputType: "Library") {
+ Sdk = "Xamarin.Legacy.Sdk/0.2.0-alpha2",
+ ProjectName = "Test.Nuget.Package",
+ IsRelease = true,
+ };
+ nuget.AddNuGetSourcesForOlderTargetFrameworks ();
+ nuget.Sources.Clear ();
+ nuget.Sources.Add (new AndroidItem.AndroidResource ("Resources/values/Strings.xml") {
+ TextContent = () => @"
+ Library Resource From Nuget
+",
+ });
+ nuget.SetProperty ("PackageName", "Test.Nuget.Package");
+ var legacyTargetFrameworkVersion = "13.0";
+ var legacyTargetFramework = $"monoandroid{legacyTargetFrameworkVersion}";
+ nuget.SetProperty ("TargetFramework", value: "");
+ nuget.SetProperty ("TargetFrameworks", value: $"{dotnetTargetFramework};{legacyTargetFramework}");
+
+ string directory = Path.Combine ("temp", TestName, "Test.Nuget.Package");
+ var dotnet = CreateDotNetBuilder (nuget, directory);
+ Assert.IsTrue (dotnet.Pack (), "`dotnet pack` should succeed");
+
+ // Build an app which references it.
+ var proj = new XamarinAndroidApplicationProject () {
+ IsRelease = true,
+ };
+ proj.SetAndroidSupportedAbis ("armeabi-v7a", "x86", "x86_64");
+ proj.OtherBuildItems.Add (new BuildItem ("None", "NuGet.config") {
+ TextContent = () => @"
+
+
+
+
+",
+ });
+ proj.PackageReferences.Add (new Package {
+ Id = "Test.Nuget.Package",
+ Version = "1.0.0",
+ });
+ builder = CreateApkBuilder (Path.Combine (path, proj.ProjectName));
+ Assert.IsTrue (builder.Install (proj, doNotCleanupOnUpdate: true), "Install should have succeeded.");
+ string resource_designer = GetResourceDesignerPath (builder, proj);
+ var contents = GetResourceDesignerText (proj, resource_designer);
+ StringAssert.Contains ("public const int library_resouce_from_nuget =", contents);
+ }
+
[Test]
public void SingleProject_ApplicationId ()
{
@@ -841,5 +898,30 @@ public MyLayout (Android.Content.Context context, Android.Util.IAttributeSet att
Path.Combine (Root, builder.ProjectDirectory, "startup-logcat.log"));
Assert.IsTrue (didStart, "Activity should have started.");
}
+
+ DotNetCLI CreateDotNetBuilder (string relativeProjectDir = null)
+ {
+ if (string.IsNullOrEmpty (relativeProjectDir)) {
+ relativeProjectDir = Path.Combine ("temp", TestName);
+ }
+ string fullProjectDirectory = Path.Combine (Root, relativeProjectDir);
+ TestOutputDirectories [TestContext.CurrentContext.Test.ID] = fullProjectDirectory;
+
+ new XASdkProject ().CopyNuGetConfig (relativeProjectDir);
+ return new DotNetCLI (Path.Combine (fullProjectDirectory, $"{TestName}.csproj"));
+ }
+
+ DotNetCLI CreateDotNetBuilder (XASdkProject project, string relativeProjectDir = null)
+ {
+ if (string.IsNullOrEmpty (relativeProjectDir)) {
+ relativeProjectDir = Path.Combine ("temp", TestName);
+ }
+ string fullProjectDirectory = Path.Combine (Root, relativeProjectDir);
+ TestOutputDirectories [TestContext.CurrentContext.Test.ID] = fullProjectDirectory;
+ var files = project.Save ();
+ project.Populate (relativeProjectDir, files);
+ project.CopyNuGetConfig (relativeProjectDir);
+ return new DotNetCLI (project, Path.Combine (fullProjectDirectory, project.ProjectFilePath));
+ }
}
}
diff --git a/tests/MSBuildDeviceIntegration/Tests/InstantRunTest.cs b/tests/MSBuildDeviceIntegration/Tests/InstantRunTest.cs
index d3ff4f02248..6605cba3a8d 100644
--- a/tests/MSBuildDeviceIntegration/Tests/InstantRunTest.cs
+++ b/tests/MSBuildDeviceIntegration/Tests/InstantRunTest.cs
@@ -56,6 +56,7 @@ public void TargetsSkipped ([Values(false, true)] bool useManagedResourceGenerat
UseLatestPlatformSdk = true,
};
proj.SetProperty ("AndroidUseManagedDesignTimeResourceGenerator", useManagedResourceGenerator.ToString ());
+ proj.SetProperty ("AndroidUseDesignerAssembly", "False");
var b = CreateApkBuilder ($"temp/InstantRunTargetsSkipped_{useManagedResourceGenerator}", cleanupOnDispose: false);
Assert.IsTrue (b.Build (proj), "1 build should have succeeded.");
diff --git a/tests/Mono.Android-Tests/Java.Interop-Tests/Java.Interop-Tests.csproj b/tests/Mono.Android-Tests/Java.Interop-Tests/Java.Interop-Tests.csproj
index 84fbd2ae182..80c5cb93874 100644
--- a/tests/Mono.Android-Tests/Java.Interop-Tests/Java.Interop-Tests.csproj
+++ b/tests/Mono.Android-Tests/Java.Interop-Tests/Java.Interop-Tests.csproj
@@ -59,6 +59,7 @@
+
diff --git a/tests/Mono.Android-Tests/Mono.Android-Test.Shared.projitems b/tests/Mono.Android-Tests/Mono.Android-Test.Shared.projitems
index 16b42acd14b..052219da8fc 100644
--- a/tests/Mono.Android-Tests/Mono.Android-Test.Shared.projitems
+++ b/tests/Mono.Android-Tests/Mono.Android-Test.Shared.projitems
@@ -13,8 +13,8 @@
-
+
diff --git a/tests/Mono.Android-Tests/Runtime-MultiDex/Mono.Android-TestsMultiDex.csproj b/tests/Mono.Android-Tests/Runtime-MultiDex/Mono.Android-TestsMultiDex.csproj
index 848607e2899..b7146ae1ca7 100644
--- a/tests/Mono.Android-Tests/Runtime-MultiDex/Mono.Android-TestsMultiDex.csproj
+++ b/tests/Mono.Android-Tests/Runtime-MultiDex/Mono.Android-TestsMultiDex.csproj
@@ -24,6 +24,7 @@
<_SkipJniAddNativeMethodRegistrationAttributeScan>True
<_MonoAndroidTestPackage>Mono.Android_TestsMultiDex
-MultiDex
+ True
@@ -67,7 +68,6 @@
-