From 74e373d0d494392af1e27071c3047e572d11e315 Mon Sep 17 00:00:00 2001 From: Dean Ellis Date: Wed, 28 Sep 2016 16:02:03 +0100 Subject: [PATCH 001/308] Initial Commit --- external/xamarin-android-tools/README.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 external/xamarin-android-tools/README.md diff --git a/external/xamarin-android-tools/README.md b/external/xamarin-android-tools/README.md new file mode 100644 index 00000000000..32ee851c44a --- /dev/null +++ b/external/xamarin-android-tools/README.md @@ -0,0 +1 @@ +# xamarin-android-tools From 6d29b3aa11c968996b7438a60f38f1440fe3d785 Mon Sep 17 00:00:00 2001 From: Dean Ellis Date: Wed, 28 Sep 2016 16:41:36 +0100 Subject: [PATCH 002/308] Initial Code drop Moved most fo the code from xamarin-android Xamarin.Androd.Build.Tools to this repository. The code deals with finding and resolving the android-sdk and ndk directories. This code can be shared between xamarin-android and the IDE's rather than having mutlipe copies of the same files. --- external/xamarin-android-tools/.gitignore | 12 + external/xamarin-android-tools/README.md | 2 + .../src/Xamarin.Android.Tools.sln | 17 + .../src/Xamarin.Android.Tools/.DS_Store | Bin 0 -> 6148 bytes .../Xamarin.Android.Tools/AndroidLogger.cs | 129 ++++++++ .../src/Xamarin.Android.Tools/AndroidSdk.cs | 148 +++++++++ .../AndroidTargetArch.cs | 19 ++ .../Xamarin.Android.Tools/AndroidVersion.cs | 108 +++++++ .../src/Xamarin.Android.Tools/MonoDroidSdk.cs | 60 ++++ .../src/Xamarin.Android.Tools/OS.cs | 227 ++++++++++++++ .../src/Xamarin.Android.Tools/ProcessUtils.cs | 124 ++++++++ .../Properties/AssemblyInfo.cs | 15 + .../Sdks/AndroidSdkBase.cs | 183 +++++++++++ .../Sdks/AndroidSdkUnix.cs | 233 ++++++++++++++ .../Sdks/AndroidSdkWindows.cs | 202 ++++++++++++ .../Sdks/MonoDroidSdkBase.cs | 292 ++++++++++++++++++ .../Sdks/MonoDroidSdkUnix.cs | 102 ++++++ .../Sdks/MonoDroidSdkWindows.cs | 76 +++++ .../Xamarin.Android.Tools.csproj | 55 ++++ .../Xamarin.Android.Tools.userprefs | 26 ++ 20 files changed, 2030 insertions(+) create mode 100644 external/xamarin-android-tools/.gitignore create mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools.sln create mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools/.DS_Store create mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools/AndroidLogger.cs create mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools/AndroidSdk.cs create mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools/AndroidTargetArch.cs create mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools/AndroidVersion.cs create mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools/MonoDroidSdk.cs create mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools/OS.cs create mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools/ProcessUtils.cs create mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools/Properties/AssemblyInfo.cs create mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools/Sdks/AndroidSdkBase.cs create mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools/Sdks/AndroidSdkUnix.cs create mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools/Sdks/AndroidSdkWindows.cs create mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools/Sdks/MonoDroidSdkBase.cs create mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools/Sdks/MonoDroidSdkUnix.cs create mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools/Sdks/MonoDroidSdkWindows.cs create mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools/Xamarin.Android.Tools.csproj create mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools/Xamarin.Android.Tools.userprefs diff --git a/external/xamarin-android-tools/.gitignore b/external/xamarin-android-tools/.gitignore new file mode 100644 index 00000000000..4b5771f1bd2 --- /dev/null +++ b/external/xamarin-android-tools/.gitignore @@ -0,0 +1,12 @@ +~ +*# +*.pdb +*.mdb +src/*.dll +*.pidb +*.suo +*.userprefs +*/.DS_Store +.DS_Store +bin +**/obj diff --git a/external/xamarin-android-tools/README.md b/external/xamarin-android-tools/README.md index 32ee851c44a..f09ff17c430 100644 --- a/external/xamarin-android-tools/README.md +++ b/external/xamarin-android-tools/README.md @@ -1 +1,3 @@ # xamarin-android-tools + + diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.sln b/external/xamarin-android-tools/src/Xamarin.Android.Tools.sln new file mode 100644 index 00000000000..ed952df4246 --- /dev/null +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.sln @@ -0,0 +1,17 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2012 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Xamarin.Android.Tools", "Xamarin.Android.Tools/Xamarin.Android.Tools.csproj", "{91713046-C358-4647-B162-ED4E1442F3D8}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {91713046-C358-4647-B162-ED4E1442F3D8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {91713046-C358-4647-B162-ED4E1442F3D8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {91713046-C358-4647-B162-ED4E1442F3D8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {91713046-C358-4647-B162-ED4E1442F3D8}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools/.DS_Store b/external/xamarin-android-tools/src/Xamarin.Android.Tools/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..5008ddfcf53c02e82d7eee2e57c38e5672ef89f6 GIT binary patch literal 6148 zcmeH~Jr2S!425mzP>H1@V-^m;4Wg<&0T*E43hX&L&p$$qDprKhvt+--jT7}7np#A3 zem<@ulZcFPQ@L2!n>{z**++&mCkOWA81W14cNZlEfg7;MkzE(HCqgga^y>{tEnwC%0;vJ&^%eQ zLs35+`xjp>T0 l.ApiLevel.ToString ()).ToArray (); + string levelList; + if (levels == null || levels.Length == 0) + levelList = "(none)"; + else + levelList = string.Join (", ", levels); + AndroidLogger.LogInfo (null, "Found Android SDK. API levels: {0}", levelList); + } else { + AndroidLogger.LogInfo (null, "Did not find Android SDK"); + } + } catch (Exception ex) { + AndroidLogger.LogError ("Error finding Android/Java SDKs", ex); + } + } + + public static IEnumerable GetBuildToolsPaths (string preferredBuildToolsVersion) + { + if (!string.IsNullOrEmpty (preferredBuildToolsVersion)) { + var preferredDir = Path.Combine (AndroidSdkPath, "build-tools", preferredBuildToolsVersion); + if (Directory.Exists (preferredDir)) + return new[] { preferredDir }.Concat (GetBuildToolsPaths ().Where (p => p!= preferredDir)); + } + return GetBuildToolsPaths (); + } + + public static IEnumerable GetBuildToolsPaths () + { + ValidatePath (AndroidSdkPath); + + var buildTools = Path.Combine (AndroidSdkPath, "build-tools"); + if (Directory.Exists (buildTools)) { + var preview = Directory.EnumerateDirectories (buildTools) + .Where(x => TryParseVersion (Path.GetFileName (x)) == null) + .Select(x => x); + + foreach (var d in preview) + yield return d; + + var sorted = from p in Directory.EnumerateDirectories (buildTools) + let version = TryParseVersion (Path.GetFileName (p)) + where version != null + orderby version descending + select p; + + foreach (var d in sorted) + yield return d; + } + var ptPath = Path.Combine (AndroidSdkPath, "platform-tools"); + if (Directory.Exists (ptPath)) + yield return ptPath; + } + + static Version TryParseVersion (string v) + { + Version version; + if (Version.TryParse (v, out version)) + return version; + return null; + } + + // it was useful when android-21 was android-L, or android-23 was android-MNC. + // We will use this when similar preview release is out. + static string ToApiName (int apiLevel) + { + return apiLevel.ToString (); + } + + static string ValidatePath (string path) + { + if (String.IsNullOrEmpty (path)) + throw new InvalidOperationException ("This property is not valid when the SDK is not installed"); + return path; + } + + public static string GetPlatformDirectory (int apiLevel) + { + return Path.Combine (AndroidSdkPath, "platforms", "android-" + ToApiName (apiLevel)); + } + + public static string GetPlatformDirectory (string osVersion) + { + var level = AndroidVersion.TryOSVersionToApiLevel (osVersion); + if (level == 0) + return null; + return GetPlatformDirectory (level); + } + + public static bool IsPlatformInstalled (int apiLevel) + { + return apiLevel != 0 && Directory.Exists (GetPlatformDirectory (apiLevel)); + } + + public static IEnumerable GetInstalledPlatformVersions () + { + var knownAndInstalledSdkLevels = AndroidVersion.KnownVersions.Where (v => IsPlatformInstalled (v.ApiLevel)); + + return knownAndInstalledSdkLevels.Where (version => { + var apiLevel = MonoDroidSdk.GetApiLevelForFrameworkVersion (version.OSVersion); + return MonoDroidSdk.IsSupportedFrameworkLevel (apiLevel); + }); + } + + public static bool IsInstalled { + get { + return !string.IsNullOrEmpty (AndroidSdkPath) && !string.IsNullOrEmpty (JavaSdkPath); + } + } + + public static string AndroidNdkPath { + get { return sdk.AndroidNdkPath; } + } + + public static string AndroidSdkPath { + get { return sdk.AndroidSdkPath; } + } + + public static string JavaSdkPath { + get { return sdk.JavaSdkPath; } + } + + public static string AndroidNdkHostPlatform { + get { return sdk.NdkHostPlatform; } + } + } +} + diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools/AndroidTargetArch.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools/AndroidTargetArch.cs new file mode 100644 index 00000000000..a54c57b8f3b --- /dev/null +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools/AndroidTargetArch.cs @@ -0,0 +1,19 @@ +using System; +using System.Text.RegularExpressions; +using System.IO; + +namespace Xamarin.Android.Tools +{ + [Flags] + public enum AndroidTargetArch + { + None = 0, + Arm = 1, + X86 = 2, + Mips = 4, + Arm64 = 8, + X86_64 = 16, + Other = 0x10000 // hope it's not too optimistic + } +} + diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools/AndroidVersion.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools/AndroidVersion.cs new file mode 100644 index 00000000000..d54f4b25615 --- /dev/null +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools/AndroidVersion.cs @@ -0,0 +1,108 @@ +using System; + +namespace Xamarin.Android.Tools +{ + public class AndroidVersion + { + public static readonly int MaxApiLevel = 24; + + public AndroidVersion (int apilevel, string osVersion) + { + this.ApiLevel = apilevel; + this.OSVersion = osVersion; + } + + AndroidVersion (int apilevel, string osVersion, string codeName, Version version) + { + this.ApiLevel = apilevel; + // TODO: remove osVersion from parameter list and generate from version + this.OSVersion = osVersion; + this.CodeName = codeName; + this.Version = version; + } + + public int ApiLevel { get; private set; } + public string OSVersion { get; private set; } + public string CodeName { get; private set; } + public Version Version { get; private set; } + + public static int OSVersionToApiLevel (string osVersion) + { + int ret = TryOSVersionToApiLevel (osVersion); + if (ret == 0) + throw new ArgumentOutOfRangeException ("OS version not recognized: " + osVersion); + return ret; + } + + public static int TryOSVersionToApiLevel (string frameworkVersion) + { + // Use MonoDroidSdk.GetApiLevelForFrameworkVersion because that will translate XA versions >= 5.xx to the correct api level + var apiLevelText = MonoDroidSdk.GetApiLevelForFrameworkVersion (frameworkVersion); + int apiLevel; + int.TryParse (apiLevelText, out apiLevel); + return apiLevel; + } + + public static string ApiLevelToOSVersion (int apiLevel) + { + string ret = TryApiLevelToOSVersion (apiLevel); + if (ret == null) + throw new ArgumentOutOfRangeException ("API level not recognized: " + apiLevel); + return ret; + } + + public static string TryApiLevelToOSVersion (int apiLevel) + { + var osVersion = MonoDroidSdk.GetFrameworkVersionForApiLevel (apiLevel.ToString ()); + if (!string.IsNullOrEmpty (osVersion)) + return osVersion.TrimStart ('v'); + return null; + } + + public static string TryOSVersionToCodeName (string frameworkVersion) + { + // match on API level, the framework version might not match what we have here (>= XA 5.x uses a different version scheme) + var apiLevel = TryOSVersionToApiLevel (frameworkVersion); + + foreach (AndroidVersion version in KnownVersions) + if (version.ApiLevel == apiLevel) + return version.CodeName; + return null; + } + + public static string TryFrameworkVersionToOSVersion (string frameworkVersion) + { + // match on API level, the framework version might not match what we have here (>= XA 5.x uses a different version scheme) + var apiLevel = TryOSVersionToApiLevel (frameworkVersion); + + foreach (AndroidVersion version in KnownVersions) + if (version.ApiLevel == apiLevel) + return version.OSVersion; + return null; + } + + public static AndroidVersion[] KnownVersions = new[] { + new AndroidVersion (4, "1.6", "Donut", new Version (1, 6)), + new AndroidVersion (5, "2.0", "Eclair", new Version (2, 0)), + new AndroidVersion (6, "2.0.1", "Eclair", new Version (2, 0, 1)), + new AndroidVersion (7, "2.1", "Eclair", new Version (2, 1)), + new AndroidVersion (8, "2.2", "Froyo", new Version (2, 2)), + new AndroidVersion (10, "2.3", "Gingerbread", new Version (2, 3)), + new AndroidVersion (11, "3.0", "Honeycomb", new Version (3, 0)), + new AndroidVersion (12, "3.1", "Honeycomb", new Version (3, 1)), + new AndroidVersion (13, "3.2", "Honeycomb", new Version (3, 2)), + new AndroidVersion (14, "4.0", "Ice Cream Sandwich", new Version (4, 0)), + new AndroidVersion (15, "4.0.3", "Ice Cream Sandwich", new Version (4, 0, 3)), + new AndroidVersion (16, "4.1", "Jelly Bean", new Version (4, 1)), + new AndroidVersion (17, "4.2", "Jelly Bean", new Version (4, 2)), + new AndroidVersion (18, "4.3", "Jelly Bean", new Version (4, 3)), + new AndroidVersion (19, "4.4", "Kit Kat", new Version (4, 4)), + new AndroidVersion (20, "4.4.87", "Kit Kat + Wear support", new Version (4, 4, 87)), + new AndroidVersion (21, "5.0", "Lollipop", new Version (5, 0)), + new AndroidVersion (22, "5.1", "Lollipop", new Version (5, 1)), + new AndroidVersion (23, "6.0", "Marshmallow", new Version (6, 0)), + new AndroidVersion (24, "7.0", "Nougat", new Version (7, 0)), + }; + } +} + diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools/MonoDroidSdk.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools/MonoDroidSdk.cs new file mode 100644 index 00000000000..b5edd619c41 --- /dev/null +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools/MonoDroidSdk.cs @@ -0,0 +1,60 @@ +using System; +using System.IO; + +namespace Xamarin.Android.Tools +{ + public static class MonoDroidSdk + { + static MonoDroidSdkBase sdk; + + public static string GetApiLevelForFrameworkVersion (string framework) + { + return GetSdk ().GetApiLevelForFrameworkVersion (framework); + } + + public static string GetFrameworkVersionForApiLevel (string apiLevel) + { + return GetSdk ().GetFrameworkVersionForApiLevel (apiLevel); + } + + public static bool IsSupportedFrameworkLevel (string apiLevel) + { + return GetSdk ().IsSupportedFrameworkLevel (apiLevel); + } + + public static void Refresh (string runtimePath = null, string binPath = null, string bclPath = null) + { + if (OS.IsWindows) { + sdk = new MonoDroidSdkWindows (); + } else { + sdk = new MonoDroidSdkUnix (); + } + + try { + sdk.Initialize (runtimePath, binPath, bclPath); + } catch (Exception ex) { + AndroidLogger.LogError ("Error finding Xamarin.Android SDK", ex); + } + } + + static MonoDroidSdkBase GetSdk () + { + if (sdk == null) { + Refresh (); + } + return sdk; + } + + public static string RuntimePath { get { return GetSdk ().RuntimePath; } } + + public static string BinPath { get { return GetSdk ().BinPath; } } + + public static string FrameworkPath { get { return GetSdk ().BclPath; } } + + [Obsolete ("Do not use.")] + public static string JavaDocToMDocExe { + get { return Path.Combine (BinPath, OS.IsWindows ? "javadoc-to-mdoc.exe" : "javadoc-to-mdoc"); } + } + } +} + diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools/OS.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools/OS.cs new file mode 100644 index 00000000000..7c04c471934 --- /dev/null +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools/OS.cs @@ -0,0 +1,227 @@ +using System; +using System.Runtime.InteropServices; +using System.IO; +using System.Text; + +namespace Xamarin.Android.Tools +{ + public class OS + { + public readonly static bool IsWindows; + public readonly static bool IsMac; + + internal readonly static string ProgramFilesX86; + + static OS () + { + IsWindows = Path.DirectorySeparatorChar == '\\'; + IsMac = !IsWindows && IsRunningOnMac (); + + if (IsWindows) { + ProgramFilesX86 = GetProgramFilesX86 (); + } + } + + //From Managed.Windows.Forms/XplatUI + static bool IsRunningOnMac () + { + IntPtr buf = IntPtr.Zero; + try { + buf = Marshal.AllocHGlobal (8192); + // This is a hacktastic way of getting sysname from uname () + if (uname (buf) == 0) { + string os = System.Runtime.InteropServices.Marshal.PtrToStringAnsi (buf); + if (os == "Darwin") + return true; + } + } catch { + } finally { + if (buf != IntPtr.Zero) + System.Runtime.InteropServices.Marshal.FreeHGlobal (buf); + } + return false; + } + + [DllImport ("libc")] + static extern int uname (IntPtr buf); + + static string GetProgramFilesX86 () + { + //SpecialFolder.ProgramFilesX86 is broken on 32-bit WinXP + if (IntPtr.Size == 8) { + return Environment.GetFolderPath (Environment.SpecialFolder.ProgramFilesX86); + } else { + return Environment.GetFolderPath (Environment.SpecialFolder.ProgramFiles); + } + } + + internal static string GetXamarinAndroidCacheDir () + { + if (IsMac) { + var home = Environment.GetFolderPath (Environment.SpecialFolder.Personal); + return Path.Combine (home, "Library", "Caches", "Xamarin.Android"); + } else if (IsWindows) { + var localAppData = Environment.GetFolderPath (Environment.SpecialFolder.LocalApplicationData); + return Path.Combine (localAppData, "Xamarin.Android", "Cache"); + } else { + var home = Environment.GetFolderPath (Environment.SpecialFolder.Personal); + var xdgCacheHome = Environment.GetEnvironmentVariable ("XDG_CACHE_HOME"); + if (string.IsNullOrEmpty (xdgCacheHome)) { + xdgCacheHome = Path.Combine (home, ".cache"); + } + return Path.Combine (xdgCacheHome, "Xamarin.Android"); + } + } + } + + public static class KernelEx { + [DllImport ("kernel32.dll", CharSet = CharSet.Auto)] + static extern int GetLongPathName ( + [MarshalAs (UnmanagedType.LPTStr)] string path, + [MarshalAs (UnmanagedType.LPTStr)] StringBuilder longPath, + int longPathLength + ); + + public static string GetLongPathName (string path) + { + StringBuilder sb = new StringBuilder (255); + GetLongPathName (path, sb, sb.Capacity); + return sb.ToString (); + } + + [DllImport ("kernel32.dll", CharSet = CharSet.Auto)] + static extern int GetShortPathName ( + [MarshalAs (UnmanagedType.LPTStr)] string path, + [MarshalAs (UnmanagedType.LPTStr)] StringBuilder shortPath, + int shortPathLength + ); + + public static string GetShortPathName (string path) + { + StringBuilder sb = new StringBuilder (255); + GetShortPathName (path, sb, sb.Capacity); + return sb.ToString (); + } + } + + internal static class RegistryEx + { + const string ADVAPI = "advapi32.dll"; + + public static UIntPtr CurrentUser = (UIntPtr)0x80000001; + public static UIntPtr LocalMachine = (UIntPtr)0x80000002; + + [DllImport (ADVAPI, CharSet = CharSet.Unicode, SetLastError = true)] + static extern int RegOpenKeyEx (UIntPtr hKey, string subKey, uint reserved, uint sam, out UIntPtr phkResult); + + [DllImport (ADVAPI, CharSet = CharSet.Unicode, SetLastError = true)] + static extern int RegQueryValueExW (UIntPtr hKey, string lpValueName, int lpReserved, out uint lpType, + StringBuilder lpData, ref uint lpcbData); + + [DllImport (ADVAPI, CharSet = CharSet.Unicode, SetLastError = true)] + static extern int RegSetValueExW (UIntPtr hKey, string lpValueName, int lpReserved, + uint dwType, string data, uint cbData); + + [DllImport (ADVAPI, CharSet = CharSet.Unicode, SetLastError = true)] + static extern int RegSetValueExW (UIntPtr hKey, string lpValueName, int lpReserved, + uint dwType, IntPtr data, uint cbData); + + [DllImport (ADVAPI, CharSet = CharSet.Unicode, SetLastError = true)] + static extern int RegCreateKeyEx (UIntPtr hKey, string subKey, uint reserved, string @class, uint options, + uint samDesired, IntPtr lpSecurityAttributes, out UIntPtr phkResult, out Disposition lpdwDisposition); + + [DllImport ("advapi32.dll", SetLastError = true)] + static extern int RegCloseKey (UIntPtr hKey); + + public static string GetValueString (UIntPtr key, string subkey, string valueName, Wow64 wow64) + { + UIntPtr regKeyHandle; + uint sam = (uint)Rights.QueryValue + (uint)wow64; + if (RegOpenKeyEx (key, subkey, 0, sam, out regKeyHandle) != 0) + return null; + + try { + uint type; + var sb = new StringBuilder (2048); + uint cbData = (uint) sb.Capacity; + if (RegQueryValueExW (regKeyHandle, valueName, 0, out type, sb, ref cbData) == 0) { + return sb.ToString (); + } + return null; + } finally { + RegCloseKey (regKeyHandle); + } + } + + public static void SetValueString (UIntPtr key, string subkey, string valueName, string value, Wow64 wow64) + { + UIntPtr regKeyHandle; + uint sam = (uint)(Rights.CreateSubKey | Rights.SetValue) + (uint)wow64; + uint options = (uint) Options.NonVolatile; + Disposition disposition; + if (RegCreateKeyEx (key, subkey, 0, null, options, sam, IntPtr.Zero, out regKeyHandle, out disposition) != 0) { + throw new Exception ("Could not open or craete key"); + } + + try { + uint type = (uint)ValueType.String; + uint lenBytesPlusNull = ((uint)value.Length + 1) * 2; + var result = RegSetValueExW (regKeyHandle, valueName, 0, type, value, lenBytesPlusNull); + if (result != 0) + throw new Exception (string.Format ("Error {0} setting registry key '{1}{2}@{3}'='{4}'", + result, key, subkey, valueName, value)); + } finally { + RegCloseKey (regKeyHandle); + } + } + + [Flags] + enum Rights : uint + { + None = 0, + QueryValue = 0x0001, + SetValue = 0x0002, + CreateSubKey = 0x0004, + EnumerateSubKey = 0x0008, + } + + enum Options + { + BackupRestore = 0x00000004, + CreateLink = 0x00000002, + NonVolatile = 0x00000000, + Volatile = 0x00000001, + } + + public enum Wow64 : uint + { + Key64 = 0x0100, + Key32 = 0x0200, + } + + enum ValueType : uint + { + None = 0, //REG_NONE + String = 1, //REG_SZ + UnexpandedString = 2, //REG_EXPAND_SZ + Binary = 3, //REG_BINARY + DWord = 4, //REG_DWORD + DWordLittleEndian = 4, //REG_DWORD_LITTLE_ENDIAN + DWordBigEndian = 5, //REG_DWORD_BIG_ENDIAN + Link = 6, //REG_LINK + MultiString = 7, //REG_MULTI_SZ + ResourceList = 8, //REG_RESOURCE_LIST + FullResourceDescriptor = 9, //REG_FULL_RESOURCE_DESCRIPTOR + ResourceRequirementsList = 10, //REG_RESOURCE_REQUIREMENTS_LIST + QWord = 11, //REG_QWORD + QWordLittleEndian = 11, //REG_QWORD_LITTLE_ENDIAN + } + + enum Disposition : uint + { + CreatedNewKey = 0x00000001, + OpenedExistingKey = 0x00000002, + } + } +} + diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools/ProcessUtils.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools/ProcessUtils.cs new file mode 100644 index 00000000000..3dc244fde0c --- /dev/null +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools/ProcessUtils.cs @@ -0,0 +1,124 @@ +using System; +using System.Diagnostics; +using System.Threading.Tasks; +using System.IO; +using System.Threading; + +namespace Xamarin.Android.Tools +{ + public static class ProcessUtils + { + public static async Task StartProcess (ProcessStartInfo psi, TextWriter stdout, TextWriter stderr, CancellationToken cancellationToken, Action onStarted = null) + { + cancellationToken.ThrowIfCancellationRequested (); + psi.UseShellExecute = false; + psi.RedirectStandardOutput |= stdout != null; + psi.RedirectStandardError |= stderr != null; + + var process = new Process { + StartInfo = psi, + EnableRaisingEvents = true, + }; + + Task output = Task.FromResult (true); + Task error = Task.FromResult (true); + Task exit = WaitForExitAsync (process); + using (process) { + process.Start (); + if (onStarted != null) + onStarted (process); + + // If the token is cancelled while we're running, kill the process. + // Otherwise once we finish the Task.WhenAll we can remove this registration + // as there is no longer any need to Kill the process. + // + // We wrap `stdout` and `stderr` in syncronized wrappers for safety in case they + // end up writing to the same buffer, or they are the same object. + using (cancellationToken.Register (() => KillProcess (process))) { + if (psi.RedirectStandardOutput) + output = ReadStreamAsync (process.StandardOutput, TextWriter.Synchronized (stdout)); + + if (psi.RedirectStandardError) + error = ReadStreamAsync (process.StandardError, TextWriter.Synchronized (stderr)); + + await Task.WhenAll (new [] { output, error, exit }).ConfigureAwait (false); + } + // If we invoke 'KillProcess' our output, error and exit tasks will all complete normally. + // To protected against passing the user incomplete data we have to call + // `cancellationToken.ThrowIfCancellationRequested ()` here. + cancellationToken.ThrowIfCancellationRequested (); + return process.ExitCode; + } + } + + static void KillProcess (Process p) + { + try { + p.Kill (); + } catch (InvalidOperationException) { + // If the process has already exited this could happen + } + } + + static Task WaitForExitAsync (Process process) + { + var exitDone = new TaskCompletionSource (); + process.Exited += (o, e) => exitDone.TrySetResult (true); + return exitDone.Task; + } + + static async Task ReadStreamAsync (StreamReader stream, TextWriter destination) + { + int read; + var buffer = new char [4096]; + while ((read = await stream.ReadAsync (buffer, 0, buffer.Length).ConfigureAwait (false)) > 0) + destination.Write (buffer, 0, read); + } + + /// + /// Executes an Android Sdk tool and returns a result. The result is based on a function of the command output. + /// + public static Task ExecuteToolAsync (string exe, Func result, CancellationToken token, Action onStarted = null) + { + var tcs = new TaskCompletionSource (); + + var log = new StringWriter (); + var error = new StringWriter (); + + var psi = new ProcessStartInfo (exe); + psi.CreateNoWindow = true; + psi.RedirectStandardInput = onStarted != null; + + var processTask = ProcessUtils.StartProcess (psi, log, error, token, onStarted); + var exeName = Path.GetFileName (exe); + + processTask.ContinueWith (t => { + var output = log.ToString (); + var errorOutput = error.ToString (); + log.Dispose (); + error.Dispose (); + + if (t.IsCanceled) { + tcs.TrySetCanceled (); + return; + } + + if (t.IsFaulted) { + tcs.TrySetException (t.Exception.Flatten ().InnerException); + return; + } + + if (t.Result == 0) { + tcs.TrySetResult (result != null ? result (output) : default (TResult)); + } else { + var errorMessage = !string.IsNullOrEmpty (errorOutput) ? errorOutput : output; + + tcs.TrySetException (new InvalidOperationException (string.IsNullOrEmpty (errorMessage) ? exeName + " returned non-zero exit code" : string.Format ("{0} : {1}", t.Result, errorMessage))); + } + }, TaskContinuationOptions.ExecuteSynchronously); + + return tcs.Task; + } + } +} + diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools/Properties/AssemblyInfo.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools/Properties/AssemblyInfo.cs new file mode 100644 index 00000000000..45dfcd0de40 --- /dev/null +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools/Properties/AssemblyInfo.cs @@ -0,0 +1,15 @@ +using System.Reflection; +using System.Runtime.CompilerServices; + +[assembly: AssemblyTitle ("Xamarin.Android.Tools")] +[assembly: AssemblyDescription ("")] +[assembly: AssemblyConfiguration ("")] +[assembly: AssemblyCompany ("Xamarin")] +[assembly: AssemblyProduct ("Xamarin.Android.Tools")] +[assembly: AssemblyCopyright ("Copyright © Xamarin 2011-2016")] +[assembly: AssemblyTrademark ("")] +[assembly: AssemblyCulture ("")] + +[assembly: AssemblyVersion ("1.0.0.0")] +[assembly: AssemblyFileVersion ("1.0.0.0")] + diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools/Sdks/AndroidSdkBase.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools/Sdks/AndroidSdkBase.cs new file mode 100644 index 00000000000..7a91096b23c --- /dev/null +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools/Sdks/AndroidSdkBase.cs @@ -0,0 +1,183 @@ +using System; +using System.Linq; +using System.IO; +using System.Collections.Generic; + +namespace Xamarin.Android.Tools +{ + abstract class AndroidSdkBase + { + string[] allAndroidSdks = null; + string[] allAndroidNdks = null; + + public string[] AllAndroidSdks { + get { + if (allAndroidSdks == null) + allAndroidSdks = GetAllAvailableAndroidSdks ().Distinct ().ToArray (); + return allAndroidSdks; + } + } + public string[] AllAndroidNdks { + get { + if (allAndroidNdks == null) + allAndroidNdks = GetAllAvailableAndroidNdks ().Distinct ().ToArray (); + return allAndroidNdks; + } + } + + public string AndroidSdkPath { get; private set; } + public string AndroidNdkPath { get; private set; } + public string JavaSdkPath { get; private set; } + public string JavaBinPath { get; private set; } + public string AndroidToolsPath { get; private set; } + public string AndroidPlatformToolsPath { get; private set; } + public string AndroidToolsPathShort { get; private set; } + public string AndroidPlatformToolsPathShort { get; private set; } + + public virtual string Adb { get; protected set; } = "adb"; + public virtual string Android { get; protected set; } = "android"; + public virtual string Emulator { get; protected set; } = "emulator"; + public virtual string Monitor { get; protected set; } = "monitor"; + public virtual string ZipAlign { get; protected set; } = "zipalign"; + public virtual string JarSigner { get; protected set; } = "jarsigner"; + public virtual string KeyTool { get; protected set; } = "keytool"; + + public virtual string NdkStack { get; protected set; } = "ndk-stack"; + public abstract string NdkHostPlatform32Bit { get; } + public abstract string NdkHostPlatform64Bit { get; } + public virtual string Javac { get; protected set; } = "javac"; + + public abstract string PreferedAndroidSdkPath { get; } + public abstract string PreferedAndroidNdkPath { get; } + public abstract string PreferedJavaSdkPath { get; } + + public virtual void Initialize (string androidSdkPath = null, string androidNdkPath = null, string javaSdkPath = null) + { + AndroidSdkPath = ValidateAndroidSdkLocation (androidSdkPath) ? androidSdkPath : AllAndroidSdks.FirstOrDefault (); + AndroidNdkPath = ValidateAndroidNdkLocation (androidNdkPath) ? androidNdkPath : AllAndroidNdks.FirstOrDefault (); + JavaSdkPath = ValidateJavaSdkLocation (javaSdkPath) ? javaSdkPath : GetJavaSdkPath (); + + if (!string.IsNullOrEmpty (JavaSdkPath)) { + JavaBinPath = Path.Combine (JavaSdkPath, "bin"); + } else { + JavaBinPath = null; + } + + if (!string.IsNullOrEmpty (AndroidSdkPath)) { + AndroidToolsPath = Path.Combine (AndroidSdkPath, "tools"); + AndroidToolsPathShort = GetShortFormPath (AndroidToolsPath); + AndroidPlatformToolsPath = Path.Combine (AndroidSdkPath, "platform-tools"); + AndroidPlatformToolsPathShort = GetShortFormPath (AndroidPlatformToolsPath); + } else { + AndroidToolsPath = null; + AndroidToolsPathShort = null; + AndroidPlatformToolsPath = null; + AndroidPlatformToolsPathShort = null; + } + + if (!string.IsNullOrEmpty (AndroidNdkPath)) { + // It would be nice if .NET had real globbing support in System.IO... + string toolchainsDir = Path.Combine (AndroidNdkPath, "toolchains"); + IsNdk64Bit = Directory.EnumerateDirectories (toolchainsDir, "arm-linux-androideabi-*") + .Any (dir => Directory.Exists (Path.Combine (dir, "prebuilt", NdkHostPlatform64Bit))); + } + // we need to look for extensions other than the default .exe|.bat + // google have a habbit of changing them. + Adb = GetExecutablePath (AndroidPlatformToolsPath, Adb); + Android = GetExecutablePath (AndroidToolsPath, Android); + Emulator = GetExecutablePath (AndroidToolsPath, Emulator); + Monitor = GetExecutablePath (AndroidToolsPath, Monitor); + NdkStack = GetExecutablePath (AndroidNdkPath, NdkStack); + } + + protected abstract IEnumerable GetAllAvailableAndroidSdks (); + protected abstract IEnumerable GetAllAvailableAndroidNdks (); + protected abstract string GetJavaSdkPath (); + protected abstract string GetShortFormPath (string path); + + public abstract void SetPreferredAndroidSdkPath (string path); + public abstract void SetPreferredJavaSdkPath (string path); + public abstract void SetPreferredAndroidNdkPath (string path); + + public bool IsNdk64Bit { get; private set; } + + public string NdkHostPlatform { + get { return IsNdk64Bit ? NdkHostPlatform64Bit : NdkHostPlatform32Bit; } + } + + /// + /// Checks that a value is the location of an Android SDK. + /// + public bool ValidateAndroidSdkLocation (string loc) + { + return !string.IsNullOrEmpty (loc) && FindExecutableInDirectory (Adb, Path.Combine (loc, "platform-tools")).Any (); + } + + /// + /// Checks that a value is the location of a Java SDK. + /// + public virtual bool ValidateJavaSdkLocation (string loc) + { + return !string.IsNullOrEmpty (loc) && FindExecutableInDirectory (JarSigner, Path.Combine (loc, "bin")).Any (); + } + + /// + /// Checks that a value is the location of an Android SDK. + /// + public bool ValidateAndroidNdkLocation (string loc) + { + return !string.IsNullOrEmpty (loc) && FindExecutableInDirectory(NdkStack, loc).Any(); + } + + protected IEnumerable FindExecutableInPath (string executable) + { + var path = Environment.GetEnvironmentVariable ("PATH"); + var pathDirs = path.Split (new char[] { Path.PathSeparator }, StringSplitOptions.RemoveEmptyEntries); + + foreach (var dir in pathDirs) { + foreach (var directory in FindExecutableInDirectory(executable, dir)) { + yield return directory; + } + } + } + + protected IEnumerable FindExecutableInDirectory(string executable, string dir) + { + foreach (var exe in Executables (executable)) + if (File.Exists (Path.Combine (dir, exe))) + yield return dir; + } + + IEnumerable Executables (string executable) + { + yield return executable; + var pathExt = Environment.GetEnvironmentVariable ("PATHEXT"); + var pathExts = pathExt?.Split (new char [] { Path.PathSeparator }, StringSplitOptions.RemoveEmptyEntries); + + if (pathExts == null) + yield break; + + foreach (var ext in pathExts) + yield return Path.ChangeExtension (executable, ext); + } + + protected string NullIfEmpty (string s) + { + if (s == null || s.Length != 0) + return s; + + return null; + } + + string GetExecutablePath (string dir, string exe) + { + if (string.IsNullOrEmpty (dir)) + return exe; + foreach (var e in Executables (exe)) + if (File.Exists (Path.Combine (dir, e))) + return e; + return exe; + } + } +} + diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools/Sdks/AndroidSdkUnix.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools/Sdks/AndroidSdkUnix.cs new file mode 100644 index 00000000000..8331c3d168a --- /dev/null +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools/Sdks/AndroidSdkUnix.cs @@ -0,0 +1,233 @@ +using System; +using System.Xml; +using System.Xml.Linq; +using System.Collections.Generic; +using System.IO; + +namespace Xamarin.Android.Tools +{ + class AndroidSdkUnix : AndroidSdkBase + { + public override string NdkHostPlatform32Bit { + get { return OS.IsMac ? "darwin-x86" : "linux-x86"; } + } + public override string NdkHostPlatform64Bit { + get { return OS.IsMac ? "darwin-x86_64" : "linux-x86_64"; } + } + + public override string PreferedAndroidSdkPath { + get { + var config_file = GetUnixConfigFile (); + var androidEl = config_file.Root.Element ("android-sdk"); + + if (androidEl != null) { + var path = (string)androidEl.Attribute ("path"); + + if (ValidateAndroidSdkLocation (path)) + return path; + } + return null; + } + } + + public override string PreferedAndroidNdkPath { + get { + var config_file = GetUnixConfigFile (); + var androidEl = config_file.Root.Element ("android-ndk"); + + if (androidEl != null) { + var path = (string)androidEl.Attribute ("path"); + + if (ValidateAndroidNdkLocation (path)) + return path; + } + return null; + } + } + + public override string PreferedJavaSdkPath { + get { + var config_file = GetUnixConfigFile (); + var javaEl = config_file.Root.Element ("java-sdk"); + + if (javaEl != null) { + var path = (string)javaEl.Attribute ("path"); + + if (ValidateJavaSdkLocation (path)) + return path; + } + return null; + } + } + + protected override IEnumerable GetAllAvailableAndroidSdks () + { + var preferedSdkPath = PreferedAndroidSdkPath; + if (!string.IsNullOrEmpty (preferedSdkPath)) + yield return preferedSdkPath; + + // Look in PATH + foreach (var path in FindExecutableInPath (Adb)) { + // Strip off "platform-tools" + var dir = Path.GetDirectoryName (path); + + if (ValidateAndroidSdkLocation (dir)) + yield return dir; + } + } + + protected override string GetJavaSdkPath () + { + var preferedJavaSdkPath = PreferedJavaSdkPath; + if (!string.IsNullOrEmpty (preferedJavaSdkPath)) + return preferedJavaSdkPath; + + // Look in PATH + foreach (var path in FindExecutableInPath (JarSigner)) { + // Strip off "bin" + var dir = Path.GetDirectoryName (path); + + if (ValidateJavaSdkLocation (dir)) + return dir; + } + + return null; + } + + public override bool ValidateJavaSdkLocation (string loc) + { + var result = base.ValidateJavaSdkLocation (loc); + + if (result) { + // handle apple's java stub + const string javaHomeExe = "/usr/libexec/java_home"; + + if (File.Exists (javaHomeExe)) { + // returns true if there is a java installed + var javaHomeTask = ProcessUtils.ExecuteToolAsync (javaHomeExe, + (output) => { + if (output.Contains ("(null)")) { + return false; + } + + return true; + }, System.Threading.CancellationToken.None + ); + + if (!javaHomeTask.Result) { + return false; + } + } + } + + return result; + } + + protected override IEnumerable GetAllAvailableAndroidNdks () + { + var preferedNdkPath = PreferedAndroidNdkPath; + if (!string.IsNullOrEmpty (preferedNdkPath)) + yield return preferedNdkPath; + + // Look in PATH + foreach (var path in FindExecutableInPath (NdkStack)) { + if (ValidateAndroidNdkLocation (path)) + yield return path; + } + } + + protected override string GetShortFormPath (string path) + { + // This is a Windows-ism, don't do anything for Unix + return path; + } + + public override void SetPreferredAndroidSdkPath (string path) + { + path = NullIfEmpty (path); + + var doc = GetUnixConfigFile (); + var androidEl = doc.Root.Element ("android-sdk"); + + if (androidEl == null) { + androidEl = new XElement ("android-sdk"); + doc.Root.Add (androidEl); + } + + androidEl.SetAttributeValue ("path", path); + doc.Save (UnixConfigPath); + } + + public override void SetPreferredJavaSdkPath (string path) + { + path = NullIfEmpty (path); + + var doc = GetUnixConfigFile (); + var javaEl = doc.Root.Element ("java-sdk"); + + if (javaEl == null) { + javaEl = new XElement ("java-sdk"); + doc.Root.Add (javaEl); + } + + javaEl.SetAttributeValue ("path", path); + doc.Save (UnixConfigPath); + } + + public override void SetPreferredAndroidNdkPath (string path) + { + path = NullIfEmpty (path); + + var doc = GetUnixConfigFile (); + var androidEl = doc.Root.Element ("android-ndk"); + + if (androidEl == null) { + androidEl = new XElement ("android-ndk"); + doc.Root.Add (androidEl); + } + + androidEl.SetAttributeValue ("path", path); + doc.Save (UnixConfigPath); + } + + private static string UnixConfigPath { + get { + var p = Environment.GetFolderPath (Environment.SpecialFolder.ApplicationData); + return Path.Combine (Path.Combine (p, "xbuild"), "monodroid-config.xml"); + } + } + + private static XDocument GetUnixConfigFile () + { + var file = UnixConfigPath; + XDocument doc = null; + if (!File.Exists (file)) { + string dir = Path.GetDirectoryName (file); + if (!Directory.Exists (dir)) + Directory.CreateDirectory (dir); + } else { + try { + doc = XDocument.Load (file); + } catch (Exception ex) { + AndroidLogger.LogError ("Could not load monodroid configuration file", ex); + + // move out of the way and create a new one + doc = new XDocument (new XElement ("monodroid")); + var newFileName = file + ".old"; + if (File.Exists (newFileName)) { + File.Delete (newFileName); + } + + File.Move (file, newFileName); + } + } + + if (doc == null || doc.Root == null) { + doc = new XDocument (new XElement ("monodroid")); + } + return doc; + } + + } +} + diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools/Sdks/AndroidSdkWindows.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools/Sdks/AndroidSdkWindows.cs new file mode 100644 index 00000000000..6e606605302 --- /dev/null +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools/Sdks/AndroidSdkWindows.cs @@ -0,0 +1,202 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +namespace Xamarin.Android.Tools +{ + class AndroidSdkWindows : AndroidSdkBase + { + const string MDREG_KEY = @"SOFTWARE\Novell\Mono for Android"; + const string MDREG_ANDROID_SDK = "AndroidSdkDirectory"; + const string MDREG_ANDROID_NDK = "AndroidNdkDirectory"; + const string MDREG_JAVA_SDK = "JavaSdkDirectory"; + const string ANDROID_INSTALLER_PATH = @"SOFTWARE\Android SDK Tools"; + const string ANDROID_INSTALLER_KEY = "Path"; + const string XAMARIN_ANDROID_INSTALLER_PATH = @"SOFTWARE\Xamarin\MonoAndroid"; + const string XAMARIN_ANDROID_INSTALLER_KEY = "PrivateAndroidSdkPath"; + + public override string ZipAlign { get; protected set; } = "zipalign.exe"; + public override string JarSigner { get; protected set; } = "jarsigner.exe"; + public override string KeyTool { get; protected set; } = "keytool.exe"; + + public override string NdkHostPlatform32Bit { get { return "windows"; } } + public override string NdkHostPlatform64Bit { get { return "windows-x86_64"; } } + public override string Javac { get; protected set; } = "javac.exe"; + + public override string PreferedAndroidSdkPath { + get { + var wow = RegistryEx.Wow64.Key32; + if (CheckRegistryKeyForExecutable (RegistryEx.CurrentUser, MDREG_KEY, MDREG_ANDROID_SDK, wow, "platform-tools", Adb)) + return RegistryEx.GetValueString (RegistryEx.CurrentUser, MDREG_KEY, MDREG_ANDROID_SDK, wow); + return null; + } + } + public override string PreferedAndroidNdkPath { + get { + var wow = RegistryEx.Wow64.Key32; + if (CheckRegistryKeyForExecutable (RegistryEx.CurrentUser, MDREG_KEY, MDREG_ANDROID_NDK, wow, ".", NdkStack)) + return RegistryEx.GetValueString (RegistryEx.CurrentUser, MDREG_KEY, MDREG_ANDROID_NDK, wow); + return null; + } + } + public override string PreferedJavaSdkPath { + get { + var wow = RegistryEx.Wow64.Key32; + if (CheckRegistryKeyForExecutable (RegistryEx.CurrentUser, MDREG_KEY, MDREG_JAVA_SDK, wow, "bin", JarSigner)) + return RegistryEx.GetValueString (RegistryEx.CurrentUser, MDREG_KEY, MDREG_JAVA_SDK, wow); + return null; + } + } + + protected override IEnumerable GetAllAvailableAndroidSdks () + { + var roots = new[] { RegistryEx.CurrentUser, RegistryEx.LocalMachine }; + var wow = RegistryEx.Wow64.Key32; + + AndroidLogger.LogInfo ("sdk", "Looking for Android SDK.."); + + // Check for the key the user gave us in the VS/addin options + foreach (var root in roots) + if (CheckRegistryKeyForExecutable (root, MDREG_KEY, MDREG_ANDROID_SDK, wow, "platform-tools", Adb)) + yield return RegistryEx.GetValueString (root, MDREG_KEY, MDREG_ANDROID_SDK, wow); + + // Check for the key written by the Xamarin installer + if (CheckRegistryKeyForExecutable (RegistryEx.CurrentUser, XAMARIN_ANDROID_INSTALLER_PATH, XAMARIN_ANDROID_INSTALLER_KEY, wow, "platform-tools", Adb)) + yield return RegistryEx.GetValueString (RegistryEx.CurrentUser, XAMARIN_ANDROID_INSTALLER_PATH, XAMARIN_ANDROID_INSTALLER_KEY, wow); + + // Check for the key written by the Android SDK installer + foreach (var root in roots) + if (CheckRegistryKeyForExecutable (root, ANDROID_INSTALLER_PATH, ANDROID_INSTALLER_KEY, wow, "platform-tools", Adb)) + yield return RegistryEx.GetValueString (root, ANDROID_INSTALLER_PATH, ANDROID_INSTALLER_KEY, wow); + + // Check some hardcoded paths for good measure + var xamarin_private = Path.Combine (Environment.GetFolderPath (Environment.SpecialFolder.LocalApplicationData), "Xamarin", "MonoAndroid", "android-sdk-windows"); + var android_default = Path.Combine (OS.ProgramFilesX86, "Android", "android-sdk-windows"); + var cdrive_default = @"C:\android-sdk-windows"; + + if (ValidateAndroidSdkLocation (xamarin_private)) + yield return xamarin_private; + + if (ValidateAndroidSdkLocation (android_default)) + yield return android_default; + + if (ValidateAndroidSdkLocation (cdrive_default)) + yield return cdrive_default; + } + + protected override string GetJavaSdkPath () + { + // check the user specified path + var roots = new[] { RegistryEx.CurrentUser, RegistryEx.LocalMachine }; + const RegistryEx.Wow64 wow = RegistryEx.Wow64.Key32; + + foreach (var root in roots) { + if (CheckRegistryKeyForExecutable (root, MDREG_KEY, MDREG_JAVA_SDK, wow, "bin", JarSigner)) + return RegistryEx.GetValueString (root, MDREG_KEY, MDREG_JAVA_SDK, wow); + } + + string subkey = @"SOFTWARE\JavaSoft\Java Development Kit"; + + AndroidLogger.LogInfo ("sdk", "Looking for Java 6 SDK.."); + + foreach (var wow64 in new[] { RegistryEx.Wow64.Key32, RegistryEx.Wow64.Key64 }) { + string key_name = string.Format (@"{0}\{1}\{2}", "HKLM", subkey, "CurrentVersion"); + var currentVersion = RegistryEx.GetValueString (RegistryEx.LocalMachine, subkey, "CurrentVersion", wow64); + + if (!string.IsNullOrEmpty (currentVersion)) { + AndroidLogger.LogInfo ("sdk", " Key {0} found.", key_name); + + // No matter what the CurrentVersion is, look for 1.6 or 1.7 or 1.8 + if (CheckRegistryKeyForExecutable (RegistryEx.LocalMachine, subkey + "\\" + "1.6", "JavaHome", wow64, "bin", JarSigner)) + return RegistryEx.GetValueString (RegistryEx.LocalMachine, subkey + "\\" + "1.6", "JavaHome", wow64); + + if (CheckRegistryKeyForExecutable (RegistryEx.LocalMachine, subkey + "\\" + "1.7", "JavaHome", wow64, "bin", JarSigner)) + return RegistryEx.GetValueString (RegistryEx.LocalMachine, subkey + "\\" + "1.7", "JavaHome", wow64); + + if (CheckRegistryKeyForExecutable (RegistryEx.LocalMachine, subkey + "\\" + "1.8", "JavaHome", wow64, "bin", JarSigner)) + return RegistryEx.GetValueString (RegistryEx.LocalMachine, subkey + "\\" + "1.8", "JavaHome", wow64); + } + + AndroidLogger.LogInfo ("sdk", " Key {0} not found.", key_name); + } + + // We ran out of things to check.. + return null; + } + + protected override IEnumerable GetAllAvailableAndroidNdks () + { + var roots = new[] { RegistryEx.CurrentUser, RegistryEx.LocalMachine }; + var wow = RegistryEx.Wow64.Key32; + + AndroidLogger.LogInfo ("sdk", "Looking for Android NDK.."); + + // Check for the key the user gave us in the VS/addin options + foreach (var root in roots) + if (CheckRegistryKeyForExecutable (root, MDREG_KEY, MDREG_ANDROID_NDK, wow, ".", NdkStack)) + yield return RegistryEx.GetValueString (root, MDREG_KEY, MDREG_ANDROID_NDK, wow); + + /* + // Check for the key written by the Xamarin installer + if (CheckRegistryKeyForExecutable (RegistryEx.CurrentUser, XAMARIN_ANDROID_INSTALLER_PATH, XAMARIN_ANDROID_INSTALLER_KEY, wow, "platform-tools", Adb)) + yield return RegistryEx.GetValueString (RegistryEx.CurrentUser, XAMARIN_ANDROID_INSTALLER_PATH, XAMARIN_ANDROID_INSTALLER_KEY, wow); + */ + + // Check some hardcoded paths for good measure + var xamarin_private = Path.Combine (Environment.GetFolderPath (Environment.SpecialFolder.LocalApplicationData), "Xamarin", "MonoAndroid"); + var android_default = Path.Combine (OS.ProgramFilesX86, "Android"); + var cdrive_default = @"C:\"; + + foreach (var basePath in new string [] {xamarin_private, android_default, cdrive_default}) + if (Directory.Exists (basePath)) + foreach (var dir in Directory.GetDirectories (basePath, "android-ndk-r*")) + if (ValidateAndroidNdkLocation (dir)) + yield return dir; + } + + protected override string GetShortFormPath (string path) + { + return KernelEx.GetShortPathName (path); + } + + public override void SetPreferredAndroidSdkPath (string path) + { + RegistryEx.SetValueString (RegistryEx.CurrentUser, MDREG_KEY, MDREG_ANDROID_SDK, path ?? "", RegistryEx.Wow64.Key32); + } + + public override void SetPreferredJavaSdkPath (string path) + { + RegistryEx.SetValueString (RegistryEx.CurrentUser, MDREG_KEY, MDREG_JAVA_SDK, path ?? "", RegistryEx.Wow64.Key32); + } + + public override void SetPreferredAndroidNdkPath (string path) + { + RegistryEx.SetValueString (RegistryEx.CurrentUser, MDREG_KEY, MDREG_ANDROID_NDK, path ?? "", RegistryEx.Wow64.Key32); + } + + #region Helper Methods + private bool CheckRegistryKeyForExecutable (UIntPtr key, string subkey, string valueName, RegistryEx.Wow64 wow64, string subdir, string exe) + { + string key_name = string.Format (@"{0}\{1}\{2}", key == RegistryEx.CurrentUser ? "HKCU" : "HKLM", subkey, valueName); + + var path = NullIfEmpty (RegistryEx.GetValueString (key, subkey, valueName, wow64)); + + if (path == null) { + AndroidLogger.LogInfo ("sdk", " Key {0} not found.", key_name); + return false; + } + + if (!FindExecutableInDirectory (exe, Path.Combine (path, subdir)).Any ()) { + AndroidLogger.LogInfo ("sdk", " Key {0} found:\n Path does not contain {1} in \\{2} ({3}).", key_name, exe, subdir, path); + return false; + } + + AndroidLogger.LogInfo ("sdk", " Key {0} found:\n Path contains {1} in \\{2} ({3}).", key_name, exe, subdir, path); + + return true; + } + #endregion + } +} + diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools/Sdks/MonoDroidSdkBase.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools/Sdks/MonoDroidSdkBase.cs new file mode 100644 index 00000000000..ac04856be3e --- /dev/null +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools/Sdks/MonoDroidSdkBase.cs @@ -0,0 +1,292 @@ +using System; +using System.Linq; +using System.IO; +using System.Collections.Generic; +using System.Xml; + +namespace Xamarin.Android.Tools +{ + abstract class MonoDroidSdkBase + { + protected readonly static string DebugRuntime = "Mono.Android.DebugRuntime-debug.apk"; + protected readonly static string ClassParseExe = "class-parse.exe"; + protected readonly static string GeneratorScript = "generator"; + + // I can never remember the difference between SdkPath and anything else... + [Obsolete ("Do not use.")] + public string SdkPath { get; private set; } + + // Contains mandroid + public string BinPath { get; private set; } + + // Not actually shipped... + public string IncludePath { get; private set; } + + // Contains Mono.Android.DebugRuntime-*.apk, platforms/*/*.apk. + public string RuntimePath { get; private set; } + + // Root directory for XA libraries, contains designer dependencies + public string LibrariesPath { get; private set; } + + // Contains mscorlib.dll + public string BclPath { get; private set; } + + public int SharedRuntimeVersion { get; private set; } + + // expectedRuntimePath: contains Mono.Android.DebugRuntime-*.apk + // binPath: contains mandroid + // mscorlibDir: contains mscorlib.dll + public void Initialize (string expectedRuntimePath = null, string binPath = null, string bclPath = null) + { + var runtimePath = GetValidPath ("MonoAndroidToolsPath", expectedRuntimePath, ValidateRuntime, () => FindRuntime ()); + if (runtimePath != null) { + binPath = GetValidPath ("MonoAndroidBinPath", binPath, ValidateBin, () => FindBin (runtimePath)); + bclPath = GetValidPath ("mscorlib.dll", bclPath, ValidateFramework, () => FindFramework (runtimePath)); + } else { + if (expectedRuntimePath != null) + AndroidLogger.LogWarning (null, "Runtime was not found at {0}", expectedRuntimePath); + binPath = bclPath = null; + } + + if (runtimePath == null || binPath == null || bclPath == null) { + Reset (); + return; + } + + RuntimePath = runtimePath; + #pragma warning disable 0618 + SdkPath = Path.GetFullPath (Path.Combine (runtimePath, "..", "..")); + #pragma warning restore 0618 + BinPath = binPath; + BclPath = bclPath; + LibrariesPath = FindLibraries (runtimePath); + + IncludePath = FindInclude (runtimePath); + if (IncludePath != null && !Directory.Exists (IncludePath)) + IncludePath = null; + + SharedRuntimeVersion = GetCurrentSharedRuntimeVersion (); + FindSupportedFrameworks (); + } + + static string GetValidPath (string description, string path, Func validator, Func defaultPath) + { + if (!string.IsNullOrEmpty (path)) { + if (Directory.Exists (path)) { + if (validator (path)) + return path; + AndroidLogger.LogWarning (null, "{0} path '{1}' is explicitly specified, but it was not valid; skipping.", description, path); + } else + AndroidLogger.LogWarning (null, "{0} path '{1}' is explicitly specified, but it was not found; skipping.", description, path); + } + path = defaultPath (); + if (path != null && validator (path)) + return path; + if (path != null) + AndroidLogger.LogWarning (null, "{0} path is defaulted to '{1}', but it was not valid; skipping", description, path); + else + AndroidLogger.LogWarning (null, "{0} path is not found and no default location is provided; skipping", description); + return null; + } + + public void Reset () + { + #pragma warning disable 0618 + SdkPath = BinPath = IncludePath = RuntimePath = BclPath = null; + #pragma warning restore 0618 + SharedRuntimeVersion = 0; + } + + protected abstract string FindRuntime (); + protected abstract string FindFramework (string runtimePath); + + // Check for platform-specific `mandroid` name + protected abstract bool ValidateBin (string binPath); + + protected static bool ValidateRuntime (string loc) + { + return !string.IsNullOrWhiteSpace (loc) && + (File.Exists (Path.Combine (loc, DebugRuntime)) || // Normal/expected + File.Exists (Path.Combine (loc, ClassParseExe)) || // Normal/expected + File.Exists (Path.Combine (loc, "Ionic.Zip.dll"))); // Wrench builds + } + + protected static bool ValidateFramework (string loc) + { + return loc != null && File.Exists (Path.Combine (loc, "mscorlib.dll")); + } + + public string FindVersionFile () + { + #pragma warning disable 0618 + if (string.IsNullOrEmpty (SdkPath)) + return null; + #pragma warning restore 0618 + foreach (var loc in GetVersionFileLocations ()) { + if (File.Exists (loc)) { + return loc; + } + } + return null; + } + + protected virtual IEnumerable GetVersionFileLocations () + { + #pragma warning disable 0618 + yield return Path.Combine (SdkPath, "Version"); + #pragma warning restore 0618 + } + + protected abstract string FindBin (string runtimePath); + + protected abstract string FindInclude (string runtimePath); + + protected abstract string FindLibraries (string runtimePath); + + [Obsolete ("Do not use.")] + public string GetPlatformNativeLibPath (string abi) + { + return FindPlatformNativeLibPath (SdkPath, abi); + } + + [Obsolete ("Do not use.")] + public string GetPlatformNativeLibPath (AndroidTargetArch arch) + { + return FindPlatformNativeLibPath (SdkPath, GetMonoDroidArchName (arch)); + } + + [Obsolete ("Do not use.")] + static string GetMonoDroidArchName (AndroidTargetArch arch) + { + switch (arch) { + case AndroidTargetArch.Arm: + return "armeabi"; + case AndroidTargetArch.Mips: + return "mips"; + case AndroidTargetArch.X86: + return "x86"; + } + return null; + } + + [Obsolete] + protected string FindPlatformNativeLibPath (string sdk, string arch) + { + return Path.Combine (sdk, "lib", arch); + } + + static XmlReaderSettings GetSafeReaderSettings () + { + //allow DTD but not try to resolve it from web + return new XmlReaderSettings { + CloseInput = true, + DtdProcessing = DtdProcessing.Ignore, + XmlResolver = null, + }; + } + + int GetCurrentSharedRuntimeVersion () + { + string file = Path.Combine (RuntimePath, "Mono.Android.DebugRuntime-debug.xml"); + + return GetManifestVersion (file); + } + + internal static int GetManifestVersion (string file) + { + // It seems that MfA 1.0 on Windows didn't include the xml files to get the runtime version. + if (!File.Exists (file)) + return int.MaxValue; + + try { + using (var r = XmlReader.Create (file, GetSafeReaderSettings())) { + if (r.MoveToContent () == XmlNodeType.Element && r.MoveToAttribute ("android:versionCode")) { + int value; + if (int.TryParse (r.Value, out value)) + return value; + AndroidLogger.LogInfo ("Cannot parse runtime version code: ({0})", r.Value); + } + } + } catch (Exception ex) { + AndroidLogger.LogError ("Error trying to find shared runtime version", ex); + } + return int.MaxValue; + } + + internal static Version ToVersion (string frameworkDir) + { + string version = Path.GetFileName (frameworkDir); + if (!version.StartsWith ("v", StringComparison.OrdinalIgnoreCase)) { + // wat? + return new Version (); + } + version = version.Substring (1); + Version v; + if (Version.TryParse (version, out v)) + return v; + return new Version (); + } + + void FindSupportedFrameworks () + { + string bclDir = MonoDroidSdk.FrameworkPath; + string frameworksDir = Path.GetDirectoryName (bclDir); + foreach (var framework in Directory.EnumerateDirectories (frameworksDir).Select (ToVersion)) { + if (framework.Major == 0) + continue; + string apiLevel; + if (FrameworkToApiLevels.TryGetValue (framework, out apiLevel)) + SupportedFrameworks.Add (framework, apiLevel); + } + } + + readonly Dictionary SupportedFrameworks = new Dictionary (); + + static readonly Dictionary FrameworkToApiLevels = new Dictionary (AndroidVersion.KnownVersions.ToDictionary (k => k.Version, v => v.ApiLevel.ToString ())); + static readonly Dictionary LegacyFrameworkToApiLevels = new Dictionary { + { new Version (4, 5), "21" } // L Preview + }; + + public IEnumerable GetSupportedApiLevels () + { + return SupportedFrameworks.Select (e => e.Value); + } + + public string GetApiLevelForFrameworkVersion (string framework) + { + Version v; + if (!Version.TryParse (framework.TrimStart ('v'), out v)) + return null; + string apiLevel; + if (SupportedFrameworks.TryGetValue (v, out apiLevel) + || FrameworkToApiLevels.TryGetValue (v, out apiLevel) + || LegacyFrameworkToApiLevels.TryGetValue (v, out apiLevel)) + return apiLevel; + return null; + } + + public string GetFrameworkVersionForApiLevel (string apiLevel) + { + // API level 9 was discontinued immediately for 10, in the rare case we get it just upgrade the number + if (apiLevel == "9") + apiLevel = "10"; + var maxFrameworkVersion = SupportedFrameworks.Concat (FrameworkToApiLevels) + .Where (e => e.Value == apiLevel) + .OrderByDescending (e => e.Key, Comparer.Default) + .Select (e => e.Key) + .FirstOrDefault (); + if (maxFrameworkVersion != null) + return "v" + maxFrameworkVersion; + return null; + } + + /// + /// Determines if the given apiLevel is supported by an installed Framework + /// + public bool IsSupportedFrameworkLevel (string apiLevel) + { + return SupportedFrameworks.Any ((sf => sf.Value == apiLevel)); + } + } +} + diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools/Sdks/MonoDroidSdkUnix.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools/Sdks/MonoDroidSdkUnix.cs new file mode 100644 index 00000000000..9476adf3f32 --- /dev/null +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools/Sdks/MonoDroidSdkUnix.cs @@ -0,0 +1,102 @@ +using System; +using System.IO; +using System.Linq; +using System.Collections.Generic; + +namespace Xamarin.Android.Tools +{ + class MonoDroidSdkUnix : MonoDroidSdkBase + { + readonly static string[] RuntimeToFrameworkPaths = new[]{ + Path.Combine ("..", "..", "..", ".xamarin.android", "lib", "xbuild-frameworks", "MonoAndroid"), + Path.Combine ("..", "xbuild-frameworks", "MonoAndroid"), + Path.Combine ("..", "mono", "2.1"), + }; + + readonly static string[] SearchPaths = { + "/Library/Frameworks/Xamarin.Android.framework/Versions/Current/lib/mandroid", + "/Developer/MonoAndroid/usr/lib/mandroid", + "/opt/mono-android/lib/mandroid" + }; + + protected override string FindRuntime () + { + string monoAndroidPath = Environment.GetEnvironmentVariable ("MONO_ANDROID_PATH"); + if (!string.IsNullOrEmpty (monoAndroidPath)) { + string libMandroid = Path.Combine (monoAndroidPath, "lib", "mandroid"); + if (Directory.Exists (libMandroid)) { + if (ValidateRuntime (libMandroid)) + return libMandroid; + AndroidLogger.LogInfo (null, "MONO_ANDROID_PATH points to {0}, but it is invalid.", monoAndroidPath); + } else + AndroidLogger.LogInfo (null, "MONO_ANDROID_PATH points to {0}, but it does not exist.", monoAndroidPath); + } + + // check also in the users folder + var personal = Environment.GetFolderPath (Environment.SpecialFolder.Personal); + var additionalSearchPaths = new [] { + // for Mono.Posix and Mono.Data.Sqlite builds in xamarin-android. + monoAndroidPath = Path.GetFullPath (Path.Combine (new Uri (GetType ().Assembly.CodeBase).LocalPath, "..", "..", "..", "..", "..", "lib", "mandroid")), + Path.Combine (personal, @".xamarin.android/lib/mandroid") + }; + + return additionalSearchPaths.Concat (SearchPaths).FirstOrDefault (ValidateRuntime); + } + + protected override bool ValidateBin (string binPath) + { + return !string.IsNullOrWhiteSpace (binPath) && + File.Exists (Path.Combine (binPath, GeneratorScript)); + } + + protected override string FindFramework (string runtimePath) + { + foreach (var relativePath in RuntimeToFrameworkPaths) { + var fullPath = Path.GetFullPath (Path.Combine (runtimePath, relativePath)); + if (Directory.Exists (fullPath)) { + if (ValidateFramework (fullPath)) + return fullPath; + + // check to see if full path is the folder that contains each framework version, eg contains folders of the form v1.0, v2.3 etc + var subdirs = Directory.GetDirectories (fullPath, "v*").OrderBy (x => x).ToArray (); + foreach (var subdir in subdirs) { + if (ValidateFramework (subdir)) + return subdir; + } + } + } + + return null; + } + + protected override string FindBin (string runtimePath) + { + string binPath = Path.GetFullPath (Path.Combine (runtimePath, "..", "..", "bin")); + if (File.Exists (Path.Combine (binPath, GeneratorScript))) + return binPath; + return null; + } + + protected override string FindInclude (string runtimePath) + { + string includeDir = Path.GetFullPath (Path.Combine (runtimePath, "..", "..", "include")); + if (Directory.Exists (includeDir)) + return includeDir; + return null; + } + + protected override string FindLibraries (string runtimePath) + { + return Path.GetFullPath (Path.Combine (runtimePath, "..")); + } + + protected override IEnumerable GetVersionFileLocations () + { + yield return Path.GetFullPath (Path.Combine (RuntimePath, "..", "..", "Version")); + string sdkPath = Path.GetDirectoryName (Path.GetDirectoryName (RuntimePath)); + if (Path.GetFileName (sdkPath) == "usr") + yield return Path.GetFullPath (Path.Combine (Path.GetDirectoryName (sdkPath), "Version")); + } + } +} + diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools/Sdks/MonoDroidSdkWindows.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools/Sdks/MonoDroidSdkWindows.cs new file mode 100644 index 00000000000..711460e79a1 --- /dev/null +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools/Sdks/MonoDroidSdkWindows.cs @@ -0,0 +1,76 @@ +using System; +using System.IO; +using System.Collections.Generic; +using System.Linq; + +namespace Xamarin.Android.Tools +{ + class MonoDroidSdkWindows : MonoDroidSdkBase + { + protected override string FindRuntime () + { + string monoAndroidPath = Environment.GetEnvironmentVariable ("MONO_ANDROID_PATH"); + if (!string.IsNullOrEmpty (monoAndroidPath)) { + string libMandroid = Path.Combine (monoAndroidPath, "lib", "mandroid"); + if (Directory.Exists (libMandroid) && ValidateRuntime (libMandroid)) + return libMandroid; + } + string xamarinSdk = Path.Combine (OS.ProgramFilesX86, "MSBuild", "Xamarin", "Android"); + return Directory.Exists (xamarinSdk) + ? xamarinSdk + : OS.ProgramFilesX86 + @"\MSBuild\Novell"; + } + + static readonly string[] RuntimeToFrameworkPaths = new []{ + Path.Combine ("..", "..", "..", "Reference Assemblies", "Microsoft", "Framework", "MonoAndroid"), + Path.Combine (OS.ProgramFilesX86, "Reference Assemblies", "Microsoft", "Framework", "MonoAndroid"), + }; + + protected override string FindFramework (string runtimePath) + { + foreach (var relativePath in RuntimeToFrameworkPaths) { + var fullPath = Path.GetFullPath (Path.Combine (runtimePath, relativePath)); + if (Directory.Exists (fullPath)) { + if (ValidateFramework (fullPath)) + return fullPath; + + // check to see if full path is the folder that contains each framework version, eg contains folders of the form v1.0, v2.3 etc + var subdirs = Directory.GetDirectories (fullPath, "v*").OrderBy (x => x).ToArray (); + foreach (var subdir in subdirs) { + if (ValidateFramework (subdir)) + return subdir; + } + } + } + + return null; + } + + protected override string FindBin (string runtimePath) + { + return runtimePath; + } + + protected override bool ValidateBin (string binPath) + { + return !string.IsNullOrWhiteSpace (binPath) && + File.Exists (Path.Combine (binPath, "generator.exe")); + } + + protected override string FindInclude (string runtimePath) + { + return Path.GetFullPath (Path.Combine (runtimePath, "include")); + } + + protected override string FindLibraries (string runtimePath) + { + return Path.GetFullPath (runtimePath); + } + + protected override IEnumerable GetVersionFileLocations () + { + yield return Path.GetFullPath (Path.Combine (RuntimePath, "Version")); + } + } +} + diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools/Xamarin.Android.Tools.csproj b/external/xamarin-android-tools/src/Xamarin.Android.Tools/Xamarin.Android.Tools.csproj new file mode 100644 index 00000000000..a3d89deec13 --- /dev/null +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools/Xamarin.Android.Tools.csproj @@ -0,0 +1,55 @@ + + + + Debug + AnyCPU + {91713046-C358-4647-B162-ED4E1442F3D8} + Library + Xamarin.Android.Tools + Xamarin.Android.Tools + v4.5 + + + true + full + false + ..\..\bin\Debug + DEBUG; + prompt + 4 + false + + + full + true + ..\..\bin\Release + prompt + 4 + false + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools/Xamarin.Android.Tools.userprefs b/external/xamarin-android-tools/src/Xamarin.Android.Tools/Xamarin.Android.Tools.userprefs new file mode 100644 index 00000000000..37a28df6a1a --- /dev/null +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools/Xamarin.Android.Tools.userprefs @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From 27c9642fd8325c865d15a0da91e26697062f31b9 Mon Sep 17 00:00:00 2001 From: Dean Ellis Date: Thu, 29 Sep 2016 10:02:27 +0100 Subject: [PATCH 003/308] Fixed the Guid of the csproj as it was the same as another in xamarin-android --- .../src/Xamarin.Android.Tools/Xamarin.Android.Tools.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools/Xamarin.Android.Tools.csproj b/external/xamarin-android-tools/src/Xamarin.Android.Tools/Xamarin.Android.Tools.csproj index a3d89deec13..9de64c33968 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools/Xamarin.Android.Tools.csproj +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools/Xamarin.Android.Tools.csproj @@ -3,7 +3,7 @@ Debug AnyCPU - {91713046-C358-4647-B162-ED4E1442F3D8} + {e34bcfa0-caa4-412c-aa1c-75db8d67d157} Library Xamarin.Android.Tools Xamarin.Android.Tools From dc3625ba73b99315006c6a0055627e9b53c26623 Mon Sep 17 00:00:00 2001 From: Dean Ellis Date: Tue, 31 Jan 2017 12:43:05 +0000 Subject: [PATCH 004/308] Allow the use of MONO_ANDROID_PATH on windows. We need to be able to support custom paths on wwindows as well as Mac/Linux. However the directory structure is slightly different. Windows does not have the tools in lib/mandroid but in the root of the path. This change allows us to use MONO_ANDROID_PATH on windows by validating the Root of the path provided as well. --- .../src/Xamarin.Android.Tools/Sdks/MonoDroidSdkWindows.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools/Sdks/MonoDroidSdkWindows.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools/Sdks/MonoDroidSdkWindows.cs index 711460e79a1..2d7c4784d3b 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools/Sdks/MonoDroidSdkWindows.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools/Sdks/MonoDroidSdkWindows.cs @@ -14,6 +14,8 @@ protected override string FindRuntime () string libMandroid = Path.Combine (monoAndroidPath, "lib", "mandroid"); if (Directory.Exists (libMandroid) && ValidateRuntime (libMandroid)) return libMandroid; + if (Directory.Exists (monoAndroidPath) && ValidateRuntime (monoAndroidPath)) + return monoAndroidPath; } string xamarinSdk = Path.Combine (OS.ProgramFilesX86, "MSBuild", "Xamarin", "Android"); return Directory.Exists (xamarinSdk) From 8e1ba872553d09c365bda7ca472da121966657bb Mon Sep 17 00:00:00 2001 From: Dean Ellis Date: Fri, 10 Feb 2017 13:54:39 +0000 Subject: [PATCH 005/308] Updated with changes from upstream --- .../src/Xamarin.Android.Tools/AndroidSdk.cs | 3 +-- .../src/Xamarin.Android.Tools/MonoDroidSdk.cs | 2 +- .../src/Xamarin.Android.Tools/Sdks/AndroidSdkBase.cs | 10 +++++----- .../src/Xamarin.Android.Tools/Sdks/AndroidSdkUnix.cs | 1 - .../Xamarin.Android.Tools/Sdks/AndroidSdkWindows.cs | 1 - .../src/Xamarin.Android.Tools/Sdks/MonoDroidSdkBase.cs | 2 +- .../src/Xamarin.Android.Tools/Sdks/MonoDroidSdkUnix.cs | 3 ++- .../Xamarin.Android.Tools/Sdks/MonoDroidSdkWindows.cs | 6 ++---- 8 files changed, 12 insertions(+), 16 deletions(-) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools/AndroidSdk.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools/AndroidSdk.cs index 7918988bd62..682295d957b 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools/AndroidSdk.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools/AndroidSdk.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Linq; using System.Collections.Generic; using System.IO; @@ -145,4 +145,3 @@ public static string AndroidNdkHostPlatform { } } } - diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools/MonoDroidSdk.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools/MonoDroidSdk.cs index b5edd619c41..e7f62ccdf07 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools/MonoDroidSdk.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools/MonoDroidSdk.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.IO; namespace Xamarin.Android.Tools diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools/Sdks/AndroidSdkBase.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools/Sdks/AndroidSdkBase.cs index 7a91096b23c..51e0ec0e33f 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools/Sdks/AndroidSdkBase.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools/Sdks/AndroidSdkBase.cs @@ -126,7 +126,7 @@ public virtual bool ValidateJavaSdkLocation (string loc) /// public bool ValidateAndroidNdkLocation (string loc) { - return !string.IsNullOrEmpty (loc) && FindExecutableInDirectory(NdkStack, loc).Any(); + return !string.IsNullOrEmpty (loc) && FindExecutableInDirectory (NdkStack, loc).Any (); } protected IEnumerable FindExecutableInPath (string executable) @@ -135,14 +135,14 @@ protected IEnumerable FindExecutableInPath (string executable) var pathDirs = path.Split (new char[] { Path.PathSeparator }, StringSplitOptions.RemoveEmptyEntries); foreach (var dir in pathDirs) { - foreach (var directory in FindExecutableInDirectory(executable, dir)) { + foreach (var directory in FindExecutableInDirectory (executable, dir)) { yield return directory; } } } - - protected IEnumerable FindExecutableInDirectory(string executable, string dir) - { + + protected IEnumerable FindExecutableInDirectory (string executable, string dir) + { foreach (var exe in Executables (executable)) if (File.Exists (Path.Combine (dir, exe))) yield return dir; diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools/Sdks/AndroidSdkUnix.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools/Sdks/AndroidSdkUnix.cs index 8331c3d168a..d6d583d3292 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools/Sdks/AndroidSdkUnix.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools/Sdks/AndroidSdkUnix.cs @@ -230,4 +230,3 @@ private static XDocument GetUnixConfigFile () } } - diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools/Sdks/AndroidSdkWindows.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools/Sdks/AndroidSdkWindows.cs index 6e606605302..f360b7c5cf1 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools/Sdks/AndroidSdkWindows.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools/Sdks/AndroidSdkWindows.cs @@ -199,4 +199,3 @@ private bool CheckRegistryKeyForExecutable (UIntPtr key, string subkey, string v #endregion } } - diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools/Sdks/MonoDroidSdkBase.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools/Sdks/MonoDroidSdkBase.cs index ac04856be3e..6ae60078f59 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools/Sdks/MonoDroidSdkBase.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools/Sdks/MonoDroidSdkBase.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Linq; using System.IO; using System.Collections.Generic; diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools/Sdks/MonoDroidSdkUnix.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools/Sdks/MonoDroidSdkUnix.cs index 9476adf3f32..19e6bbea707 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools/Sdks/MonoDroidSdkUnix.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools/Sdks/MonoDroidSdkUnix.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.IO; using System.Linq; using System.Collections.Generic; @@ -16,6 +16,7 @@ class MonoDroidSdkUnix : MonoDroidSdkBase readonly static string[] SearchPaths = { "/Library/Frameworks/Xamarin.Android.framework/Versions/Current/lib/mandroid", "/Developer/MonoAndroid/usr/lib/mandroid", + "/app/lib/mandroid", "/opt/mono-android/lib/mandroid" }; diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools/Sdks/MonoDroidSdkWindows.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools/Sdks/MonoDroidSdkWindows.cs index 2d7c4784d3b..c0886858b60 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools/Sdks/MonoDroidSdkWindows.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools/Sdks/MonoDroidSdkWindows.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.IO; using System.Collections.Generic; using System.Linq; @@ -11,9 +11,6 @@ protected override string FindRuntime () { string monoAndroidPath = Environment.GetEnvironmentVariable ("MONO_ANDROID_PATH"); if (!string.IsNullOrEmpty (monoAndroidPath)) { - string libMandroid = Path.Combine (monoAndroidPath, "lib", "mandroid"); - if (Directory.Exists (libMandroid) && ValidateRuntime (libMandroid)) - return libMandroid; if (Directory.Exists (monoAndroidPath) && ValidateRuntime (monoAndroidPath)) return monoAndroidPath; } @@ -24,6 +21,7 @@ protected override string FindRuntime () } static readonly string[] RuntimeToFrameworkPaths = new []{ + Path.Combine ("..", "..", "..", "Common7", "IDE", "ReferenceAssemblies", "Microsoft", "Framework","MonoAndroid"), Path.Combine ("..", "..", "..", "Reference Assemblies", "Microsoft", "Framework", "MonoAndroid"), Path.Combine (OS.ProgramFilesX86, "Reference Assemblies", "Microsoft", "Framework", "MonoAndroid"), }; From e9e3fc250c624682d2e3986071838eedb4a31c11 Mon Sep 17 00:00:00 2001 From: Dean Ellis Date: Thu, 9 Mar 2017 09:52:47 +0000 Subject: [PATCH 006/308] Fixed csproj to work with the latest Xamarin Studio --- .../src/Xamarin.Android.Tools/Xamarin.Android.Tools.csproj | 2 ++ 1 file changed, 2 insertions(+) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools/Xamarin.Android.Tools.csproj b/external/xamarin-android-tools/src/Xamarin.Android.Tools/Xamarin.Android.Tools.csproj index 9de64c33968..3e36d882b7d 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools/Xamarin.Android.Tools.csproj +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools/Xamarin.Android.Tools.csproj @@ -8,6 +8,8 @@ Xamarin.Android.Tools Xamarin.Android.Tools v4.5 + 8.0.30703 + 2.0 true From 08fd01cc3385fa3db66651130a4d6cd0a0883575 Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Mon, 19 Jun 2017 10:10:31 -0500 Subject: [PATCH 007/308] Locate VS 2017 installation of Android NDK The VS 2017 installer put my NDK in `C:\ProgramData\Microsoft\AndroidNDK64\android-ndk-r13b` --- .../src/Xamarin.Android.Tools/Sdks/AndroidSdkWindows.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools/Sdks/AndroidSdkWindows.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools/Sdks/AndroidSdkWindows.cs index f360b7c5cf1..78c37a57054 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools/Sdks/AndroidSdkWindows.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools/Sdks/AndroidSdkWindows.cs @@ -146,9 +146,10 @@ protected override IEnumerable GetAllAvailableAndroidNdks () // Check some hardcoded paths for good measure var xamarin_private = Path.Combine (Environment.GetFolderPath (Environment.SpecialFolder.LocalApplicationData), "Xamarin", "MonoAndroid"); var android_default = Path.Combine (OS.ProgramFilesX86, "Android"); + var vs_2017_default = Path.Combine (Environment.GetFolderPath (Environment.SpecialFolder.CommonApplicationData), "Microsoft", "AndroidNDK64"); var cdrive_default = @"C:\"; - foreach (var basePath in new string [] {xamarin_private, android_default, cdrive_default}) + foreach (var basePath in new string [] {xamarin_private, android_default, vs_2017_default, cdrive_default}) if (Directory.Exists (basePath)) foreach (var dir in Directory.GetDirectories (basePath, "android-ndk-r*")) if (ValidateAndroidNdkLocation (dir)) From f638548def7bfbdb1cef072599da43b71ed668c2 Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Thu, 22 Jun 2017 07:38:36 -0500 Subject: [PATCH 008/308] Locate VS 2017 installation of Xamarin.Android Not a fan of this, maybe a registry key would be better? --- .../Sdks/MonoDroidSdkWindows.cs | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools/Sdks/MonoDroidSdkWindows.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools/Sdks/MonoDroidSdkWindows.cs index c0886858b60..2087f313a6e 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools/Sdks/MonoDroidSdkWindows.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools/Sdks/MonoDroidSdkWindows.cs @@ -14,10 +14,23 @@ protected override string FindRuntime () if (Directory.Exists (monoAndroidPath) && ValidateRuntime (monoAndroidPath)) return monoAndroidPath; } + string xamarinSdk = Path.Combine (OS.ProgramFilesX86, "MSBuild", "Xamarin", "Android"); - return Directory.Exists (xamarinSdk) - ? xamarinSdk - : OS.ProgramFilesX86 + @"\MSBuild\Novell"; + if (Directory.Exists(xamarinSdk)) + return xamarinSdk; + xamarinSdk = Path.Combine(OS.ProgramFilesX86, "Microsoft Visual Studio", "2017"); + if (Directory.Exists(xamarinSdk)) + { + xamarinSdk = Directory.GetDirectories(xamarinSdk).FirstOrDefault(); + if (!string.IsNullOrEmpty(xamarinSdk)) + { + xamarinSdk = Path.Combine(xamarinSdk, "MSBuild", "Xamarin", "Android"); + if (Directory.Exists(xamarinSdk)) + return xamarinSdk; + } + } + + return OS.ProgramFilesX86 + @"\MSBuild\Novell"; } static readonly string[] RuntimeToFrameworkPaths = new []{ From 46acdaf2c56ac3e019b8d6b37befff5027a5a66b Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Thu, 22 Jun 2017 10:18:23 -0500 Subject: [PATCH 009/308] Setup a test project for validating `MonoDroidSdk` class This is basically just a smoke test making sure all the paths exist, and `AndroidLogger` doesn't throw errors out. --- external/xamarin-android-tools/.gitignore | 1 + .../MonoDroidSdkTests.cs | 69 +++++++++++++++++++ .../Properties/AssemblyInfo.cs | 13 ++++ .../Xamarin.Android.Tools.Tests.csproj | 60 ++++++++++++++++ .../packages.config | 4 ++ .../src/Xamarin.Android.Tools.sln | 20 ++++-- 6 files changed, 161 insertions(+), 6 deletions(-) create mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools.Tests/MonoDroidSdkTests.cs create mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools.Tests/Properties/AssemblyInfo.cs create mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools.Tests/Xamarin.Android.Tools.Tests.csproj create mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools.Tests/packages.config diff --git a/external/xamarin-android-tools/.gitignore b/external/xamarin-android-tools/.gitignore index 4b5771f1bd2..602bba7b1d2 100644 --- a/external/xamarin-android-tools/.gitignore +++ b/external/xamarin-android-tools/.gitignore @@ -10,3 +10,4 @@ src/*.dll .DS_Store bin **/obj +packages/ \ No newline at end of file diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.Tests/MonoDroidSdkTests.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.Tests/MonoDroidSdkTests.cs new file mode 100644 index 00000000000..599f4916c60 --- /dev/null +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.Tests/MonoDroidSdkTests.cs @@ -0,0 +1,69 @@ +using NUnit.Framework; +using System.Diagnostics; +using System.IO; + +namespace Xamarin.Android.Tools.Tests +{ + [TestFixture] + class MonoDroidSdkTests + { + [TestFixtureSetUp] + public void FixtureSetUp() + { + AndroidLogger.Info += OnInfo; + AndroidLogger.Warning += OnWarning; + AndroidLogger.Error += OnError; + } + + [TestFixtureTearDown] + public void FixtureTearDown() + { + AndroidLogger.Info -= OnInfo; + AndroidLogger.Warning -= OnWarning; + AndroidLogger.Error -= OnError; + } + + void OnInfo(string task, string message) + { + Debug.WriteLine(task + ": " + message); + } + + void OnWarning(string task, string message) + { + Assert.Fail(task + ": " + message); + } + + void OnError(string task, string message) + { + Assert.Fail(task + ": " + message); + } + + [Test] + public void RefreshWithoutParameters() + { + //Just checking for exceptions, or AndroidLogger + MonoDroidSdk.Refresh(); + } + + [Test] + public void BinPathExists() + { + string path = MonoDroidSdk.BinPath; + Assert.IsTrue(Directory.Exists(path), path + " does not exist!"); + } + + [Test] + public void FrameworkPathExists() + { + string path = MonoDroidSdk.FrameworkPath; + Assert.IsTrue(Directory.Exists(path), path + " does not exist!"); + } + + [Test] + public void RuntimePathExists() + { + string path = MonoDroidSdk.RuntimePath; + Assert.IsTrue(Directory.Exists(path), path + " does not exist!"); + } + } +} diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.Tests/Properties/AssemblyInfo.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.Tests/Properties/AssemblyInfo.cs new file mode 100644 index 00000000000..3dfdbae2c03 --- /dev/null +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.Tests/Properties/AssemblyInfo.cs @@ -0,0 +1,13 @@ +using System.Reflection; + +[assembly: AssemblyTitle("Xamarin.Android.Tools.Tests")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Xamarin.Android.Tools.Tests")] +[assembly: AssemblyCopyright("Copyright © Xamarin 2017")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.Tests/Xamarin.Android.Tools.Tests.csproj b/external/xamarin-android-tools/src/Xamarin.Android.Tools.Tests/Xamarin.Android.Tools.Tests.csproj new file mode 100644 index 00000000000..7d03cba28ff --- /dev/null +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.Tests/Xamarin.Android.Tools.Tests.csproj @@ -0,0 +1,60 @@ + + + + + Debug + AnyCPU + {1E5501E8-49C1-4659-838D-CC9720C5208F} + Library + Properties + Xamarin.Android.Tools.Tests + Xamarin.Android.Tools.Tests + v4.5.2 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\packages\NUnit.2.6.4\lib\nunit.framework.dll + + + + + + + + + + + + + + + + + + {e34bcfa0-caa4-412c-aa1c-75db8d67d157} + Xamarin.Android.Tools + + + + + + + \ No newline at end of file diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.Tests/packages.config b/external/xamarin-android-tools/src/Xamarin.Android.Tools.Tests/packages.config new file mode 100644 index 00000000000..e733ad565ae --- /dev/null +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.Tests/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.sln b/external/xamarin-android-tools/src/Xamarin.Android.Tools.sln index ed952df4246..53646d2ae98 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.sln +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.sln @@ -1,7 +1,8 @@ - Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 2012 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Xamarin.Android.Tools", "Xamarin.Android.Tools/Xamarin.Android.Tools.csproj", "{91713046-C358-4647-B162-ED4E1442F3D8}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Xamarin.Android.Tools", "Xamarin.Android.Tools\Xamarin.Android.Tools.csproj", "{E34BCFA0-CAA4-412C-AA1C-75DB8D67D157}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Xamarin.Android.Tools.Tests", "Xamarin.Android.Tools.Tests\Xamarin.Android.Tools.Tests.csproj", "{1E5501E8-49C1-4659-838D-CC9720C5208F}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -9,9 +10,16 @@ Global Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {91713046-C358-4647-B162-ED4E1442F3D8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {91713046-C358-4647-B162-ED4E1442F3D8}.Debug|Any CPU.Build.0 = Debug|Any CPU - {91713046-C358-4647-B162-ED4E1442F3D8}.Release|Any CPU.ActiveCfg = Release|Any CPU - {91713046-C358-4647-B162-ED4E1442F3D8}.Release|Any CPU.Build.0 = Release|Any CPU + {E34BCFA0-CAA4-412C-AA1C-75DB8D67D157}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E34BCFA0-CAA4-412C-AA1C-75DB8D67D157}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E34BCFA0-CAA4-412C-AA1C-75DB8D67D157}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E34BCFA0-CAA4-412C-AA1C-75DB8D67D157}.Release|Any CPU.Build.0 = Release|Any CPU + {1E5501E8-49C1-4659-838D-CC9720C5208F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1E5501E8-49C1-4659-838D-CC9720C5208F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1E5501E8-49C1-4659-838D-CC9720C5208F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1E5501E8-49C1-4659-838D-CC9720C5208F}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE EndGlobalSection EndGlobal From b31e9af85b1c5effae6870786ada385d5ae49d05 Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Thu, 22 Jun 2017 10:21:06 -0500 Subject: [PATCH 010/308] Implement running `vswhere.exe` to locate Xamarin SDKs - We can run `vswhere -latest -requires Component.Xamarin` which will locate the latest VS installation that contains Xamarin - Using `-property installationPath` causes stdout to only contain the single value - Included comments containing various URLs of interest --- .../Sdks/MonoDroidSdkWindows.cs | 40 ++++++++++++++----- 1 file changed, 29 insertions(+), 11 deletions(-) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools/Sdks/MonoDroidSdkWindows.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools/Sdks/MonoDroidSdkWindows.cs index 2087f313a6e..5ef827ecd0d 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools/Sdks/MonoDroidSdkWindows.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools/Sdks/MonoDroidSdkWindows.cs @@ -2,6 +2,7 @@ using System.IO; using System.Collections.Generic; using System.Linq; +using System.Diagnostics; namespace Xamarin.Android.Tools { @@ -18,19 +19,36 @@ protected override string FindRuntime () string xamarinSdk = Path.Combine (OS.ProgramFilesX86, "MSBuild", "Xamarin", "Android"); if (Directory.Exists(xamarinSdk)) return xamarinSdk; - xamarinSdk = Path.Combine(OS.ProgramFilesX86, "Microsoft Visual Studio", "2017"); - if (Directory.Exists(xamarinSdk)) + if (TryVSWhere(out xamarinSdk)) + return xamarinSdk; + return OS.ProgramFilesX86 + @"\MSBuild\Novell"; + } + + bool TryVSWhere(out string xamarinSdk) + { + xamarinSdk = null; + + //Docs on this tool here: https://github.com/Microsoft/vswhere + string vswhere = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), "Microsoft Visual Studio", "Installer", "vswhere.exe"); + if (!File.Exists(vswhere)) + return false; + + //VS Workload ID here: https://docs.microsoft.com/en-us/visualstudio/install/workload-component-id-vs-community + var p = Process.Start(new ProcessStartInfo { - xamarinSdk = Directory.GetDirectories(xamarinSdk).FirstOrDefault(); - if (!string.IsNullOrEmpty(xamarinSdk)) - { - xamarinSdk = Path.Combine(xamarinSdk, "MSBuild", "Xamarin", "Android"); - if (Directory.Exists(xamarinSdk)) - return xamarinSdk; - } - } + FileName = vswhere, + Arguments = "-latest -requires Component.Xamarin -property installationPath", + RedirectStandardError = true, + RedirectStandardOutput = true, + UseShellExecute = false, + }); + p.WaitForExit(); - return OS.ProgramFilesX86 + @"\MSBuild\Novell"; + if (p.ExitCode != 0) + return false; + + xamarinSdk = Path.Combine(p.StandardOutput.ReadToEnd().Trim(), "MSBuild", "Xamarin", "Android"); + return true; } static readonly string[] RuntimeToFrameworkPaths = new []{ From 52f56cc42c29d8d0bf36b9804fb05282b3d95e71 Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Thu, 22 Jun 2017 13:18:35 -0500 Subject: [PATCH 011/308] `ValidateRuntime` should include check for `Xamarin.Android.Common.Targets` --- .../src/Xamarin.Android.Tools/Sdks/MonoDroidSdkBase.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools/Sdks/MonoDroidSdkBase.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools/Sdks/MonoDroidSdkBase.cs index 6ae60078f59..b6038a87ca1 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools/Sdks/MonoDroidSdkBase.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools/Sdks/MonoDroidSdkBase.cs @@ -108,7 +108,8 @@ protected static bool ValidateRuntime (string loc) return !string.IsNullOrWhiteSpace (loc) && (File.Exists (Path.Combine (loc, DebugRuntime)) || // Normal/expected File.Exists (Path.Combine (loc, ClassParseExe)) || // Normal/expected - File.Exists (Path.Combine (loc, "Ionic.Zip.dll"))); // Wrench builds + File.Exists (Path.Combine (loc, "Ionic.Zip.dll")) || // Wrench builds + File.Exists (Path.Combine (loc, "Xamarin.Android.Common.targets"))); //VS on Windows } protected static bool ValidateFramework (string loc) From 2ceaf4dd0014ea2960d8db5ca9494722cab2a27b Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Thu, 22 Jun 2017 13:52:57 -0500 Subject: [PATCH 012/308] Removed vswhere in favor of probing known directories for VS 2017 - First check %VSINSTALLDIR% if we are in VS command prompt - Next check each known edition: Enterprise, Professional, and Community --- .../Sdks/MonoDroidSdkWindows.cs | 45 +++++++------------ 1 file changed, 15 insertions(+), 30 deletions(-) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools/Sdks/MonoDroidSdkWindows.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools/Sdks/MonoDroidSdkWindows.cs index 5ef827ecd0d..172bd9960eb 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools/Sdks/MonoDroidSdkWindows.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools/Sdks/MonoDroidSdkWindows.cs @@ -2,12 +2,18 @@ using System.IO; using System.Collections.Generic; using System.Linq; -using System.Diagnostics; namespace Xamarin.Android.Tools { class MonoDroidSdkWindows : MonoDroidSdkBase { + static readonly string[] VisualStudioPaths = new[] { + Environment.GetEnvironmentVariable ("VSINSTALLDIR"), + Path.Combine (OS.ProgramFilesX86, "Microsoft Visual Studio", "2017", "Enterprise"), + Path.Combine (OS.ProgramFilesX86, "Microsoft Visual Studio", "2017", "Professional"), + Path.Combine (OS.ProgramFilesX86, "Microsoft Visual Studio", "2017", "Community"), + }; + protected override string FindRuntime () { string monoAndroidPath = Environment.GetEnvironmentVariable ("MONO_ANDROID_PATH"); @@ -19,36 +25,15 @@ protected override string FindRuntime () string xamarinSdk = Path.Combine (OS.ProgramFilesX86, "MSBuild", "Xamarin", "Android"); if (Directory.Exists(xamarinSdk)) return xamarinSdk; - if (TryVSWhere(out xamarinSdk)) - return xamarinSdk; - return OS.ProgramFilesX86 + @"\MSBuild\Novell"; - } - - bool TryVSWhere(out string xamarinSdk) - { - xamarinSdk = null; - - //Docs on this tool here: https://github.com/Microsoft/vswhere - string vswhere = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), "Microsoft Visual Studio", "Installer", "vswhere.exe"); - if (!File.Exists(vswhere)) - return false; - - //VS Workload ID here: https://docs.microsoft.com/en-us/visualstudio/install/workload-component-id-vs-community - var p = Process.Start(new ProcessStartInfo - { - FileName = vswhere, - Arguments = "-latest -requires Component.Xamarin -property installationPath", - RedirectStandardError = true, - RedirectStandardOutput = true, - UseShellExecute = false, - }); - p.WaitForExit(); - if (p.ExitCode != 0) - return false; - - xamarinSdk = Path.Combine(p.StandardOutput.ReadToEnd().Trim(), "MSBuild", "Xamarin", "Android"); - return true; + foreach (var vsPath in VisualStudioPaths) { + if (string.IsNullOrEmpty(vsPath)) + continue; + xamarinSdk = Path.Combine(vsPath, "MSBuild", "Xamarin", "Android"); + if (Directory.Exists(xamarinSdk) && ValidateRuntime(xamarinSdk)) + return xamarinSdk; + } + return OS.ProgramFilesX86 + @"\MSBuild\Novell"; } static readonly string[] RuntimeToFrameworkPaths = new []{ From 93ec306351df6c93473e7abaaa844db8a0727546 Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Thu, 22 Jun 2017 15:05:53 -0500 Subject: [PATCH 013/308] `MonoDroidSdkBase.ValidateRuntime` remove check for Ionic.Zip.dll Also fixed formatting slightly --- .../src/Xamarin.Android.Tools/Sdks/MonoDroidSdkBase.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools/Sdks/MonoDroidSdkBase.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools/Sdks/MonoDroidSdkBase.cs index b6038a87ca1..ed5ddd7ac28 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools/Sdks/MonoDroidSdkBase.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools/Sdks/MonoDroidSdkBase.cs @@ -108,8 +108,7 @@ protected static bool ValidateRuntime (string loc) return !string.IsNullOrWhiteSpace (loc) && (File.Exists (Path.Combine (loc, DebugRuntime)) || // Normal/expected File.Exists (Path.Combine (loc, ClassParseExe)) || // Normal/expected - File.Exists (Path.Combine (loc, "Ionic.Zip.dll")) || // Wrench builds - File.Exists (Path.Combine (loc, "Xamarin.Android.Common.targets"))); //VS on Windows + File.Exists (Path.Combine (loc, "Xamarin.Android.Common.targets"))); //VS on Windows } protected static bool ValidateFramework (string loc) From c53682dc2ff382b57b48f259586862c1716b8351 Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Thu, 22 Jun 2017 15:25:26 -0500 Subject: [PATCH 014/308] Check for older VS installations the same as 2017 - Also check for them last, choosing 2017 first - Fixed coding style --- .../Xamarin.Android.Tools/Sdks/MonoDroidSdkWindows.cs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools/Sdks/MonoDroidSdkWindows.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools/Sdks/MonoDroidSdkWindows.cs index 172bd9960eb..62ea0cabc41 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools/Sdks/MonoDroidSdkWindows.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools/Sdks/MonoDroidSdkWindows.cs @@ -12,6 +12,7 @@ class MonoDroidSdkWindows : MonoDroidSdkBase Path.Combine (OS.ProgramFilesX86, "Microsoft Visual Studio", "2017", "Enterprise"), Path.Combine (OS.ProgramFilesX86, "Microsoft Visual Studio", "2017", "Professional"), Path.Combine (OS.ProgramFilesX86, "Microsoft Visual Studio", "2017", "Community"), + Path.Combine (OS.ProgramFilesX86, "MSBuild", "Xamarin", "Android"), //VS older than 2017 }; protected override string FindRuntime () @@ -22,15 +23,11 @@ protected override string FindRuntime () return monoAndroidPath; } - string xamarinSdk = Path.Combine (OS.ProgramFilesX86, "MSBuild", "Xamarin", "Android"); - if (Directory.Exists(xamarinSdk)) - return xamarinSdk; - foreach (var vsPath in VisualStudioPaths) { - if (string.IsNullOrEmpty(vsPath)) + if (string.IsNullOrEmpty (vsPath)) continue; - xamarinSdk = Path.Combine(vsPath, "MSBuild", "Xamarin", "Android"); - if (Directory.Exists(xamarinSdk) && ValidateRuntime(xamarinSdk)) + var xamarinSdk = Path.Combine (vsPath, "MSBuild", "Xamarin", "Android"); + if (Directory.Exists (xamarinSdk) && ValidateRuntime (xamarinSdk)) return xamarinSdk; } return OS.ProgramFilesX86 + @"\MSBuild\Novell"; From 71f2318439272f43056ee94c8952dae665ced6e2 Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Thu, 22 Jun 2017 15:27:37 -0500 Subject: [PATCH 015/308] Fixed coding style in unit tests --- .../MonoDroidSdkTests.cs | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.Tests/MonoDroidSdkTests.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.Tests/MonoDroidSdkTests.cs index 599f4916c60..68299a31ebe 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.Tests/MonoDroidSdkTests.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.Tests/MonoDroidSdkTests.cs @@ -8,7 +8,7 @@ namespace Xamarin.Android.Tools.Tests class MonoDroidSdkTests { [TestFixtureSetUp] - public void FixtureSetUp() + public void FixtureSetUp () { AndroidLogger.Info += OnInfo; AndroidLogger.Warning += OnWarning; @@ -16,54 +16,54 @@ public void FixtureSetUp() } [TestFixtureTearDown] - public void FixtureTearDown() + public void FixtureTearDown () { AndroidLogger.Info -= OnInfo; AndroidLogger.Warning -= OnWarning; AndroidLogger.Error -= OnError; } - void OnInfo(string task, string message) + void OnInfo (string task, string message) { - Debug.WriteLine(task + ": " + message); + Debug.WriteLine (task + ": " + message); } - void OnWarning(string task, string message) + void OnWarning (string task, string message) { - Assert.Fail(task + ": " + message); + Assert.Fail (task + ": " + message); } - void OnError(string task, string message) + void OnError (string task, string message) { - Assert.Fail(task + ": " + message); + Assert.Fail (task + ": " + message); } [Test] - public void RefreshWithoutParameters() + public void RefreshWithoutParameters () { //Just checking for exceptions, or AndroidLogger - MonoDroidSdk.Refresh(); + MonoDroidSdk.Refresh (); } [Test] - public void BinPathExists() + public void BinPathExists () { string path = MonoDroidSdk.BinPath; - Assert.IsTrue(Directory.Exists(path), path + " does not exist!"); + Assert.IsTrue (Directory.Exists (path), path + " does not exist!"); } [Test] - public void FrameworkPathExists() + public void FrameworkPathExists () { string path = MonoDroidSdk.FrameworkPath; - Assert.IsTrue(Directory.Exists(path), path + " does not exist!"); + Assert.IsTrue (Directory.Exists (path), path + " does not exist!"); } [Test] - public void RuntimePathExists() + public void RuntimePathExists () { string path = MonoDroidSdk.RuntimePath; - Assert.IsTrue(Directory.Exists(path), path + " does not exist!"); + Assert.IsTrue (Directory.Exists (path), path + " does not exist!"); } } } From 67c3fcbda78988af8d5c3dd8dd34555cebbb5310 Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Thu, 22 Jun 2017 15:38:49 -0500 Subject: [PATCH 016/308] VS prior to 2017, fix path The code below actually appendsd \MSBuild\Xamarin\Android, so we shouldn't include it here. --- .../src/Xamarin.Android.Tools/Sdks/MonoDroidSdkWindows.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools/Sdks/MonoDroidSdkWindows.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools/Sdks/MonoDroidSdkWindows.cs index 62ea0cabc41..60eaeddfe28 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools/Sdks/MonoDroidSdkWindows.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools/Sdks/MonoDroidSdkWindows.cs @@ -12,7 +12,7 @@ class MonoDroidSdkWindows : MonoDroidSdkBase Path.Combine (OS.ProgramFilesX86, "Microsoft Visual Studio", "2017", "Enterprise"), Path.Combine (OS.ProgramFilesX86, "Microsoft Visual Studio", "2017", "Professional"), Path.Combine (OS.ProgramFilesX86, "Microsoft Visual Studio", "2017", "Community"), - Path.Combine (OS.ProgramFilesX86, "MSBuild", "Xamarin", "Android"), //VS older than 2017 + Path.Combine (OS.ProgramFilesX86), //VS older than 2017, MSBuild\Xamarin\Android is located in C:\Program Files (x86)\ }; protected override string FindRuntime () From d4b3b0bb818fc5ce29b2d8c5c91b24947a10c8f5 Mon Sep 17 00:00:00 2001 From: Jonathan Pryor Date: Wed, 6 Sep 2017 21:50:25 -0400 Subject: [PATCH 017/308] Restructure the project for consistency with xamarin-android `Xamarin.Android.sln` is now a toplevel file, so it's easier to see without navigating into the `src` directory. Add a `Makefile` with three targets of consequence: * `make prepare`: Prepare the project for building, e.g. restore NuGet packages. * `make all`: Build the project. * `make run-all-tests`: Run the NUnit tests. Update to use NUnit 3.2.1 packages. Update `README.md` with build instructions. Add a `LICENSE` file. Rename `Xamarin.Android.Tools.dll` to `Xamarin.Android.Tools.AndroidSdk.dll`. Xamarin.Android already has multiple `Xamarin.Android.Tools.*.dll` assemblies, e.g. `Xamarin.Android.Tools.BootstrapTasks.dll`. Naming-wise, `Xamarin.Android.Tools.dll` sounds like it should be *core* to *all* related assemblies, but it isn't, and won't be. Instead, its core responsibility is around the Android SDK, so name it accordingly. Move unit tests for `Xamarin.Android.Tools.AndroidSdk` into `src/Xamarin.Android.Tools.AndroidSdk/Tests` (child, not a peer). Rename the unit test assembly to `Xamarin.Android.Tools.AndroidSdk-Tests.dll`, and build into `bin/Test$(Configuration)`. --- external/xamarin-android-tools/.gitignore | 3 +- external/xamarin-android-tools/LICENSE | 25 +++++++ external/xamarin-android-tools/Makefile | 40 ++++++++++ external/xamarin-android-tools/README.md | 70 ++++++++++++++++++ .../{src => }/Xamarin.Android.Tools.sln | 4 +- .../build-tools/scripts/msbuild.mk | 50 +++++++++++++ .../.DS_Store | Bin .../AndroidLogger.cs | 0 .../AndroidSdk.cs | 0 .../AndroidTargetArch.cs | 0 .../AndroidVersion.cs | 0 .../MonoDroidSdk.cs | 0 .../OS.cs | 0 .../ProcessUtils.cs | 0 .../Properties/AssemblyInfo.cs | 0 .../Sdks/AndroidSdkBase.cs | 0 .../Sdks/AndroidSdkUnix.cs | 0 .../Sdks/AndroidSdkWindows.cs | 0 .../Sdks/MonoDroidSdkBase.cs | 0 .../Sdks/MonoDroidSdkUnix.cs | 0 .../Sdks/MonoDroidSdkWindows.cs | 0 .../Tests}/MonoDroidSdkTests.cs | 0 .../Tests}/Properties/AssemblyInfo.cs | 0 ...rin.Android.Tools.AndroidSdk-Tests.csproj} | 14 ++-- .../Tests/packages.config | 8 ++ .../Xamarin.Android.Tools.AndroidSdk.csproj} | 0 ...amarin.Android.Tools.AndroidSdk.userprefs} | 0 .../packages.config | 4 - 28 files changed, 204 insertions(+), 14 deletions(-) create mode 100644 external/xamarin-android-tools/LICENSE create mode 100644 external/xamarin-android-tools/Makefile rename external/xamarin-android-tools/{src => }/Xamarin.Android.Tools.sln (78%) create mode 100644 external/xamarin-android-tools/build-tools/scripts/msbuild.mk rename external/xamarin-android-tools/src/{Xamarin.Android.Tools => Xamarin.Android.Tools.AndroidSdk}/.DS_Store (100%) rename external/xamarin-android-tools/src/{Xamarin.Android.Tools => Xamarin.Android.Tools.AndroidSdk}/AndroidLogger.cs (100%) rename external/xamarin-android-tools/src/{Xamarin.Android.Tools => Xamarin.Android.Tools.AndroidSdk}/AndroidSdk.cs (100%) rename external/xamarin-android-tools/src/{Xamarin.Android.Tools => Xamarin.Android.Tools.AndroidSdk}/AndroidTargetArch.cs (100%) rename external/xamarin-android-tools/src/{Xamarin.Android.Tools => Xamarin.Android.Tools.AndroidSdk}/AndroidVersion.cs (100%) rename external/xamarin-android-tools/src/{Xamarin.Android.Tools => Xamarin.Android.Tools.AndroidSdk}/MonoDroidSdk.cs (100%) rename external/xamarin-android-tools/src/{Xamarin.Android.Tools => Xamarin.Android.Tools.AndroidSdk}/OS.cs (100%) rename external/xamarin-android-tools/src/{Xamarin.Android.Tools => Xamarin.Android.Tools.AndroidSdk}/ProcessUtils.cs (100%) rename external/xamarin-android-tools/src/{Xamarin.Android.Tools => Xamarin.Android.Tools.AndroidSdk}/Properties/AssemblyInfo.cs (100%) rename external/xamarin-android-tools/src/{Xamarin.Android.Tools => Xamarin.Android.Tools.AndroidSdk}/Sdks/AndroidSdkBase.cs (100%) rename external/xamarin-android-tools/src/{Xamarin.Android.Tools => Xamarin.Android.Tools.AndroidSdk}/Sdks/AndroidSdkUnix.cs (100%) rename external/xamarin-android-tools/src/{Xamarin.Android.Tools => Xamarin.Android.Tools.AndroidSdk}/Sdks/AndroidSdkWindows.cs (100%) rename external/xamarin-android-tools/src/{Xamarin.Android.Tools => Xamarin.Android.Tools.AndroidSdk}/Sdks/MonoDroidSdkBase.cs (100%) rename external/xamarin-android-tools/src/{Xamarin.Android.Tools => Xamarin.Android.Tools.AndroidSdk}/Sdks/MonoDroidSdkUnix.cs (100%) rename external/xamarin-android-tools/src/{Xamarin.Android.Tools => Xamarin.Android.Tools.AndroidSdk}/Sdks/MonoDroidSdkWindows.cs (100%) rename external/xamarin-android-tools/src/{Xamarin.Android.Tools.Tests => Xamarin.Android.Tools.AndroidSdk/Tests}/MonoDroidSdkTests.cs (100%) rename external/xamarin-android-tools/src/{Xamarin.Android.Tools.Tests => Xamarin.Android.Tools.AndroidSdk/Tests}/Properties/AssemblyInfo.cs (100%) rename external/xamarin-android-tools/src/{Xamarin.Android.Tools.Tests/Xamarin.Android.Tools.Tests.csproj => Xamarin.Android.Tools.AndroidSdk/Tests/Xamarin.Android.Tools.AndroidSdk-Tests.csproj} (81%) create mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/packages.config rename external/xamarin-android-tools/src/{Xamarin.Android.Tools/Xamarin.Android.Tools.csproj => Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj} (100%) rename external/xamarin-android-tools/src/{Xamarin.Android.Tools/Xamarin.Android.Tools.userprefs => Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.userprefs} (100%) delete mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools.Tests/packages.config diff --git a/external/xamarin-android-tools/.gitignore b/external/xamarin-android-tools/.gitignore index 602bba7b1d2..dc27d996fb2 100644 --- a/external/xamarin-android-tools/.gitignore +++ b/external/xamarin-android-tools/.gitignore @@ -10,4 +10,5 @@ src/*.dll .DS_Store bin **/obj -packages/ \ No newline at end of file +packages/ +TestResult-*.xml diff --git a/external/xamarin-android-tools/LICENSE b/external/xamarin-android-tools/LICENSE new file mode 100644 index 00000000000..e5d11ed6686 --- /dev/null +++ b/external/xamarin-android-tools/LICENSE @@ -0,0 +1,25 @@ +xamarin-android-tools + +The MIT License (MIT) + +Copyright (c) .NET Foundation Contributors + +All rights reserved. + +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. diff --git a/external/xamarin-android-tools/Makefile b/external/xamarin-android-tools/Makefile new file mode 100644 index 00000000000..7cc754fa447 --- /dev/null +++ b/external/xamarin-android-tools/Makefile @@ -0,0 +1,40 @@ +CONFIGURATION := Debug +NUNIT_CONSOLE := packages/NUnit.ConsoleRunner.3.2.1/tools/nunit3-console.exe +OS := $(shell uname) +RUNTIME := mono --debug=casts +V ?= 0 + +include build-tools/scripts/msbuild.mk + +all: + $(MSBUILD) $(MSBUILD_FLAGS) Xamarin.Android.Tools.sln + +clean: + -$(MSBUILD) $(MSBUILD_FLAGS) /t:Clean Xamarin.Android.Tools.sln + +prepare: + nuget restore Xamarin.Android.Tools.sln + +run-all-tests: run-nunit-tests + +# $(call RUN_NUNIT_TEST,filename,log-lref?) +define RUN_NUNIT_TEST + MONO_TRACE_LISTENER=Console.Out \ + $(RUNTIME) \ + $(NUNIT_CONSOLE) $(NUNIT_EXTRA) $(1) \ + $(if $(RUN),-run:$(RUN)) \ + --result="TestResult-$(basename $(notdir $(1))).xml;format=nunit2" \ + -output=bin/Test$(CONFIGURATION)/TestOutput-$(basename $(notdir $(1))).txt \ + || true ; \ + if [ -f "bin/Test$(CONFIGURATION)/TestOutput-$(basename $(notdir $(1))).txt" ] ; then \ + cat bin/Test$(CONFIGURATION)/TestOutput-$(basename $(notdir $(1))).txt ; \ + fi +endef + +$(NUNIT_CONSOLE): prepare + +NUNIT_TESTS = \ + bin/Test$(CONFIGURATION)/Xamarin.Android.Tools-Tests.dll + +run-nunit-tests: $(NUNIT_TESTS) + $(foreach t,$(NUNIT_TESTS), $(call RUN_NUNIT_TEST,$(t),1)) diff --git a/external/xamarin-android-tools/README.md b/external/xamarin-android-tools/README.md index f09ff17c430..aeaf9f10ae1 100644 --- a/external/xamarin-android-tools/README.md +++ b/external/xamarin-android-tools/README.md @@ -1,3 +1,73 @@ # xamarin-android-tools +**xamarin-android-tools** is a repo to easily share code between the +[xamarin-android][x-a] repo and the Xamarin.Android commercial tooling, +such as IDE extensions, without requiring that the IDE extensions +submodule the entire **xamarin-android** repo, which is gigantic. +[x-a]: https://github.com/xamarin/xamarin-android + +# Build Requirements + +**xamarin-android-tools** requires Mono 4.8 or later and `nuget`. + +# Build Configuration + +The default `make all` target accepts the following optional +**make**(1) variables: + + * `$(CONFIGURATION)`: The configuration to build. + Possible values include `Debug` and `Release`. + The default value is `Debug`. + * `$(V)`: Controls build verbosity. When set to a non-zero value, + The build is built with `/v:diag` logging. + +# Build + +To build **xamarin-android-tools**, first prepare the project: + + make prepare + +This will perform `nuget restore` and any other pre-build tasks +that need to be performed. + +Next, run `make`: + + make + +# Tests + +To run the unit tests: + + make run-all-tests + +# Build Output Directory Structure + +There are two configurations, `Debug` and `Release`, controlled by the +`$(Configuration)` MSBuild property or the `$(CONFIGURATION)` make variable. + +The `bin\$(Configuration)` directory, e.g. `bin\Debug`, contains +*redistributable* artifacts. The `bin\Test$(Configuration)` directory, +e.g. `bin\TestDebug`, contains unit tests and related files. + +* `bin\$(Configuration)`: redistributable build artifacts. +* `bin\Test$(Configuration)`: Unit tests and related files. + +# Contributing + +This project has adopted the code of conduct defined by the Contributor Covenant +to clarify expected behavior in our community. For more information, see the +[.NET Foundation Code of Conduct](http://www.dotnetfoundation.org/code-of-conduct). + +# Mailing Lists + +To discuss this project, and participate in the design, we use the +[android-devel@lists.xamarin.com](http://lists.xamarin.com/mailman/listinfo/android-devel) mailing list. + +# Coding Guidelines + +We use [Mono's Coding Guidelines](http://www.mono-project.com/community/contributing/coding-guidelines/). + +# Reporting Bugs + +We use [GitHub](https://github.com/xamarin/xamarin-android-tools/issues) to track issues. diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.sln b/external/xamarin-android-tools/Xamarin.Android.Tools.sln similarity index 78% rename from external/xamarin-android-tools/src/Xamarin.Android.Tools.sln rename to external/xamarin-android-tools/Xamarin.Android.Tools.sln index 53646d2ae98..7fbff60706e 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.sln +++ b/external/xamarin-android-tools/Xamarin.Android.Tools.sln @@ -1,8 +1,8 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 2012 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Xamarin.Android.Tools", "Xamarin.Android.Tools\Xamarin.Android.Tools.csproj", "{E34BCFA0-CAA4-412C-AA1C-75DB8D67D157}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Xamarin.Android.Tools.AndroidSdk", "src\Xamarin.Android.Tools.AndroidSdk\Xamarin.Android.Tools.AndroidSdk.csproj", "{E34BCFA0-CAA4-412C-AA1C-75DB8D67D157}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Xamarin.Android.Tools.Tests", "Xamarin.Android.Tools.Tests\Xamarin.Android.Tools.Tests.csproj", "{1E5501E8-49C1-4659-838D-CC9720C5208F}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Xamarin.Android.Tools.AndroidSdk-Tests", "src\Xamarin.Android.Tools.AndroidSdk\Tests\Xamarin.Android.Tools.AndroidSdk-Tests.csproj", "{1E5501E8-49C1-4659-838D-CC9720C5208F}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/external/xamarin-android-tools/build-tools/scripts/msbuild.mk b/external/xamarin-android-tools/build-tools/scripts/msbuild.mk new file mode 100644 index 00000000000..54adf86daaf --- /dev/null +++ b/external/xamarin-android-tools/build-tools/scripts/msbuild.mk @@ -0,0 +1,50 @@ +# +# MSBuild Abstraction. +# +# Makefile targets which need to invoke MSBuild should use `$(MSBUILD)`, +# not some specific MSBuild program such as `xbuild` or `msbuild`. +# +# Typical use will also include `$(MSBUILD_FLAGS)`, which provides the +# Configuration and logging verbosity, as per $(CONFIGURATION) and $(V): +# +# $(MSBUILD) $(MSBUILD_FLAGS) path/to/Project.csproj +# +# Inputs: +# +# $(CONFIGURATION): Build configuration name, e.g. Debug or Release +# $(MSBUILD): The MSBuild program to use. +# $(MSBUILD_ARGS): Extra arguments to pass to $(MSBUILD); embedded into $(MSBUILD_FLAGS) +# $(OS): Operating system; used to determine `pkg-config` location +# $(V): Build verbosity +# +# Outputs: +# +# $(MSBUILD): The MSBuild program to use. Defaults to `xbuild` unless overridden. +# $(MSBUILD_FLAGS): Additional MSBuild flags; contains $(CONFIGURATION), $(V), $(MSBUILD_ARGS). + +MSBUILD = msbuild +MSBUILD_FLAGS = /p:Configuration=$(CONFIGURATION) $(MSBUILD_ARGS) + +ifeq ($(OS),Darwin) +_PKG_CONFIG = /Library/Frameworks/Mono.framework/Commands/pkg-config +else # $(OS) != Darwin +_PKG_CONFIG = pkg-config +endif # $(OS) == Darwin + +ifneq ($(V),0) +MSBUILD_FLAGS += /v:diag +endif # $(V) != 0 + +ifeq ($(MSBUILD),msbuild) +USE_MSBUILD = 1 +endif # $(MSBUILD) == msbuild + +ifeq ($(USE_MSBUILD),1) +else # $(MSBUILD) != 1 +_CSC_EMITS_PDB := $(shell if $(_PKG_CONFIG) --atleast-version=4.9 mono ; then echo Pdb; fi ) +ifeq ($(_CSC_EMITS_PDB),Pdb) +MSBUILD_FLAGS += /p:_DebugFileExt=.pdb +else # $(_CSC_EMITS_PDB) == '' +MSBUILD_FLAGS += /p:_DebugFileExt=.mdb +endif # $(_CSC_EMITS_PDB) == Pdb +endif # $(USE_MSBUILD) == 1 diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools/.DS_Store b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/.DS_Store similarity index 100% rename from external/xamarin-android-tools/src/Xamarin.Android.Tools/.DS_Store rename to external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/.DS_Store diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools/AndroidLogger.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidLogger.cs similarity index 100% rename from external/xamarin-android-tools/src/Xamarin.Android.Tools/AndroidLogger.cs rename to external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidLogger.cs diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools/AndroidSdk.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidSdk.cs similarity index 100% rename from external/xamarin-android-tools/src/Xamarin.Android.Tools/AndroidSdk.cs rename to external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidSdk.cs diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools/AndroidTargetArch.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidTargetArch.cs similarity index 100% rename from external/xamarin-android-tools/src/Xamarin.Android.Tools/AndroidTargetArch.cs rename to external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidTargetArch.cs diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools/AndroidVersion.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidVersion.cs similarity index 100% rename from external/xamarin-android-tools/src/Xamarin.Android.Tools/AndroidVersion.cs rename to external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidVersion.cs diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools/MonoDroidSdk.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/MonoDroidSdk.cs similarity index 100% rename from external/xamarin-android-tools/src/Xamarin.Android.Tools/MonoDroidSdk.cs rename to external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/MonoDroidSdk.cs diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools/OS.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/OS.cs similarity index 100% rename from external/xamarin-android-tools/src/Xamarin.Android.Tools/OS.cs rename to external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/OS.cs diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools/ProcessUtils.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/ProcessUtils.cs similarity index 100% rename from external/xamarin-android-tools/src/Xamarin.Android.Tools/ProcessUtils.cs rename to external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/ProcessUtils.cs diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools/Properties/AssemblyInfo.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/AssemblyInfo.cs similarity index 100% rename from external/xamarin-android-tools/src/Xamarin.Android.Tools/Properties/AssemblyInfo.cs rename to external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/AssemblyInfo.cs diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools/Sdks/AndroidSdkBase.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkBase.cs similarity index 100% rename from external/xamarin-android-tools/src/Xamarin.Android.Tools/Sdks/AndroidSdkBase.cs rename to external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkBase.cs diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools/Sdks/AndroidSdkUnix.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkUnix.cs similarity index 100% rename from external/xamarin-android-tools/src/Xamarin.Android.Tools/Sdks/AndroidSdkUnix.cs rename to external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkUnix.cs diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools/Sdks/AndroidSdkWindows.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkWindows.cs similarity index 100% rename from external/xamarin-android-tools/src/Xamarin.Android.Tools/Sdks/AndroidSdkWindows.cs rename to external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkWindows.cs diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools/Sdks/MonoDroidSdkBase.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/MonoDroidSdkBase.cs similarity index 100% rename from external/xamarin-android-tools/src/Xamarin.Android.Tools/Sdks/MonoDroidSdkBase.cs rename to external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/MonoDroidSdkBase.cs diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools/Sdks/MonoDroidSdkUnix.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/MonoDroidSdkUnix.cs similarity index 100% rename from external/xamarin-android-tools/src/Xamarin.Android.Tools/Sdks/MonoDroidSdkUnix.cs rename to external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/MonoDroidSdkUnix.cs diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools/Sdks/MonoDroidSdkWindows.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/MonoDroidSdkWindows.cs similarity index 100% rename from external/xamarin-android-tools/src/Xamarin.Android.Tools/Sdks/MonoDroidSdkWindows.cs rename to external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/MonoDroidSdkWindows.cs diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.Tests/MonoDroidSdkTests.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/MonoDroidSdkTests.cs similarity index 100% rename from external/xamarin-android-tools/src/Xamarin.Android.Tools.Tests/MonoDroidSdkTests.cs rename to external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/MonoDroidSdkTests.cs diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.Tests/Properties/AssemblyInfo.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/Properties/AssemblyInfo.cs similarity index 100% rename from external/xamarin-android-tools/src/Xamarin.Android.Tools.Tests/Properties/AssemblyInfo.cs rename to external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/Properties/AssemblyInfo.cs diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.Tests/Xamarin.Android.Tools.Tests.csproj b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/Xamarin.Android.Tools.AndroidSdk-Tests.csproj similarity index 81% rename from external/xamarin-android-tools/src/Xamarin.Android.Tools.Tests/Xamarin.Android.Tools.Tests.csproj rename to external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/Xamarin.Android.Tools.AndroidSdk-Tests.csproj index 7d03cba28ff..c267315f4ee 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.Tests/Xamarin.Android.Tools.Tests.csproj +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/Xamarin.Android.Tools.AndroidSdk-Tests.csproj @@ -8,7 +8,7 @@ Library Properties Xamarin.Android.Tools.Tests - Xamarin.Android.Tools.Tests + Xamarin.Android.Tools-Tests v4.5.2 512 @@ -16,7 +16,7 @@ true full false - bin\Debug\ + ..\..\..\bin\TestDebug\ DEBUG;TRACE prompt 4 @@ -24,14 +24,14 @@ pdbonly true - bin\Release\ + ..\..\..\bin\TestRelease\ TRACE prompt 4 - - ..\packages\NUnit.2.6.4\lib\nunit.framework.dll + + ..\..\..\packages\NUnit.3.2.1\lib\net45\nunit.framework.dll @@ -48,9 +48,9 @@ - + {e34bcfa0-caa4-412c-aa1c-75db8d67d157} - Xamarin.Android.Tools + Xamarin.Android.Tools.AndroidSdk diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/packages.config b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/packages.config new file mode 100644 index 00000000000..482431befdc --- /dev/null +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/packages.config @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools/Xamarin.Android.Tools.csproj b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj similarity index 100% rename from external/xamarin-android-tools/src/Xamarin.Android.Tools/Xamarin.Android.Tools.csproj rename to external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools/Xamarin.Android.Tools.userprefs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.userprefs similarity index 100% rename from external/xamarin-android-tools/src/Xamarin.Android.Tools/Xamarin.Android.Tools.userprefs rename to external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.userprefs diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.Tests/packages.config b/external/xamarin-android-tools/src/Xamarin.Android.Tools.Tests/packages.config deleted file mode 100644 index e733ad565ae..00000000000 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.Tests/packages.config +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file From 07ca557c3bb30fd381b96bd26014e3de94532d79 Mon Sep 17 00:00:00 2001 From: Jonathan Pryor Date: Thu, 7 Sep 2017 17:41:08 -0400 Subject: [PATCH 018/308] Add AndroidVersions Commit [xamarin-android/8e7d37bb][xa8e] contains a sign of duplication: in order to use a `Mono.Android.dll` binding assembly, not only do we need to *build* the binding assembly, but we *also* need to update `Xamarin.Android.Build.Utilities.dll`/etc. to "know" about the new binding version (to map API level to `$(TargetFrameworkVersion)`). [xa8e]: https://github.com/xamarin/xamarin-android/commit/8e7d37bb Even "better" (worse), if the new API level is a *preview*, there is no *consistent* API level. For example, with API-O, `android.jar` isn't at: $(AndroidSdkDirectory)\platforms\android-@API_LEVEL@\android.jar where `@API_LEVEL@` is 26 (because various codepaths require that the "api level" be an integer). Instead, it's installed at: $(AndroidSdkDirectory)\platforms\android-O\android.jar where `O` is the "id" of the preview API level. This "id" is "leaky", in turn requiring that `Xamarin.Android.Build.Tasks.dll` *also* be updated to deal with the mappings. Even "better" (worse), if we *forget* to cross all our our 't's and dot all of our 'i's, we'll have a binding assembly which can't be used. (Which is why we needed [xamarin-android/8e7d37bb][xe8e]; without it, the API-O binding can't be used!) This is all obviously madness. ;-) [xamarin-android/8942eca0][xa89] contains a fix: [xa89]: https://github.com/xamarin/xamarin-android/commit/8942eca00a219882f48b5bccf11452f35ddbcaee 1. The build system is updated to create a new `AndroidApiInfo.xml` file within the `$(TargetFrameworkVersion)` directory. This contains all the information needed to map Android API levels to Ids and Android OS versions and `$(TargetFrameworkVersion)` values: ```xml 10 10 Gingerbread v2.3 ``` 2. Add a new `Xamarin.Android.Build.Utilities.AndroidVersions` type was added which parses the `AndroidApiInfo.xml` files. The advantage to all this is that we can support new API level bindings by just building a new `Mono.Android.dll` and placing an `AndroidApiInfo.xml` into the appropriate location (next to `Mono.Android.dll`). No further code changes would be required. This is all well and good...but it's not entirely useful if *only* Xamarin.Android makes use of this functionality. The IDEs also need to be updated to make use of these files. Copy over `Xamarin.Android.Build.Utilities.AndroidVersions` into the **xamarin-android-tools** repo as `Xamarin.Android.Tools.AndroidVersions`, so that we have a path for eventual IDE support of the new paradigm. --- .../AndroidSdk.cs | 48 ++-- .../AndroidVersion.cs | 134 +++++------ .../AndroidVersions.cs | 178 ++++++++++++++ .../Sdks/MonoDroidSdkBase.cs | 2 +- .../Tests/AndroidVersionTests.cs | 68 ++++++ .../Tests/AndroidVersionsTests.cs | 223 ++++++++++++++++++ ...arin.Android.Tools.AndroidSdk-Tests.csproj | 4 +- .../Xamarin.Android.Tools.AndroidSdk.csproj | 1 + 8 files changed, 548 insertions(+), 110 deletions(-) create mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidVersions.cs create mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/AndroidVersionTests.cs create mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/AndroidVersionsTests.cs diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidSdk.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidSdk.cs index 682295d957b..07c59ad08ac 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidSdk.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidSdk.cs @@ -20,13 +20,7 @@ public static void Refresh (string androidSdkPath = null, string androidNdkPath sdk.Initialize (androidSdkPath ?? sdk.PreferedAndroidSdkPath, androidNdkPath ?? sdk.PreferedAndroidNdkPath, javaSdkPath ?? sdk.PreferedJavaSdkPath); if (IsInstalled) { - var levels = GetInstalledPlatformVersions ().Select (l => l.ApiLevel.ToString ()).ToArray (); - string levelList; - if (levels == null || levels.Length == 0) - levelList = "(none)"; - else - levelList = string.Join (", ", levels); - AndroidLogger.LogInfo (null, "Found Android SDK. API levels: {0}", levelList); + AndroidLogger.LogInfo (null, "Found Android SDK."); } else { AndroidLogger.LogInfo (null, "Did not find Android SDK"); } @@ -80,13 +74,6 @@ static Version TryParseVersion (string v) return null; } - // it was useful when android-21 was android-L, or android-23 was android-MNC. - // We will use this when similar preview release is out. - static string ToApiName (int apiLevel) - { - return apiLevel.ToString (); - } - static string ValidatePath (string path) { if (String.IsNullOrEmpty (path)) @@ -96,30 +83,33 @@ static string ValidatePath (string path) public static string GetPlatformDirectory (int apiLevel) { - return Path.Combine (AndroidSdkPath, "platforms", "android-" + ToApiName (apiLevel)); + return GetPlatformDirectoryFromId (apiLevel.ToString ()); } - public static string GetPlatformDirectory (string osVersion) + public static string GetPlatformDirectoryFromId (string id) { - var level = AndroidVersion.TryOSVersionToApiLevel (osVersion); - if (level == 0) - return null; - return GetPlatformDirectory (level); + return Path.Combine (AndroidSdkPath, "platforms", "android-" + id); } - public static bool IsPlatformInstalled (int apiLevel) + public static string TryGetPlatformDirectoryFromApiLevel (string apiLevel, AndroidVersions versions) { - return apiLevel != 0 && Directory.Exists (GetPlatformDirectory (apiLevel)); + var id = versions.GetIdFromApiLevel (apiLevel); + var dir = GetPlatformDirectoryFromId (id); + + if (Directory.Exists (dir)) + return dir; + + var level = versions.GetApiLevelFromId (id); + dir = level.HasValue ? GetPlatformDirectory (level.Value) : null; + if (dir != null && Directory.Exists (dir)) + return dir; + + return null; } - public static IEnumerable GetInstalledPlatformVersions () + public static bool IsPlatformInstalled (int apiLevel) { - var knownAndInstalledSdkLevels = AndroidVersion.KnownVersions.Where (v => IsPlatformInstalled (v.ApiLevel)); - - return knownAndInstalledSdkLevels.Where (version => { - var apiLevel = MonoDroidSdk.GetApiLevelForFrameworkVersion (version.OSVersion); - return MonoDroidSdk.IsSupportedFrameworkLevel (apiLevel); - }); + return apiLevel != 0 && Directory.Exists (GetPlatformDirectory (apiLevel)); } public static bool IsInstalled { diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidVersion.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidVersion.cs index d54f4b25615..9805fc62c70 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidVersion.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidVersion.cs @@ -1,108 +1,84 @@ using System; +using System.IO; +using System.Xml.Linq; namespace Xamarin.Android.Tools { public class AndroidVersion { - public static readonly int MaxApiLevel = 24; + // Android API Level. *Usually* corresponds to $(AndroidSdkPath)/platforms/android-$(ApiLevel)/android.jar + public int ApiLevel { get; private set; } - public AndroidVersion (int apilevel, string osVersion) - { - this.ApiLevel = apilevel; - this.OSVersion = osVersion; - } + // Android API Level ID. == ApiLevel on stable versions, will be e.g. `N` for previews: $(AndroidSdkPath)/platforms/android-N/android.jar + public string Id { get; private set; } - AndroidVersion (int apilevel, string osVersion, string codeName, Version version) - { - this.ApiLevel = apilevel; - // TODO: remove osVersion from parameter list and generate from version - this.OSVersion = osVersion; - this.CodeName = codeName; - this.Version = version; - } + // Name of an Android release, e.g. "Oreo" + public string CodeName { get; private set; } - public int ApiLevel { get; private set; } - public string OSVersion { get; private set; } - public string CodeName { get; private set; } - public Version Version { get; private set; } + // Android version number, e.g. 8.0 + public string OSVersion { get; private set; } - public static int OSVersionToApiLevel (string osVersion) - { - int ret = TryOSVersionToApiLevel (osVersion); - if (ret == 0) - throw new ArgumentOutOfRangeException ("OS version not recognized: " + osVersion); - return ret; - } + // Xamarin.Android $(TargetFrameworkVersion) value, e.g. 8.0 + public Version TargetFrameworkVersion { get; private set; } - public static int TryOSVersionToApiLevel (string frameworkVersion) + // TargetFrameworkVersion *with* a leading `v`, e.g. "v8.0" + public string FrameworkVersion { get; private set; } + + // Is this API level stable? Should be False for non-numeric Id values. + public bool Stable { get; private set; } + + // Alternate Ids for a given API level. Allows for historical mapping, e.g. API-11 has alternate ID 'H'. + internal string[] AlternateIds { get; set; } + + public AndroidVersion (int apiLevel, string osVersion, string codeName = null, string id = null, bool stable = true) { - // Use MonoDroidSdk.GetApiLevelForFrameworkVersion because that will translate XA versions >= 5.xx to the correct api level - var apiLevelText = MonoDroidSdk.GetApiLevelForFrameworkVersion (frameworkVersion); - int apiLevel; - int.TryParse (apiLevelText, out apiLevel); - return apiLevel; + if (osVersion == null) + throw new ArgumentNullException (nameof (osVersion)); + + ApiLevel = apiLevel; + Id = id ?? ApiLevel.ToString (); + CodeName = codeName; + OSVersion = osVersion; + TargetFrameworkVersion = Version.Parse (osVersion); + FrameworkVersion = "v" + osVersion; + Stable = stable; } - public static string ApiLevelToOSVersion (int apiLevel) + public override string ToString () { - string ret = TryApiLevelToOSVersion (apiLevel); - if (ret == null) - throw new ArgumentOutOfRangeException ("API level not recognized: " + apiLevel); - return ret; + return $"(AndroidVersion: ApiLevel={ApiLevel} Id={Id} OSVersion={OSVersion} CodeName='{CodeName}' TargetFrameworkVersion={TargetFrameworkVersion} Stable={Stable})"; } - public static string TryApiLevelToOSVersion (int apiLevel) + public static AndroidVersion Load (Stream stream) { - var osVersion = MonoDroidSdk.GetFrameworkVersionForApiLevel (apiLevel.ToString ()); - if (!string.IsNullOrEmpty (osVersion)) - return osVersion.TrimStart ('v'); - return null; + var doc = XDocument.Load (stream); + return Load (doc); } - public static string TryOSVersionToCodeName (string frameworkVersion) + public static AndroidVersion Load (string uri) { - // match on API level, the framework version might not match what we have here (>= XA 5.x uses a different version scheme) - var apiLevel = TryOSVersionToApiLevel (frameworkVersion); - - foreach (AndroidVersion version in KnownVersions) - if (version.ApiLevel == apiLevel) - return version.CodeName; - return null; + var doc = XDocument.Load (uri); + return Load (doc); } - public static string TryFrameworkVersionToOSVersion (string frameworkVersion) + // Example: + // + // 26 + // 26 + // Oreo + // v8.0 + // True + // + static AndroidVersion Load (XDocument doc) { - // match on API level, the framework version might not match what we have here (>= XA 5.x uses a different version scheme) - var apiLevel = TryOSVersionToApiLevel (frameworkVersion); + var id = (string) doc.Root.Element ("Id"); + var level = (int) doc.Root.Element ("Level"); + var name = (string) doc.Root.Element ("Name"); + var version = (string) doc.Root.Element ("Version"); + var stable = (bool) doc.Root.Element ("Stable"); - foreach (AndroidVersion version in KnownVersions) - if (version.ApiLevel == apiLevel) - return version.OSVersion; - return null; + return new AndroidVersion (level, version.TrimStart ('v'), name, id, stable); } - - public static AndroidVersion[] KnownVersions = new[] { - new AndroidVersion (4, "1.6", "Donut", new Version (1, 6)), - new AndroidVersion (5, "2.0", "Eclair", new Version (2, 0)), - new AndroidVersion (6, "2.0.1", "Eclair", new Version (2, 0, 1)), - new AndroidVersion (7, "2.1", "Eclair", new Version (2, 1)), - new AndroidVersion (8, "2.2", "Froyo", new Version (2, 2)), - new AndroidVersion (10, "2.3", "Gingerbread", new Version (2, 3)), - new AndroidVersion (11, "3.0", "Honeycomb", new Version (3, 0)), - new AndroidVersion (12, "3.1", "Honeycomb", new Version (3, 1)), - new AndroidVersion (13, "3.2", "Honeycomb", new Version (3, 2)), - new AndroidVersion (14, "4.0", "Ice Cream Sandwich", new Version (4, 0)), - new AndroidVersion (15, "4.0.3", "Ice Cream Sandwich", new Version (4, 0, 3)), - new AndroidVersion (16, "4.1", "Jelly Bean", new Version (4, 1)), - new AndroidVersion (17, "4.2", "Jelly Bean", new Version (4, 2)), - new AndroidVersion (18, "4.3", "Jelly Bean", new Version (4, 3)), - new AndroidVersion (19, "4.4", "Kit Kat", new Version (4, 4)), - new AndroidVersion (20, "4.4.87", "Kit Kat + Wear support", new Version (4, 4, 87)), - new AndroidVersion (21, "5.0", "Lollipop", new Version (5, 0)), - new AndroidVersion (22, "5.1", "Lollipop", new Version (5, 1)), - new AndroidVersion (23, "6.0", "Marshmallow", new Version (6, 0)), - new AndroidVersion (24, "7.0", "Nougat", new Version (7, 0)), - }; } } diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidVersions.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidVersions.cs new file mode 100644 index 00000000000..bdcb736f9d0 --- /dev/null +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidVersions.cs @@ -0,0 +1,178 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.IO; +using System.Linq; +using System.Xml.Linq; + +namespace Xamarin.Android.Tools +{ + public class AndroidVersions + { + List installedVersions = new List (); + + public IReadOnlyList FrameworkDirectories { get; } + public AndroidVersion MaxStableVersion { get; private set; } + + public AndroidVersions (IEnumerable frameworkDirectories) + { + if (frameworkDirectories == null) + throw new ArgumentNullException (nameof (frameworkDirectories)); + + var dirs = new List (); + + foreach (var d in frameworkDirectories) { + if (!Directory.Exists (d)) + throw new ArgumentException ($"`{d}` must be a directory!", nameof (frameworkDirectories)); + + var dp = d.TrimEnd (Path.DirectorySeparatorChar); + var dn = Path.GetFileName (dp); + // In "normal" use, `dp` will contain e.g. `...\MonoAndroid\v1.0`. + // We want the `MonoAndroid` dir, not the versioned dir. + var p = dn.StartsWith ("v", StringComparison.Ordinal) ? Path.GetDirectoryName (dp) : dp; + dirs.Add (Path.GetFullPath (p)); + } + + dirs = dirs.Distinct (StringComparer.OrdinalIgnoreCase) + .ToList (); + + FrameworkDirectories = new ReadOnlyCollection (dirs); + + var versions = dirs.SelectMany (d => Directory.EnumerateFiles (d, "AndroidApiInfo.xml", SearchOption.AllDirectories)) + .Select (file => AndroidVersion.Load (file)); + + LoadVersions (versions); + } + + public AndroidVersions (IEnumerable versions) + { + if (versions == null) + throw new ArgumentNullException (nameof (versions)); + + FrameworkDirectories = new ReadOnlyCollection (new string [0]); + + LoadVersions (versions); + } + + void LoadVersions (IEnumerable versions) + { + foreach (var version in versions) { + installedVersions.Add (version); + if (MaxStableVersion == null || (version.Stable && MaxStableVersion.TargetFrameworkVersion < version.TargetFrameworkVersion)) { + MaxStableVersion = version; + } + } + } + + public int? GetApiLevelFromFrameworkVersion (string frameworkVersion) + { + return installedVersions.FirstOrDefault (v => v.FrameworkVersion == frameworkVersion)?.ApiLevel ?? + KnownVersions.FirstOrDefault (v => v.FrameworkVersion == frameworkVersion)?.ApiLevel; + } + + public int? GetApiLevelFromId (string id) + { + return installedVersions.FirstOrDefault (v => MatchesId (v, id))?.ApiLevel ?? + KnownVersions.FirstOrDefault (v => MatchesId (v, id))?.ApiLevel; + } + + static bool MatchesId (AndroidVersion version, string id) + { + return version.Id == id || + (version.AlternateIds?.Contains (id) ?? false) || + (version.ApiLevel.ToString () == id); + } + + public string GetIdFromApiLevel (int apiLevel) + { + return installedVersions.FirstOrDefault (v => v.ApiLevel == apiLevel)?.Id ?? + KnownVersions.FirstOrDefault (v => v.ApiLevel == apiLevel)?.Id; + } + + // Sometimes, e.g. when new API levels are introduced, the "API level" is a letter, not a number, + // e.g. 'API-H' for API-11, 'API-O' for API-26, etc. + public string GetIdFromApiLevel (string apiLevel) + { + if (int.TryParse (apiLevel, out var platform)) + return GetIdFromApiLevel (platform); + return installedVersions.FirstOrDefault (v => MatchesId (v, apiLevel))?.Id ?? + KnownVersions.FirstOrDefault (v => MatchesId (v, apiLevel))?.Id; + } + + public string GetIdFromFrameworkVersion (string frameworkVersion) + { + return installedVersions.FirstOrDefault (v => v.FrameworkVersion == frameworkVersion)?.Id ?? + KnownVersions.FirstOrDefault (v => v.FrameworkVersion == frameworkVersion)?.Id; + } + + public string GetFrameworkVersionFromApiLevel (int apiLevel) + { + return installedVersions.FirstOrDefault (v => v.ApiLevel == apiLevel)?.FrameworkVersion ?? + KnownVersions.FirstOrDefault (v => v.ApiLevel == apiLevel)?.FrameworkVersion; + } + + public string GetFrameworkVersionFromId (string id) + { + return installedVersions.FirstOrDefault (v => MatchesId (v, id))?.FrameworkVersion ?? + KnownVersions.FirstOrDefault (v => MatchesId (v, id))?.FrameworkVersion; + } + + static readonly AndroidVersion [] KnownVersions = new [] { + new AndroidVersion (4, "1.6", "Donut"), + new AndroidVersion (5, "2.0", "Eclair"), + new AndroidVersion (6, "2.0.1", "Eclair"), + new AndroidVersion (7, "2.1", "Eclair"), + new AndroidVersion (8, "2.2", "Froyo"), + new AndroidVersion (10, "2.3", "Gingerbread"), + new AndroidVersion (11, "3.0", "Honeycomb") { + AlternateIds = new[]{ "H" }, + }, + new AndroidVersion (12, "3.1", "Honeycomb"), + new AndroidVersion (13, "3.2", "Honeycomb"), + new AndroidVersion (14, "4.0", "Ice Cream Sandwich"), + new AndroidVersion (15, "4.0.3", "Ice Cream Sandwich"), + new AndroidVersion (16, "4.1", "Jelly Bean"), + new AndroidVersion (17, "4.2", "Jelly Bean"), + new AndroidVersion (18, "4.3", "Jelly Bean"), + new AndroidVersion (19, "4.4", "Kit Kat"), + new AndroidVersion (20, "4.4.87", "Kit Kat + Wear support"), + new AndroidVersion (21, "5.0", "Lollipop") { + AlternateIds = new[]{ "L" }, + }, + new AndroidVersion (22, "5.1", "Lollipop"), + new AndroidVersion (23, "6.0", "Marshmallow") { + AlternateIds = new[]{ "M" }, + }, + new AndroidVersion (24, "7.0", "Nougat") { + AlternateIds = new[]{ "N" }, + }, + new AndroidVersion (25, "7.1", "Nougat"), + new AndroidVersion (26, "8.0", "Oreo") { + AlternateIds = new[]{ "O" }, + }, + }; + } + + class EqualityComparer : IEqualityComparer + { + Func equals; + Func getHashCode; + + public EqualityComparer (Func equals, Func getHashCode = null) + { + this.equals = equals; + this.getHashCode = getHashCode ?? (v => v.GetHashCode ()); + } + + public bool Equals (T x, T y) + { + return equals (x, y); + } + + public int GetHashCode (T obj) + { + return getHashCode (obj); + } + } +} diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/MonoDroidSdkBase.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/MonoDroidSdkBase.cs index ed5ddd7ac28..309131aa034 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/MonoDroidSdkBase.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/MonoDroidSdkBase.cs @@ -242,7 +242,7 @@ void FindSupportedFrameworks () readonly Dictionary SupportedFrameworks = new Dictionary (); - static readonly Dictionary FrameworkToApiLevels = new Dictionary (AndroidVersion.KnownVersions.ToDictionary (k => k.Version, v => v.ApiLevel.ToString ())); + static readonly Dictionary FrameworkToApiLevels = new Dictionary (); static readonly Dictionary LegacyFrameworkToApiLevels = new Dictionary { { new Version (4, 5), "21" } // L Preview }; diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/AndroidVersionTests.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/AndroidVersionTests.cs new file mode 100644 index 00000000000..c162b5901be --- /dev/null +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/AndroidVersionTests.cs @@ -0,0 +1,68 @@ +using System; +using System.IO; +using System.Text; + +using NUnit.Framework; + +namespace Xamarin.Android.Tools.Tests +{ + [TestFixture] + public class AndroidVersionTests + { + [Test] + public void Constructor_Exceptions () + { + Assert.Throws (() => new AndroidVersion (0, null)); + Assert.Throws (() => new AndroidVersion (0, "not a number")); + } + + [Test] + public void Constructor () + { + var v = new AndroidVersion (apiLevel: 1, osVersion: "2.3", codeName: "Four", id: "E", stable: false); + Assert.AreEqual (1, v.ApiLevel); + Assert.AreEqual ("E", v.Id); + Assert.AreEqual ("Four", v.CodeName); + Assert.AreEqual ("2.3", v.OSVersion); + Assert.AreEqual (new Version (2, 3), v.TargetFrameworkVersion); + Assert.AreEqual ("v2.3", v.FrameworkVersion); + Assert.AreEqual (false, v.Stable); + } + + [Test] + public void Load_NoFile () + { + Assert.Throws (() => AndroidVersion.Load ((string) null)); + + var p = Path.GetTempFileName (); + File.Delete (p); + Assert.Throws (() => AndroidVersion.Load (p)); + } + + [Test] + public void Load_NoStream () + { + Assert.Throws (() => AndroidVersion.Load ((Stream) null)); + } + + [Test] + public void Load () + { + var xml = @" + O + 26 + Android O + v7.99.0 + False +"; + var v = AndroidVersion.Load (new MemoryStream (Encoding.UTF8.GetBytes (xml))); + Assert.AreEqual (26, v.ApiLevel); + Assert.AreEqual ("O", v.Id); + Assert.AreEqual ("Android O", v.CodeName); + Assert.AreEqual ("7.99.0", v.OSVersion); + Assert.AreEqual (new Version (7, 99, 0), v.TargetFrameworkVersion); + Assert.AreEqual ("v7.99.0", v.FrameworkVersion); + Assert.AreEqual (false, v.Stable); + } + } +} diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/AndroidVersionsTests.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/AndroidVersionsTests.cs new file mode 100644 index 00000000000..9d18c098237 --- /dev/null +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/AndroidVersionsTests.cs @@ -0,0 +1,223 @@ +using System; +using System.Collections.Generic; +using System.IO; + +using NUnit.Framework; + +namespace Xamarin.Android.Tools.Tests +{ + [TestFixture] + public class AndroidVersionsTests + { + [Test] + public void Constructor_Exceptions () + { + Assert.Throws (() => new AndroidVersions ((IEnumerable) null)); + Assert.Throws (() => new AndroidVersions ((IEnumerable) null)); + + var tempDir = Path.GetTempFileName (); + File.Delete (tempDir); + // Directory not found + Assert.Throws(() => new AndroidVersions (new[]{tempDir})); + } + + [Test] + public void Constructor_NoDirectories () + { + var versions = new AndroidVersions (new string [0]); + Assert.AreEqual (null, versions.MaxStableVersion); + Assert.IsNotNull (versions.FrameworkDirectories); + Assert.AreEqual (0, versions.FrameworkDirectories.Count); + } + + [Test] + public void Constructor_NoVersions () + { + var versions = new AndroidVersions (new AndroidVersion [0]); + Assert.AreEqual (null, versions.MaxStableVersion); + Assert.IsNotNull (versions.FrameworkDirectories); + Assert.AreEqual (0, versions.FrameworkDirectories.Count); + } + + [Test] + public void Constructor_FrameworkDirectories () + { + var frameworkDir = Path.GetTempFileName (); + File.Delete (frameworkDir); + Directory.CreateDirectory (frameworkDir); + try { + Directory.CreateDirectory (Path.Combine (frameworkDir, "MonoAndroid")); + Directory.CreateDirectory (Path.Combine (frameworkDir, "MonoAndroid", "v6.0")); + File.WriteAllLines (Path.Combine (frameworkDir, "MonoAndroid", "v6.0", "AndroidApiInfo.xml"), new []{ + "", + " 23", + " 23", + " Marshmallow", + " v6.0", + " True", + "", + }); + Directory.CreateDirectory (Path.Combine (frameworkDir, "MonoAndroid", "v8.0")); + File.WriteAllLines (Path.Combine (frameworkDir, "MonoAndroid", "v8.0", "AndroidApiInfo.xml"), new []{ + "", + " O", + " 26", + " Oreo", + " v8.0", + " False", + "", + }); + var versions = new AndroidVersions (new [] { Path.Combine (frameworkDir, "MonoAndroid", "v6.0") }); + Assert.IsNotNull (versions.FrameworkDirectories); + Assert.AreEqual (1, versions.FrameworkDirectories.Count); + Assert.AreEqual (Path.Combine (frameworkDir, "MonoAndroid"), versions.FrameworkDirectories [0]); + Assert.IsNotNull (versions.MaxStableVersion); + Assert.AreEqual (23, versions.MaxStableVersion.ApiLevel); + } + finally { + Directory.Delete (frameworkDir, recursive: true); + } + } + + [Test] + public void Constructor_Versions () + { + var versions = new AndroidVersions (new []{ + new AndroidVersion (apiLevel: 1, osVersion: "1.0", codeName: "One", id: "A", stable: true), + new AndroidVersion (apiLevel: 2, osVersion: "1.1", codeName: "One.One", id: "B", stable: true), + }); + Assert.IsNotNull (versions.FrameworkDirectories); + Assert.AreEqual (0, versions.FrameworkDirectories.Count); + Assert.IsNotNull (versions.MaxStableVersion); + Assert.AreEqual (2, versions.MaxStableVersion.ApiLevel); + } + + static AndroidVersions CreateTestVersions () + { + return new AndroidVersions (new []{ + new AndroidVersion (apiLevel: 1, osVersion: "1.0", id: "A", stable: true), + new AndroidVersion (apiLevel: 2, osVersion: "1.1", id: "B", stable: false), + new AndroidVersion (apiLevel: 3, osVersion: "1.2", id: "C", stable: true), + // Hides/shadows a Known Version + new AndroidVersion (apiLevel: 14, osVersion: "4.0", id: "II", stable: false), + }); + } + + [Test] + public void GetApiLevelFromFrameworkVersion () + { + var versions = CreateTestVersions (); + + Assert.AreEqual (null, versions.GetApiLevelFromFrameworkVersion (null)); + Assert.AreEqual (1, versions.GetApiLevelFromFrameworkVersion ("v1.0")); + Assert.AreEqual (2, versions.GetApiLevelFromFrameworkVersion ("v1.1")); + Assert.AreEqual (3, versions.GetApiLevelFromFrameworkVersion ("v1.2")); + Assert.AreEqual (null, versions.GetApiLevelFromFrameworkVersion ("v1.3")); + Assert.AreEqual (14, versions.GetApiLevelFromFrameworkVersion ("v4.0")); + + // via KnownVersions + Assert.AreEqual (4, versions.GetApiLevelFromFrameworkVersion ("v1.6")); + } + + [Test] + public void GetApiLevelFromId () + { + var versions = CreateTestVersions (); + + Assert.AreEqual (null, versions.GetApiLevelFromId (null)); + Assert.AreEqual (1, versions.GetApiLevelFromId ("A")); + Assert.AreEqual (1, versions.GetApiLevelFromId ("1")); + Assert.AreEqual (2, versions.GetApiLevelFromId ("B")); + Assert.AreEqual (2, versions.GetApiLevelFromId ("2")); + Assert.AreEqual (3, versions.GetApiLevelFromId ("C")); + Assert.AreEqual (3, versions.GetApiLevelFromId ("3")); + Assert.AreEqual (14, versions.GetApiLevelFromId ("14")); + Assert.AreEqual (14, versions.GetApiLevelFromId ("II")); + + Assert.AreEqual (null, versions.GetApiLevelFromId ("D")); + + // via KnownVersions + Assert.AreEqual (11, versions.GetApiLevelFromId ("H")); + } + + [Test] + public void GetIdFromApiLevel () + { + var versions = CreateTestVersions (); + + Assert.AreEqual (null, versions.GetIdFromApiLevel (null)); + Assert.AreEqual ("A", versions.GetIdFromApiLevel (1)); + Assert.AreEqual ("A", versions.GetIdFromApiLevel ("1")); + Assert.AreEqual ("A", versions.GetIdFromApiLevel ("A")); + Assert.AreEqual ("B", versions.GetIdFromApiLevel (2)); + Assert.AreEqual ("B", versions.GetIdFromApiLevel ("2")); + Assert.AreEqual ("B", versions.GetIdFromApiLevel ("B")); + Assert.AreEqual ("C", versions.GetIdFromApiLevel (3)); + Assert.AreEqual ("C", versions.GetIdFromApiLevel ("3")); + Assert.AreEqual ("C", versions.GetIdFromApiLevel ("C")); + Assert.AreEqual ("II", versions.GetIdFromApiLevel ("14")); + Assert.AreEqual ("II", versions.GetIdFromApiLevel ("II")); + + Assert.AreEqual (null, versions.GetIdFromApiLevel (-1)); + Assert.AreEqual (null, versions.GetIdFromApiLevel ("-1")); + Assert.AreEqual (null, versions.GetIdFromApiLevel ("D")); + + // via KnownVersions + Assert.AreEqual ("11", versions.GetIdFromApiLevel (11)); + Assert.AreEqual ("11", versions.GetIdFromApiLevel ("11")); + Assert.AreEqual ("11", versions.GetIdFromApiLevel ("H")); + } + + [Test] + public void GetIdFromFrameworkVersion () + { + var versions = CreateTestVersions (); + + Assert.AreEqual (null, versions.GetIdFromFrameworkVersion (null)); + Assert.AreEqual ("A", versions.GetIdFromFrameworkVersion ("v1.0")); + Assert.AreEqual ("B", versions.GetIdFromFrameworkVersion ("v1.1")); + Assert.AreEqual ("C", versions.GetIdFromFrameworkVersion ("v1.2")); + Assert.AreEqual ("II", versions.GetIdFromFrameworkVersion ("v4.0")); + + Assert.AreEqual (null, versions.GetIdFromFrameworkVersion ("0.99")); + + // via KnownVersions + Assert.AreEqual ("10", versions.GetIdFromFrameworkVersion ("v2.3")); + } + + [Test] + public void GetFrameworkVersionFromApiLevel () + { + var versions = CreateTestVersions (); + + Assert.AreEqual (null, versions.GetFrameworkVersionFromApiLevel (0)); + Assert.AreEqual ("v1.0", versions.GetFrameworkVersionFromApiLevel (1)); + Assert.AreEqual ("v1.1", versions.GetFrameworkVersionFromApiLevel (2)); + Assert.AreEqual ("v1.2", versions.GetFrameworkVersionFromApiLevel (3)); + Assert.AreEqual ("v4.0", versions.GetFrameworkVersionFromApiLevel (14)); + + // via KnownVersions + Assert.AreEqual ("v2.3", versions.GetFrameworkVersionFromApiLevel (10)); + } + + [Test] + public void GetFrameworkVersionFromId () + { + var versions = CreateTestVersions (); + + Assert.AreEqual (null, versions.GetFrameworkVersionFromId (null)); + Assert.AreEqual ("v1.0", versions.GetFrameworkVersionFromId ("1")); + Assert.AreEqual ("v1.0", versions.GetFrameworkVersionFromId ("A")); + Assert.AreEqual ("v1.1", versions.GetFrameworkVersionFromId ("2")); + Assert.AreEqual ("v1.1", versions.GetFrameworkVersionFromId ("B")); + Assert.AreEqual ("v1.2", versions.GetFrameworkVersionFromId ("3")); + Assert.AreEqual ("v1.2", versions.GetFrameworkVersionFromId ("C")); + Assert.AreEqual ("v4.0", versions.GetFrameworkVersionFromId ("14")); + Assert.AreEqual ("v4.0", versions.GetFrameworkVersionFromId ("II")); + + // via KnownVersions + Assert.AreEqual ("v3.0", versions.GetFrameworkVersionFromId ("11")); + Assert.AreEqual ("v3.0", versions.GetFrameworkVersionFromId ("H")); + } + } +} diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/Xamarin.Android.Tools.AndroidSdk-Tests.csproj b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/Xamarin.Android.Tools.AndroidSdk-Tests.csproj index c267315f4ee..10c58b9b80b 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/Xamarin.Android.Tools.AndroidSdk-Tests.csproj +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/Xamarin.Android.Tools.AndroidSdk-Tests.csproj @@ -1,5 +1,5 @@  - + Debug @@ -41,6 +41,8 @@ + + diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj index 3e36d882b7d..8c484c1bd3a 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj @@ -40,6 +40,7 @@ + From 50ac399979b1e5624f2cf04dbd7b3b1204b0e553 Mon Sep 17 00:00:00 2001 From: Jonathan Pryor Date: Fri, 8 Sep 2017 11:47:45 -0400 Subject: [PATCH 019/308] Remove MonoDroidSdk (#7) [xamarin-android/8942eca0][xa89] removed `MonoDroidSdk`, as it was no longer necessary; `AndroidVersions` (07ca557c) largely replaced it. [xa89]: https://github.com/xamarin/xamarin-android/commit/8942eca00a219882f48b5bccf11452f35ddbcaee Remove `MonoDroidSdk` from `Xamarin.Android.Tools.AndroidSdk`. --- .../MonoDroidSdk.cs | 60 ---- .../Sdks/MonoDroidSdkBase.cs | 292 ------------------ .../Sdks/MonoDroidSdkUnix.cs | 103 ------ .../Sdks/MonoDroidSdkWindows.cs | 89 ------ .../Tests/MonoDroidSdkTests.cs | 69 ----- ...arin.Android.Tools.AndroidSdk-Tests.csproj | 1 - .../Xamarin.Android.Tools.AndroidSdk.csproj | 4 - 7 files changed, 618 deletions(-) delete mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/MonoDroidSdk.cs delete mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/MonoDroidSdkBase.cs delete mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/MonoDroidSdkUnix.cs delete mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/MonoDroidSdkWindows.cs delete mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/MonoDroidSdkTests.cs diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/MonoDroidSdk.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/MonoDroidSdk.cs deleted file mode 100644 index e7f62ccdf07..00000000000 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/MonoDroidSdk.cs +++ /dev/null @@ -1,60 +0,0 @@ -using System; -using System.IO; - -namespace Xamarin.Android.Tools -{ - public static class MonoDroidSdk - { - static MonoDroidSdkBase sdk; - - public static string GetApiLevelForFrameworkVersion (string framework) - { - return GetSdk ().GetApiLevelForFrameworkVersion (framework); - } - - public static string GetFrameworkVersionForApiLevel (string apiLevel) - { - return GetSdk ().GetFrameworkVersionForApiLevel (apiLevel); - } - - public static bool IsSupportedFrameworkLevel (string apiLevel) - { - return GetSdk ().IsSupportedFrameworkLevel (apiLevel); - } - - public static void Refresh (string runtimePath = null, string binPath = null, string bclPath = null) - { - if (OS.IsWindows) { - sdk = new MonoDroidSdkWindows (); - } else { - sdk = new MonoDroidSdkUnix (); - } - - try { - sdk.Initialize (runtimePath, binPath, bclPath); - } catch (Exception ex) { - AndroidLogger.LogError ("Error finding Xamarin.Android SDK", ex); - } - } - - static MonoDroidSdkBase GetSdk () - { - if (sdk == null) { - Refresh (); - } - return sdk; - } - - public static string RuntimePath { get { return GetSdk ().RuntimePath; } } - - public static string BinPath { get { return GetSdk ().BinPath; } } - - public static string FrameworkPath { get { return GetSdk ().BclPath; } } - - [Obsolete ("Do not use.")] - public static string JavaDocToMDocExe { - get { return Path.Combine (BinPath, OS.IsWindows ? "javadoc-to-mdoc.exe" : "javadoc-to-mdoc"); } - } - } -} - diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/MonoDroidSdkBase.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/MonoDroidSdkBase.cs deleted file mode 100644 index 309131aa034..00000000000 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/MonoDroidSdkBase.cs +++ /dev/null @@ -1,292 +0,0 @@ -using System; -using System.Linq; -using System.IO; -using System.Collections.Generic; -using System.Xml; - -namespace Xamarin.Android.Tools -{ - abstract class MonoDroidSdkBase - { - protected readonly static string DebugRuntime = "Mono.Android.DebugRuntime-debug.apk"; - protected readonly static string ClassParseExe = "class-parse.exe"; - protected readonly static string GeneratorScript = "generator"; - - // I can never remember the difference between SdkPath and anything else... - [Obsolete ("Do not use.")] - public string SdkPath { get; private set; } - - // Contains mandroid - public string BinPath { get; private set; } - - // Not actually shipped... - public string IncludePath { get; private set; } - - // Contains Mono.Android.DebugRuntime-*.apk, platforms/*/*.apk. - public string RuntimePath { get; private set; } - - // Root directory for XA libraries, contains designer dependencies - public string LibrariesPath { get; private set; } - - // Contains mscorlib.dll - public string BclPath { get; private set; } - - public int SharedRuntimeVersion { get; private set; } - - // expectedRuntimePath: contains Mono.Android.DebugRuntime-*.apk - // binPath: contains mandroid - // mscorlibDir: contains mscorlib.dll - public void Initialize (string expectedRuntimePath = null, string binPath = null, string bclPath = null) - { - var runtimePath = GetValidPath ("MonoAndroidToolsPath", expectedRuntimePath, ValidateRuntime, () => FindRuntime ()); - if (runtimePath != null) { - binPath = GetValidPath ("MonoAndroidBinPath", binPath, ValidateBin, () => FindBin (runtimePath)); - bclPath = GetValidPath ("mscorlib.dll", bclPath, ValidateFramework, () => FindFramework (runtimePath)); - } else { - if (expectedRuntimePath != null) - AndroidLogger.LogWarning (null, "Runtime was not found at {0}", expectedRuntimePath); - binPath = bclPath = null; - } - - if (runtimePath == null || binPath == null || bclPath == null) { - Reset (); - return; - } - - RuntimePath = runtimePath; - #pragma warning disable 0618 - SdkPath = Path.GetFullPath (Path.Combine (runtimePath, "..", "..")); - #pragma warning restore 0618 - BinPath = binPath; - BclPath = bclPath; - LibrariesPath = FindLibraries (runtimePath); - - IncludePath = FindInclude (runtimePath); - if (IncludePath != null && !Directory.Exists (IncludePath)) - IncludePath = null; - - SharedRuntimeVersion = GetCurrentSharedRuntimeVersion (); - FindSupportedFrameworks (); - } - - static string GetValidPath (string description, string path, Func validator, Func defaultPath) - { - if (!string.IsNullOrEmpty (path)) { - if (Directory.Exists (path)) { - if (validator (path)) - return path; - AndroidLogger.LogWarning (null, "{0} path '{1}' is explicitly specified, but it was not valid; skipping.", description, path); - } else - AndroidLogger.LogWarning (null, "{0} path '{1}' is explicitly specified, but it was not found; skipping.", description, path); - } - path = defaultPath (); - if (path != null && validator (path)) - return path; - if (path != null) - AndroidLogger.LogWarning (null, "{0} path is defaulted to '{1}', but it was not valid; skipping", description, path); - else - AndroidLogger.LogWarning (null, "{0} path is not found and no default location is provided; skipping", description); - return null; - } - - public void Reset () - { - #pragma warning disable 0618 - SdkPath = BinPath = IncludePath = RuntimePath = BclPath = null; - #pragma warning restore 0618 - SharedRuntimeVersion = 0; - } - - protected abstract string FindRuntime (); - protected abstract string FindFramework (string runtimePath); - - // Check for platform-specific `mandroid` name - protected abstract bool ValidateBin (string binPath); - - protected static bool ValidateRuntime (string loc) - { - return !string.IsNullOrWhiteSpace (loc) && - (File.Exists (Path.Combine (loc, DebugRuntime)) || // Normal/expected - File.Exists (Path.Combine (loc, ClassParseExe)) || // Normal/expected - File.Exists (Path.Combine (loc, "Xamarin.Android.Common.targets"))); //VS on Windows - } - - protected static bool ValidateFramework (string loc) - { - return loc != null && File.Exists (Path.Combine (loc, "mscorlib.dll")); - } - - public string FindVersionFile () - { - #pragma warning disable 0618 - if (string.IsNullOrEmpty (SdkPath)) - return null; - #pragma warning restore 0618 - foreach (var loc in GetVersionFileLocations ()) { - if (File.Exists (loc)) { - return loc; - } - } - return null; - } - - protected virtual IEnumerable GetVersionFileLocations () - { - #pragma warning disable 0618 - yield return Path.Combine (SdkPath, "Version"); - #pragma warning restore 0618 - } - - protected abstract string FindBin (string runtimePath); - - protected abstract string FindInclude (string runtimePath); - - protected abstract string FindLibraries (string runtimePath); - - [Obsolete ("Do not use.")] - public string GetPlatformNativeLibPath (string abi) - { - return FindPlatformNativeLibPath (SdkPath, abi); - } - - [Obsolete ("Do not use.")] - public string GetPlatformNativeLibPath (AndroidTargetArch arch) - { - return FindPlatformNativeLibPath (SdkPath, GetMonoDroidArchName (arch)); - } - - [Obsolete ("Do not use.")] - static string GetMonoDroidArchName (AndroidTargetArch arch) - { - switch (arch) { - case AndroidTargetArch.Arm: - return "armeabi"; - case AndroidTargetArch.Mips: - return "mips"; - case AndroidTargetArch.X86: - return "x86"; - } - return null; - } - - [Obsolete] - protected string FindPlatformNativeLibPath (string sdk, string arch) - { - return Path.Combine (sdk, "lib", arch); - } - - static XmlReaderSettings GetSafeReaderSettings () - { - //allow DTD but not try to resolve it from web - return new XmlReaderSettings { - CloseInput = true, - DtdProcessing = DtdProcessing.Ignore, - XmlResolver = null, - }; - } - - int GetCurrentSharedRuntimeVersion () - { - string file = Path.Combine (RuntimePath, "Mono.Android.DebugRuntime-debug.xml"); - - return GetManifestVersion (file); - } - - internal static int GetManifestVersion (string file) - { - // It seems that MfA 1.0 on Windows didn't include the xml files to get the runtime version. - if (!File.Exists (file)) - return int.MaxValue; - - try { - using (var r = XmlReader.Create (file, GetSafeReaderSettings())) { - if (r.MoveToContent () == XmlNodeType.Element && r.MoveToAttribute ("android:versionCode")) { - int value; - if (int.TryParse (r.Value, out value)) - return value; - AndroidLogger.LogInfo ("Cannot parse runtime version code: ({0})", r.Value); - } - } - } catch (Exception ex) { - AndroidLogger.LogError ("Error trying to find shared runtime version", ex); - } - return int.MaxValue; - } - - internal static Version ToVersion (string frameworkDir) - { - string version = Path.GetFileName (frameworkDir); - if (!version.StartsWith ("v", StringComparison.OrdinalIgnoreCase)) { - // wat? - return new Version (); - } - version = version.Substring (1); - Version v; - if (Version.TryParse (version, out v)) - return v; - return new Version (); - } - - void FindSupportedFrameworks () - { - string bclDir = MonoDroidSdk.FrameworkPath; - string frameworksDir = Path.GetDirectoryName (bclDir); - foreach (var framework in Directory.EnumerateDirectories (frameworksDir).Select (ToVersion)) { - if (framework.Major == 0) - continue; - string apiLevel; - if (FrameworkToApiLevels.TryGetValue (framework, out apiLevel)) - SupportedFrameworks.Add (framework, apiLevel); - } - } - - readonly Dictionary SupportedFrameworks = new Dictionary (); - - static readonly Dictionary FrameworkToApiLevels = new Dictionary (); - static readonly Dictionary LegacyFrameworkToApiLevels = new Dictionary { - { new Version (4, 5), "21" } // L Preview - }; - - public IEnumerable GetSupportedApiLevels () - { - return SupportedFrameworks.Select (e => e.Value); - } - - public string GetApiLevelForFrameworkVersion (string framework) - { - Version v; - if (!Version.TryParse (framework.TrimStart ('v'), out v)) - return null; - string apiLevel; - if (SupportedFrameworks.TryGetValue (v, out apiLevel) - || FrameworkToApiLevels.TryGetValue (v, out apiLevel) - || LegacyFrameworkToApiLevels.TryGetValue (v, out apiLevel)) - return apiLevel; - return null; - } - - public string GetFrameworkVersionForApiLevel (string apiLevel) - { - // API level 9 was discontinued immediately for 10, in the rare case we get it just upgrade the number - if (apiLevel == "9") - apiLevel = "10"; - var maxFrameworkVersion = SupportedFrameworks.Concat (FrameworkToApiLevels) - .Where (e => e.Value == apiLevel) - .OrderByDescending (e => e.Key, Comparer.Default) - .Select (e => e.Key) - .FirstOrDefault (); - if (maxFrameworkVersion != null) - return "v" + maxFrameworkVersion; - return null; - } - - /// - /// Determines if the given apiLevel is supported by an installed Framework - /// - public bool IsSupportedFrameworkLevel (string apiLevel) - { - return SupportedFrameworks.Any ((sf => sf.Value == apiLevel)); - } - } -} - diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/MonoDroidSdkUnix.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/MonoDroidSdkUnix.cs deleted file mode 100644 index 19e6bbea707..00000000000 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/MonoDroidSdkUnix.cs +++ /dev/null @@ -1,103 +0,0 @@ -using System; -using System.IO; -using System.Linq; -using System.Collections.Generic; - -namespace Xamarin.Android.Tools -{ - class MonoDroidSdkUnix : MonoDroidSdkBase - { - readonly static string[] RuntimeToFrameworkPaths = new[]{ - Path.Combine ("..", "..", "..", ".xamarin.android", "lib", "xbuild-frameworks", "MonoAndroid"), - Path.Combine ("..", "xbuild-frameworks", "MonoAndroid"), - Path.Combine ("..", "mono", "2.1"), - }; - - readonly static string[] SearchPaths = { - "/Library/Frameworks/Xamarin.Android.framework/Versions/Current/lib/mandroid", - "/Developer/MonoAndroid/usr/lib/mandroid", - "/app/lib/mandroid", - "/opt/mono-android/lib/mandroid" - }; - - protected override string FindRuntime () - { - string monoAndroidPath = Environment.GetEnvironmentVariable ("MONO_ANDROID_PATH"); - if (!string.IsNullOrEmpty (monoAndroidPath)) { - string libMandroid = Path.Combine (monoAndroidPath, "lib", "mandroid"); - if (Directory.Exists (libMandroid)) { - if (ValidateRuntime (libMandroid)) - return libMandroid; - AndroidLogger.LogInfo (null, "MONO_ANDROID_PATH points to {0}, but it is invalid.", monoAndroidPath); - } else - AndroidLogger.LogInfo (null, "MONO_ANDROID_PATH points to {0}, but it does not exist.", monoAndroidPath); - } - - // check also in the users folder - var personal = Environment.GetFolderPath (Environment.SpecialFolder.Personal); - var additionalSearchPaths = new [] { - // for Mono.Posix and Mono.Data.Sqlite builds in xamarin-android. - monoAndroidPath = Path.GetFullPath (Path.Combine (new Uri (GetType ().Assembly.CodeBase).LocalPath, "..", "..", "..", "..", "..", "lib", "mandroid")), - Path.Combine (personal, @".xamarin.android/lib/mandroid") - }; - - return additionalSearchPaths.Concat (SearchPaths).FirstOrDefault (ValidateRuntime); - } - - protected override bool ValidateBin (string binPath) - { - return !string.IsNullOrWhiteSpace (binPath) && - File.Exists (Path.Combine (binPath, GeneratorScript)); - } - - protected override string FindFramework (string runtimePath) - { - foreach (var relativePath in RuntimeToFrameworkPaths) { - var fullPath = Path.GetFullPath (Path.Combine (runtimePath, relativePath)); - if (Directory.Exists (fullPath)) { - if (ValidateFramework (fullPath)) - return fullPath; - - // check to see if full path is the folder that contains each framework version, eg contains folders of the form v1.0, v2.3 etc - var subdirs = Directory.GetDirectories (fullPath, "v*").OrderBy (x => x).ToArray (); - foreach (var subdir in subdirs) { - if (ValidateFramework (subdir)) - return subdir; - } - } - } - - return null; - } - - protected override string FindBin (string runtimePath) - { - string binPath = Path.GetFullPath (Path.Combine (runtimePath, "..", "..", "bin")); - if (File.Exists (Path.Combine (binPath, GeneratorScript))) - return binPath; - return null; - } - - protected override string FindInclude (string runtimePath) - { - string includeDir = Path.GetFullPath (Path.Combine (runtimePath, "..", "..", "include")); - if (Directory.Exists (includeDir)) - return includeDir; - return null; - } - - protected override string FindLibraries (string runtimePath) - { - return Path.GetFullPath (Path.Combine (runtimePath, "..")); - } - - protected override IEnumerable GetVersionFileLocations () - { - yield return Path.GetFullPath (Path.Combine (RuntimePath, "..", "..", "Version")); - string sdkPath = Path.GetDirectoryName (Path.GetDirectoryName (RuntimePath)); - if (Path.GetFileName (sdkPath) == "usr") - yield return Path.GetFullPath (Path.Combine (Path.GetDirectoryName (sdkPath), "Version")); - } - } -} - diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/MonoDroidSdkWindows.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/MonoDroidSdkWindows.cs deleted file mode 100644 index 60eaeddfe28..00000000000 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/MonoDroidSdkWindows.cs +++ /dev/null @@ -1,89 +0,0 @@ -using System; -using System.IO; -using System.Collections.Generic; -using System.Linq; - -namespace Xamarin.Android.Tools -{ - class MonoDroidSdkWindows : MonoDroidSdkBase - { - static readonly string[] VisualStudioPaths = new[] { - Environment.GetEnvironmentVariable ("VSINSTALLDIR"), - Path.Combine (OS.ProgramFilesX86, "Microsoft Visual Studio", "2017", "Enterprise"), - Path.Combine (OS.ProgramFilesX86, "Microsoft Visual Studio", "2017", "Professional"), - Path.Combine (OS.ProgramFilesX86, "Microsoft Visual Studio", "2017", "Community"), - Path.Combine (OS.ProgramFilesX86), //VS older than 2017, MSBuild\Xamarin\Android is located in C:\Program Files (x86)\ - }; - - protected override string FindRuntime () - { - string monoAndroidPath = Environment.GetEnvironmentVariable ("MONO_ANDROID_PATH"); - if (!string.IsNullOrEmpty (monoAndroidPath)) { - if (Directory.Exists (monoAndroidPath) && ValidateRuntime (monoAndroidPath)) - return monoAndroidPath; - } - - foreach (var vsPath in VisualStudioPaths) { - if (string.IsNullOrEmpty (vsPath)) - continue; - var xamarinSdk = Path.Combine (vsPath, "MSBuild", "Xamarin", "Android"); - if (Directory.Exists (xamarinSdk) && ValidateRuntime (xamarinSdk)) - return xamarinSdk; - } - return OS.ProgramFilesX86 + @"\MSBuild\Novell"; - } - - static readonly string[] RuntimeToFrameworkPaths = new []{ - Path.Combine ("..", "..", "..", "Common7", "IDE", "ReferenceAssemblies", "Microsoft", "Framework","MonoAndroid"), - Path.Combine ("..", "..", "..", "Reference Assemblies", "Microsoft", "Framework", "MonoAndroid"), - Path.Combine (OS.ProgramFilesX86, "Reference Assemblies", "Microsoft", "Framework", "MonoAndroid"), - }; - - protected override string FindFramework (string runtimePath) - { - foreach (var relativePath in RuntimeToFrameworkPaths) { - var fullPath = Path.GetFullPath (Path.Combine (runtimePath, relativePath)); - if (Directory.Exists (fullPath)) { - if (ValidateFramework (fullPath)) - return fullPath; - - // check to see if full path is the folder that contains each framework version, eg contains folders of the form v1.0, v2.3 etc - var subdirs = Directory.GetDirectories (fullPath, "v*").OrderBy (x => x).ToArray (); - foreach (var subdir in subdirs) { - if (ValidateFramework (subdir)) - return subdir; - } - } - } - - return null; - } - - protected override string FindBin (string runtimePath) - { - return runtimePath; - } - - protected override bool ValidateBin (string binPath) - { - return !string.IsNullOrWhiteSpace (binPath) && - File.Exists (Path.Combine (binPath, "generator.exe")); - } - - protected override string FindInclude (string runtimePath) - { - return Path.GetFullPath (Path.Combine (runtimePath, "include")); - } - - protected override string FindLibraries (string runtimePath) - { - return Path.GetFullPath (runtimePath); - } - - protected override IEnumerable GetVersionFileLocations () - { - yield return Path.GetFullPath (Path.Combine (RuntimePath, "Version")); - } - } -} - diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/MonoDroidSdkTests.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/MonoDroidSdkTests.cs deleted file mode 100644 index 68299a31ebe..00000000000 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/MonoDroidSdkTests.cs +++ /dev/null @@ -1,69 +0,0 @@ -using NUnit.Framework; -using System.Diagnostics; -using System.IO; - -namespace Xamarin.Android.Tools.Tests -{ - [TestFixture] - class MonoDroidSdkTests - { - [TestFixtureSetUp] - public void FixtureSetUp () - { - AndroidLogger.Info += OnInfo; - AndroidLogger.Warning += OnWarning; - AndroidLogger.Error += OnError; - } - - [TestFixtureTearDown] - public void FixtureTearDown () - { - AndroidLogger.Info -= OnInfo; - AndroidLogger.Warning -= OnWarning; - AndroidLogger.Error -= OnError; - } - - void OnInfo (string task, string message) - { - Debug.WriteLine (task + ": " + message); - } - - void OnWarning (string task, string message) - { - Assert.Fail (task + ": " + message); - } - - void OnError (string task, string message) - { - Assert.Fail (task + ": " + message); - } - - [Test] - public void RefreshWithoutParameters () - { - //Just checking for exceptions, or AndroidLogger - MonoDroidSdk.Refresh (); - } - - [Test] - public void BinPathExists () - { - string path = MonoDroidSdk.BinPath; - Assert.IsTrue (Directory.Exists (path), path + " does not exist!"); - } - - [Test] - public void FrameworkPathExists () - { - string path = MonoDroidSdk.FrameworkPath; - Assert.IsTrue (Directory.Exists (path), path + " does not exist!"); - } - - [Test] - public void RuntimePathExists () - { - string path = MonoDroidSdk.RuntimePath; - Assert.IsTrue (Directory.Exists (path), path + " does not exist!"); - } - } -} diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/Xamarin.Android.Tools.AndroidSdk-Tests.csproj b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/Xamarin.Android.Tools.AndroidSdk-Tests.csproj index 10c58b9b80b..6ae53e33175 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/Xamarin.Android.Tools.AndroidSdk-Tests.csproj +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/Xamarin.Android.Tools.AndroidSdk-Tests.csproj @@ -43,7 +43,6 @@ - diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj index 8c484c1bd3a..34544e712e6 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj @@ -38,13 +38,9 @@ - - - - From 0c850ee456915b4bb58dc908d04500c0d9a0851b Mon Sep 17 00:00:00 2001 From: Jonathan Pryor Date: Fri, 8 Sep 2017 13:14:54 -0400 Subject: [PATCH 020/308] Rename AndroidSdk to AndroidSdkInfo, make non-static (#8) There are multiple `AndroidSdk` types running around across various assemblies, so that name is problematic. Additionally, it's `static`-oriented nature is *also* problematic: There Can Be Only One bit of `AndroidSdk` info, which means it needs to support an "invalid" state (no Android SDK or Java SDK available). Then there are initialization concerns (who does the initialization?), thread safey concens (*who does the initialization* and *when*?!), sanity concerns ("what's going on?!"), error handling, etc. Rethink all this. Instead of a static `AndroidSdk` class, have an `AndroidSdkInfo` class which contains *instance* members, not static member. "Someone else" can deal with the initialization issues, and if something goes wrong -- no Android SDK found? -- then we can sanely thrown an exception at construction time instead of swallowing the problem and logging it for later perusal. Additionally, remove the `AndroidLogger` type. This is another source of shared state/threading concerns (who initializes it? when?), which is more simply handled by using an `Action` delegate. See also, [Java.Interop/ab3c2b26][jia], which introduced the `Action` construct. [jia]: https://github.com/xamarin/Java.Interop/commit/ab3c2b2679d986ed3036c4cfb492f8c8b33657db --- .../AndroidLogger.cs | 129 ------------------ .../{AndroidSdk.cs => AndroidSdkInfo.cs} | 75 ++++------ .../Sdks/AndroidSdkBase.cs | 12 ++ .../Sdks/AndroidSdkUnix.cs | 15 +- .../Sdks/AndroidSdkWindows.cs | 22 +-- .../Tests/AndroidSdkInfoTests.cs | 19 +++ ...arin.Android.Tools.AndroidSdk-Tests.csproj | 1 + .../Xamarin.Android.Tools.AndroidSdk.csproj | 3 +- 8 files changed, 87 insertions(+), 189 deletions(-) delete mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidLogger.cs rename external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/{AndroidSdk.cs => AndroidSdkInfo.cs} (54%) create mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/AndroidSdkInfoTests.cs diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidLogger.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidLogger.cs deleted file mode 100644 index 5abad04e483..00000000000 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidLogger.cs +++ /dev/null @@ -1,129 +0,0 @@ -using System; - -namespace Xamarin.Android.Tools -{ - public delegate void MessageHandler (string task, string message); - public delegate void TaskLogHandler (AndroidTaskLog log); - - public static class AndroidLogger - { - public static event MessageHandler Info; - public static event MessageHandler Warning; - public static event MessageHandler Error; - public static event MessageHandler Debug; - public static event TaskLogHandler Task; - - public static void LogInfo (string format, params object[] args) - { - LogInfo (string.Empty, format, args); - } - - public static void LogInfo (string task, string format, params object[] args) - { - if (Info != null) { - if (args == null || args.Length == 0) - Info (task, format); - else - Info (task, String.Format (format, args)); - } else - throw new InvalidOperationException ("Internal Error: should initialize Info"); - } - - public static void LogWarning (string format, params object[] args) - { - LogWarning (string.Empty, format, args); - } - - public static void LogWarning (string task, string format, params object[] args) - { - if (Warning != null) { - if (args == null) - Warning (task, format); - else - Warning (task, String.Format (format, args)); - } else - throw new InvalidOperationException ("Internal Error: should initialize Warning"); - } - - public static void LogError (string format, params object[] args) - { - LogError (string.Empty, format, args); - } - - public static void LogError (string message, Exception ex) - { - message += (ex != null? System.Environment.NewLine + ex.ToString () : string.Empty); - LogError (message); - } - - public static void LogError (string task, string format, params object[] args) - { - if (Error != null) { - if (args == null || args.Length == 0) - Error (task, format); - else - Error (task, String.Format (format, args)); - } else - throw new InvalidOperationException ("Internal Error: should initialize Error"); - } - - public static void LogDebug (string format, params object[] args) - { - LogDebug (string.Empty, format, args); - } - - public static void LogDebug (string task, string format, params object[] args) - { - if (Debug != null) { - if (args == null || args.Length == 0) - Debug (task, format); - else - Debug (task, String.Format (format, args)); - } else - throw new InvalidOperationException ("Internal Error: should initialize Debug"); - } - - public static void LogTask (AndroidTaskLog log) - { - if (Task != null) - Task (log); - else - throw new InvalidOperationException ("Internal Error: should initialize Task"); - } - } - - public class AndroidTaskLog - { - public string Task { get; private set; } - public string Input { get; private set; } - public string Output { get; private set; } - public DateTime StartTime { get; private set; } - public DateTime EndTime { get; private set; } - - public AndroidTaskLog (string task, string input) - { - Task = task; - Input = input; - StartTime = DateTime.Now; - } - - public AndroidTaskLog Complete (string output) - { - Output = output; - EndTime = DateTime.Now; - - return this; - } - - public AndroidTaskLog Complete (object output) - { - if (output == null) - output = ""; - Output = output.ToString(); - EndTime = DateTime.Now; - - return this; - } - } -} - diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidSdk.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidSdkInfo.cs similarity index 54% rename from external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidSdk.cs rename to external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidSdkInfo.cs index 07c59ad08ac..cf02c5a38d5 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidSdk.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidSdkInfo.cs @@ -1,35 +1,33 @@ using System; -using System.Linq; using System.Collections.Generic; +using System.Diagnostics; using System.IO; +using System.Linq; namespace Xamarin.Android.Tools { - public class AndroidSdk + public class AndroidSdkInfo { - private static AndroidSdkBase sdk; + AndroidSdkBase sdk; - public static void Refresh (string androidSdkPath = null, string androidNdkPath = null, string javaSdkPath = null) + public AndroidSdkInfo (Action logger, string androidSdkPath = null, string androidNdkPath = null, string javaSdkPath = null) { - if (OS.IsWindows) - sdk = new AndroidSdkWindows (); - else - sdk = new AndroidSdkUnix (); - - try { - sdk.Initialize (androidSdkPath ?? sdk.PreferedAndroidSdkPath, androidNdkPath ?? sdk.PreferedAndroidNdkPath, - javaSdkPath ?? sdk.PreferedJavaSdkPath); - if (IsInstalled) { - AndroidLogger.LogInfo (null, "Found Android SDK."); - } else { - AndroidLogger.LogInfo (null, "Did not find Android SDK"); - } - } catch (Exception ex) { - AndroidLogger.LogError ("Error finding Android/Java SDKs", ex); - } + if (logger == null) + throw new ArgumentNullException (nameof (logger)); + + sdk = OS.IsWindows + ? (AndroidSdkBase) new AndroidSdkWindows (logger) + : (AndroidSdkBase) new AndroidSdkUnix (logger); + sdk.Initialize (androidSdkPath, androidNdkPath, javaSdkPath); + + // shouldn't happen, in that sdk.Initialize() should throw instead + if (string.IsNullOrEmpty (AndroidSdkPath)) + throw new InvalidOperationException ($"Could not determine Android SDK location. Please provide `{nameof (androidSdkPath)}`."); + if (string.IsNullOrEmpty (JavaSdkPath)) + throw new InvalidOperationException ($"Could not determine Java SDK location. Please provide `{nameof (javaSdkPath)}`."); } - public static IEnumerable GetBuildToolsPaths (string preferredBuildToolsVersion) + public IEnumerable GetBuildToolsPaths (string preferredBuildToolsVersion) { if (!string.IsNullOrEmpty (preferredBuildToolsVersion)) { var preferredDir = Path.Combine (AndroidSdkPath, "build-tools", preferredBuildToolsVersion); @@ -39,10 +37,8 @@ public static IEnumerable GetBuildToolsPaths (string preferredBuildTools return GetBuildToolsPaths (); } - public static IEnumerable GetBuildToolsPaths () + public IEnumerable GetBuildToolsPaths () { - ValidatePath (AndroidSdkPath); - var buildTools = Path.Combine (AndroidSdkPath, "build-tools"); if (Directory.Exists (buildTools)) { var preview = Directory.EnumerateDirectories (buildTools) @@ -74,26 +70,19 @@ static Version TryParseVersion (string v) return null; } - static string ValidatePath (string path) - { - if (String.IsNullOrEmpty (path)) - throw new InvalidOperationException ("This property is not valid when the SDK is not installed"); - return path; - } - - public static string GetPlatformDirectory (int apiLevel) + public string GetPlatformDirectory (int apiLevel) { return GetPlatformDirectoryFromId (apiLevel.ToString ()); } - public static string GetPlatformDirectoryFromId (string id) + public string GetPlatformDirectoryFromId (string id) { return Path.Combine (AndroidSdkPath, "platforms", "android-" + id); } - public static string TryGetPlatformDirectoryFromApiLevel (string apiLevel, AndroidVersions versions) + public string TryGetPlatformDirectoryFromApiLevel (string idOrApiLevel, AndroidVersions versions) { - var id = versions.GetIdFromApiLevel (apiLevel); + var id = versions.GetIdFromApiLevel (idOrApiLevel); var dir = GetPlatformDirectoryFromId (id); if (Directory.Exists (dir)) @@ -107,30 +96,24 @@ public static string TryGetPlatformDirectoryFromApiLevel (string apiLevel, Andro return null; } - public static bool IsPlatformInstalled (int apiLevel) + public bool IsPlatformInstalled (int apiLevel) { return apiLevel != 0 && Directory.Exists (GetPlatformDirectory (apiLevel)); } - public static bool IsInstalled { - get { - return !string.IsNullOrEmpty (AndroidSdkPath) && !string.IsNullOrEmpty (JavaSdkPath); - } - } - - public static string AndroidNdkPath { + public string AndroidNdkPath { get { return sdk.AndroidNdkPath; } } - public static string AndroidSdkPath { + public string AndroidSdkPath { get { return sdk.AndroidSdkPath; } } - public static string JavaSdkPath { + public string JavaSdkPath { get { return sdk.JavaSdkPath; } } - public static string AndroidNdkHostPlatform { + public string AndroidNdkHostPlatform { get { return sdk.NdkHostPlatform; } } } diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkBase.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkBase.cs index 51e0ec0e33f..b317863e93b 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkBase.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkBase.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using System.Linq; using System.IO; using System.Collections.Generic; @@ -25,6 +26,13 @@ public string[] AllAndroidNdks { } } + public readonly Action Logger; + + public AndroidSdkBase (Action logger) + { + Logger = logger; + } + public string AndroidSdkPath { get; private set; } public string AndroidNdkPath { get; private set; } public string JavaSdkPath { get; private set; } @@ -53,6 +61,10 @@ public string[] AllAndroidNdks { public virtual void Initialize (string androidSdkPath = null, string androidNdkPath = null, string javaSdkPath = null) { + androidSdkPath = androidSdkPath ?? PreferedAndroidSdkPath; + androidNdkPath = androidNdkPath ?? PreferedAndroidNdkPath; + javaSdkPath = javaSdkPath ?? PreferedJavaSdkPath; + AndroidSdkPath = ValidateAndroidSdkLocation (androidSdkPath) ? androidSdkPath : AllAndroidSdks.FirstOrDefault (); AndroidNdkPath = ValidateAndroidNdkLocation (androidNdkPath) ? androidNdkPath : AllAndroidNdks.FirstOrDefault (); JavaSdkPath = ValidateJavaSdkLocation (javaSdkPath) ? javaSdkPath : GetJavaSdkPath (); diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkUnix.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkUnix.cs index d6d583d3292..f77079bbfe3 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkUnix.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkUnix.cs @@ -1,13 +1,19 @@ using System; -using System.Xml; -using System.Xml.Linq; using System.Collections.Generic; +using System.Diagnostics; using System.IO; +using System.Xml; +using System.Xml.Linq; namespace Xamarin.Android.Tools { class AndroidSdkUnix : AndroidSdkBase { + public AndroidSdkUnix (Action logger) + : base (logger) + { + } + public override string NdkHostPlatform32Bit { get { return OS.IsMac ? "darwin-x86" : "linux-x86"; } } @@ -197,7 +203,7 @@ private static string UnixConfigPath { } } - private static XDocument GetUnixConfigFile () + private XDocument GetUnixConfigFile () { var file = UnixConfigPath; XDocument doc = null; @@ -209,7 +215,8 @@ private static XDocument GetUnixConfigFile () try { doc = XDocument.Load (file); } catch (Exception ex) { - AndroidLogger.LogError ("Could not load monodroid configuration file", ex); + Logger (TraceLevel.Error, "Could not load monodroid configuration file"); + Logger (TraceLevel.Verbose, ex.ToString ()); // move out of the way and create a new one doc = new XDocument (new XElement ("monodroid")); diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkWindows.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkWindows.cs index 78c37a57054..330d82f3387 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkWindows.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkWindows.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Linq; @@ -16,6 +17,11 @@ class AndroidSdkWindows : AndroidSdkBase const string XAMARIN_ANDROID_INSTALLER_PATH = @"SOFTWARE\Xamarin\MonoAndroid"; const string XAMARIN_ANDROID_INSTALLER_KEY = "PrivateAndroidSdkPath"; + public AndroidSdkWindows (Action logger) + : base (logger) + { + } + public override string ZipAlign { get; protected set; } = "zipalign.exe"; public override string JarSigner { get; protected set; } = "jarsigner.exe"; public override string KeyTool { get; protected set; } = "keytool.exe"; @@ -54,7 +60,7 @@ protected override IEnumerable GetAllAvailableAndroidSdks () var roots = new[] { RegistryEx.CurrentUser, RegistryEx.LocalMachine }; var wow = RegistryEx.Wow64.Key32; - AndroidLogger.LogInfo ("sdk", "Looking for Android SDK.."); + Logger (TraceLevel.Info, "Looking for Android SDK..."); // Check for the key the user gave us in the VS/addin options foreach (var root in roots) @@ -98,14 +104,14 @@ protected override string GetJavaSdkPath () string subkey = @"SOFTWARE\JavaSoft\Java Development Kit"; - AndroidLogger.LogInfo ("sdk", "Looking for Java 6 SDK.."); + Logger (TraceLevel.Info, "Looking for Java 6 SDK..."); foreach (var wow64 in new[] { RegistryEx.Wow64.Key32, RegistryEx.Wow64.Key64 }) { string key_name = string.Format (@"{0}\{1}\{2}", "HKLM", subkey, "CurrentVersion"); var currentVersion = RegistryEx.GetValueString (RegistryEx.LocalMachine, subkey, "CurrentVersion", wow64); if (!string.IsNullOrEmpty (currentVersion)) { - AndroidLogger.LogInfo ("sdk", " Key {0} found.", key_name); + Logger (TraceLevel.Info, $" Key {key_name} found."); // No matter what the CurrentVersion is, look for 1.6 or 1.7 or 1.8 if (CheckRegistryKeyForExecutable (RegistryEx.LocalMachine, subkey + "\\" + "1.6", "JavaHome", wow64, "bin", JarSigner)) @@ -118,7 +124,7 @@ protected override string GetJavaSdkPath () return RegistryEx.GetValueString (RegistryEx.LocalMachine, subkey + "\\" + "1.8", "JavaHome", wow64); } - AndroidLogger.LogInfo ("sdk", " Key {0} not found.", key_name); + Logger (TraceLevel.Info, $" Key {key_name} not found."); } // We ran out of things to check.. @@ -130,7 +136,7 @@ protected override IEnumerable GetAllAvailableAndroidNdks () var roots = new[] { RegistryEx.CurrentUser, RegistryEx.LocalMachine }; var wow = RegistryEx.Wow64.Key32; - AndroidLogger.LogInfo ("sdk", "Looking for Android NDK.."); + Logger (TraceLevel.Info, "Looking for Android NDK..."); // Check for the key the user gave us in the VS/addin options foreach (var root in roots) @@ -184,16 +190,16 @@ private bool CheckRegistryKeyForExecutable (UIntPtr key, string subkey, string v var path = NullIfEmpty (RegistryEx.GetValueString (key, subkey, valueName, wow64)); if (path == null) { - AndroidLogger.LogInfo ("sdk", " Key {0} not found.", key_name); + Logger (TraceLevel.Info, $" Key {key_name} not found."); return false; } if (!FindExecutableInDirectory (exe, Path.Combine (path, subdir)).Any ()) { - AndroidLogger.LogInfo ("sdk", " Key {0} found:\n Path does not contain {1} in \\{2} ({3}).", key_name, exe, subdir, path); + Logger (TraceLevel.Info, $" Key {key_name} found:\n Path does not contain {exe} in \\{subdir} ({path})."); return false; } - AndroidLogger.LogInfo ("sdk", " Key {0} found:\n Path contains {1} in \\{2} ({3}).", key_name, exe, subdir, path); + Logger (TraceLevel.Info, $" Key {key_name} found:\n Path contains {exe} in \\{subdir} ({path})."); return true; } diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/AndroidSdkInfoTests.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/AndroidSdkInfoTests.cs new file mode 100644 index 00000000000..2190f23be0d --- /dev/null +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/AndroidSdkInfoTests.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; + +using NUnit.Framework; + +namespace Xamarin.Android.Tools.Tests +{ + [TestFixture] + public class AndroidSdkInfoTests + { + [Test] + public void Constructor_NullLogger () + { + Action logger = null; + Assert.Throws (() => new AndroidSdkInfo (logger)); + } + } +} diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/Xamarin.Android.Tools.AndroidSdk-Tests.csproj b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/Xamarin.Android.Tools.AndroidSdk-Tests.csproj index 6ae53e33175..9e65e90df02 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/Xamarin.Android.Tools.AndroidSdk-Tests.csproj +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/Xamarin.Android.Tools.AndroidSdk-Tests.csproj @@ -44,6 +44,7 @@ + diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj index 34544e712e6..d9514706978 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj @@ -40,8 +40,7 @@ - - + From 1d50216c711a029bce8374094f3db8a4f995eebe Mon Sep 17 00:00:00 2001 From: Jonathan Pryor Date: Tue, 12 Sep 2017 04:13:54 -0400 Subject: [PATCH 021/308] Import AndroidAppManifest from xamarin-android/8942eca (#9) `AndroidAppManifest` allows parsing and modifying select parts of `AndroidManifest.xml` files which are part of Android projects. `AndroidAppManifest` is [imported from xamarin-android/8942eca][0]. [0]: https://github.com/xamarin/xamarin-android/blob/8942eca/src/Xamarin.Android.Build.Utilities/AndroidAppManifest.cs --- .../AndroidAppManifest.cs | 324 ++++++++++++++++++ .../FileUtil.cs | 58 ++++ .../Tests/AndroidAppManifestTests.cs | 166 +++++++++ .../Tests/Resources/manifest-simplewidget.xml | 29 ++ ...arin.Android.Tools.AndroidSdk-Tests.csproj | 6 + .../Xamarin.Android.Tools.AndroidSdk.csproj | 2 + 6 files changed, 585 insertions(+) create mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidAppManifest.cs create mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/FileUtil.cs create mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/AndroidAppManifestTests.cs create mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/Resources/manifest-simplewidget.xml diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidAppManifest.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidAppManifest.cs new file mode 100644 index 00000000000..88ee2cbe522 --- /dev/null +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidAppManifest.cs @@ -0,0 +1,324 @@ +using System; +using System.Linq; +using System.Xml; +using System.Collections.Generic; +using System.Xml.Linq; +using System.Text.RegularExpressions; +using System.Text; +using System.IO; + +namespace Xamarin.Android.Tools +{ + public class AndroidAppManifest + { + AndroidVersions versions; + XDocument doc; + XElement manifest, application, usesSdk; + + static readonly XNamespace aNS = "http://schemas.android.com/apk/res/android"; + static readonly XName aName = aNS + "name"; + + AndroidAppManifest (AndroidVersions versions, XDocument doc) + { + if (versions == null) + throw new ArgumentNullException (nameof (versions)); + if (doc == null) + throw new ArgumentNullException (nameof (doc)); + this.versions = versions; + this.doc = doc; + manifest = doc.Root; + if (manifest.Name != "manifest") + throw new ArgumentException ("App manifest does not have 'manifest' root element", nameof (doc)); + + application = manifest.Element ("application"); + if (application == null) + manifest.Add (application = new XElement ("application")); + + usesSdk = manifest.Element ("uses-sdk"); + if (usesSdk == null) + manifest.Add (usesSdk = new XElement ("uses-sdk")); + } + + public static string CanonicalizePackageName (string packageNameOrAssemblyName) + { + if (packageNameOrAssemblyName == null) + throw new ArgumentNullException ("packageNameOrAssemblyName"); + if (string.IsNullOrEmpty (packageNameOrAssemblyName = packageNameOrAssemblyName.Trim ())) + throw new ArgumentException ("Must specify a package name or assembly name", "packageNameOrAssemblyName"); + + string[] packageParts = packageNameOrAssemblyName.Split (new[]{'.'}, StringSplitOptions.RemoveEmptyEntries); + for (int i = 0; i < packageParts.Length; ++i) { + packageParts [i] = Regex.Replace (packageParts [i], "[^A-Za-z0-9_]", "_"); + if (char.IsDigit (packageParts [i], 0) || packageParts [i][0] == '_') + packageParts [i] = "x" + packageParts [i]; + } + return packageParts.Length == 1 + ? packageParts [0] + "." + packageParts [0] + : string.Join (".", packageParts); + } + + public static AndroidAppManifest Create (string packageName, string appLabel, AndroidVersions versions) + { + return new AndroidAppManifest (versions, XDocument.Parse ( + @" + + + + +")) { + PackageName = packageName, + ApplicationLabel = appLabel, + }; + } + + public static AndroidAppManifest Load (string filename, AndroidVersions versions) + { + return Load (XDocument.Load (filename), versions); + } + + public static AndroidAppManifest Load (XDocument doc, AndroidVersions versions) + { + return new AndroidAppManifest (versions, doc); + } + + public void Write (XmlWriter writer) + { + doc.Save (writer); + } + + public void WriteToFile (string fileName) + { + var xmlSettings = new XmlWriterSettings () { + Encoding = Encoding.UTF8, + CloseOutput = false, + Indent = true, + IndentChars = "\t", + NewLineChars = "\n", + }; + + var tempFile = FileUtil.GetTempFilenameForWrite (fileName); + bool success = false; + try { + using (var writer = XmlTextWriter.Create (tempFile, xmlSettings)) { + Write (writer); + } + FileUtil.SystemRename (tempFile, fileName); + success = true; + } finally { + if (!success) { + try { + File.Delete (tempFile); + } catch { + //the original exception is more important than this one + } + } + } + } + + static string NullIfEmpty (string value) + { + return string.IsNullOrEmpty (value) ? null : value; + } + + public string PackageName { + get { return (string) manifest.Attribute ("package"); } + set { manifest.SetAttributeValue ("package", NullIfEmpty (value)); } + } + + public string ApplicationLabel { + get { return (string) application.Attribute (aNS + "label"); } + set { application.SetAttributeValue (aNS + "label", NullIfEmpty (value)); } + } + + public string ApplicationIcon { + get { return (string) application.Attribute (aNS + "icon"); } + set { application.SetAttributeValue (aNS + "icon", NullIfEmpty (value)); } + } + + public string ApplicationTheme { + get { return (string) application.Attribute (aNS + "theme"); } + set { application.SetAttributeValue (aNS + "theme", NullIfEmpty (value)); } + } + + public string VersionName { + get { return (string) manifest.Attribute (aNS + "versionName"); } + set { manifest.SetAttributeValue (aNS + "versionName", NullIfEmpty (value)); } + } + + public string VersionCode { + get { return (string) manifest.Attribute (aNS + "versionCode"); } + set { manifest.SetAttributeValue (aNS + "versionCode", NullIfEmpty (value)); } + } + + public string InstallLocation { + get { return (string) manifest.Attribute (aNS + "installLocation"); } + set { manifest.SetAttributeValue (aNS + "installLocation", NullIfEmpty (value)); } + } + + public int? MinSdkVersion { + get { return ParseSdkVersion (usesSdk.Attribute (aNS + "minSdkVersion")); } + set { usesSdk.SetAttributeValue (aNS + "minSdkVersion", value == null ? null : value.ToString ()); } + } + + public int? TargetSdkVersion { + get { return ParseSdkVersion (usesSdk.Attribute (aNS + "targetSdkVersion")); } + set { usesSdk.SetAttributeValue (aNS + "targetSdkVersion", value == null ? null : value.ToString ()); } + } + + int? ParseSdkVersion (XAttribute attribute) + { + var version = (string)attribute; + if (string.IsNullOrEmpty (version)) + return null; + int vn; + if (!int.TryParse (version, out vn)) { + int? apiLevel = versions.GetApiLevelFromId (version); + if (apiLevel.HasValue) + return apiLevel.Value; + return versions.MaxStableVersion.ApiLevel; + } + return vn; + } + + public IEnumerable AndroidPermissions { + get { + foreach (var el in manifest.Elements ("uses-permission")) { + var name = (string) el.Attribute (aName); + if (name == null) + continue; + var lastDot = name.LastIndexOf ('.'); + if (lastDot >= 0) + yield return name.Substring (lastDot + 1); + } + } + } + + public IEnumerable AndroidPermissionsQualified { + get { + foreach (var el in manifest.Elements ("uses-permission")) { + var name = (string) el.Attribute (aName); + if (name != null) + yield return name; + } + } + } + + public bool? Debuggable { + get { return (bool?) application.Attribute (aNS + "debuggable"); } + set { application.SetAttributeValue (aNS + "debuggable", value); } + } + + public void SetAndroidPermissions (IEnumerable permissions) + { + var newPerms = new HashSet (permissions.Select (FullyQualifyPermission)); + var current = new HashSet (AndroidPermissionsQualified); + AddAndroidPermissions (newPerms.Except (current)); + RemoveAndroidPermissions (current.Except (newPerms)); + } + + void AddAndroidPermissions (IEnumerable permissions) + { + var newElements = permissions.Select (p => new XElement ("uses-permission", new XAttribute (aName, p))); + + var lastPerm = manifest.Elements ("uses-permission").LastOrDefault (); + if (lastPerm != null) { + foreach (var el in newElements) { + lastPerm.AddAfterSelf (el); + lastPerm = el; + } + } else { + var parentNode = (XNode) manifest.Element ("application") ?? manifest.LastNode; + foreach (var el in newElements) + parentNode.AddBeforeSelf (el); + } + } + + string FullyQualifyPermission (string permission) + { + //if already qualified, don't mess with it + if (permission.IndexOf ('.') > -1) + return permission; + + switch (permission) { + case "READ_HISTORY_BOOKMARKS": + case "WRITE_HISTORY_BOOKMARKS": + return string.Format ("com.android.browser.permission.{0}", permission); + default: + return string.Format ("android.permission.{0}", permission); + } + } + + void RemoveAndroidPermissions (IEnumerable permissions) + { + var perms = new HashSet (permissions); + var list = manifest.Elements ("uses-permission") + .Where (el => perms.Contains ((string)el.Attribute (aName))).ToList (); + foreach (var el in list) + el.Remove (); + } + + [Obsolete ("Use GetLaunchableFastdevActivityName or GetLaunchableUserActivityName")] + public string GetLaunchableActivityName () + { + return GetLaunchableFastDevActivityName (); + } + + /// Gets an activity that can be used to initialize the override directory for fastdev. + [Obsolete ("This should not be needed anymore; Activity execution is not part of installation.")] + public string GetLaunchableFastDevActivityName () + { + string first = null; + foreach (var a in GetLaunchableActivities ()) { + var name = (string) a.Attribute (aName); + //prefer the fastdev launcher, it's quicker + if (name == "mono.android.__FastDevLauncher") { + return name; + } + //else just use the first other launchable activity + if (first == null) { + first = name; + } + } + + return string.IsNullOrEmpty (first)? null : first; + } + + // We add a fake launchable activity for FastDev, but we don't want + // to launch that one when the user does Run or Debug + public string GetLaunchableUserActivityName () + { + return GetLaunchableActivities () + .Select (a => (string) a.Attribute (aName)) + .FirstOrDefault (name => !string.IsNullOrEmpty (name) && name != "mono.android.__FastDevLauncher"); + } + + IEnumerable GetLaunchableActivities () + { + foreach (var activity in application.Elements ("activity")) { + var filter = activity.Element ("intent-filter"); + if (filter != null) { + foreach (var category in filter.Elements ("category")) + if (category != null && (string)category.Attribute (aName) == "android.intent.category.LAUNCHER") + yield return activity; + } + } + } + + public IEnumerable GetAllActivityNames () + { + foreach (var activity in application.Elements ("activity")) { + var activityName = (string) activity.Attribute (aName); + if (activityName != "mono.android.__FastDevLauncher") + yield return activityName; + } + } + + public IEnumerable GetLaunchableActivityNames () + { + return GetLaunchableActivities () + .Select (a => (string) a.Attribute (aName)) + .Where (name => !string.IsNullOrEmpty (name) && name != "mono.android.__FastDevLauncher"); + } + } +} + diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/FileUtil.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/FileUtil.cs new file mode 100644 index 00000000000..6fe8487e583 --- /dev/null +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/FileUtil.cs @@ -0,0 +1,58 @@ +using System; +using System.IO; +using System.Runtime.InteropServices; + +namespace Xamarin.Android.Tools +{ + class FileUtil + { + public static string GetTempFilenameForWrite (string fileName) + { + return Path.GetDirectoryName (fileName) + Path.DirectorySeparatorChar + ".#" + Path.GetFileName (fileName); + } + + //From MonoDevelop.Core.FileService + public static void SystemRename (string sourceFile, string destFile) + { + //FIXME: use the atomic System.IO.File.Replace on NTFS + if (OS.IsWindows) { + string wtmp = null; + if (File.Exists (destFile)) { + do { + wtmp = Path.Combine (Path.GetTempPath (), Guid.NewGuid ().ToString ()); + } while (File.Exists (wtmp)); + + File.Move (destFile, wtmp); + } + try { + File.Move (sourceFile, destFile); + } + catch { + try { + if (wtmp != null) + File.Move (wtmp, destFile); + } + catch { + wtmp = null; + } + throw; + } + finally { + if (wtmp != null) { + try { + File.Delete (wtmp); + } + catch { } + } + } + } + else { + rename (sourceFile, destFile); + } + } + + [DllImport ("libc", SetLastError=true)] + static extern int rename (string old, string @new); + } +} + diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/AndroidAppManifestTests.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/AndroidAppManifestTests.cs new file mode 100644 index 00000000000..cefd8e3e8aa --- /dev/null +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/AndroidAppManifestTests.cs @@ -0,0 +1,166 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Text; +using System.Xml; +using System.Xml.Linq; + +using NUnit.Framework; + +namespace Xamarin.Android.Tools.Tests +{ + [TestFixture] + public class AndroidAppManifestTests + { + [Test] + public void Load () + { + var versions = new AndroidVersions (new AndroidVersion [0]); + Assert.Throws (() => AndroidAppManifest.Load ((string) null, versions)); + Assert.Throws (() => AndroidAppManifest.Load ("filename", null)); + Assert.Throws (() => AndroidAppManifest.Load ((XDocument) null, versions)); + Assert.Throws (() => AndroidAppManifest.Load (GetTestAppManifest (), versions)); + + Assert.Throws (() => AndroidAppManifest.Load (XDocument.Parse (""), versions)); + } + + [Test] + public void ParsePermissions () + { + var versions = new AndroidVersions (new AndroidVersion [0]); + var manifest = AndroidAppManifest.Load (GetTestAppManifest (), versions); + var permissions = manifest.AndroidPermissions.ToArray (); + Assert.AreEqual (3, permissions.Length, "#1"); + Assert.IsTrue (permissions.Contains ("INTERNET"), "#2"); + Assert.IsTrue (permissions.Contains ("READ_CONTACTS"), "#3"); + Assert.IsTrue (permissions.Contains ("WRITE_CONTACTS"), "#4"); + } + + static XDocument GetTestAppManifest () + { + using (var xml = typeof (AndroidAppManifestTests).Assembly.GetManifestResourceStream ("manifest-simplewidget.xml")) { + return XDocument.Load (xml); + } + } + + [Test] + public void SetNewPermissions () + { + var versions = new AndroidVersions (new AndroidVersion [0]); + var manifest = AndroidAppManifest.Load (GetTestAppManifest (), versions); + manifest.SetAndroidPermissions (new [] { "FOO" }); + + var sb = new StringBuilder (); + using (var writer = XmlWriter.Create (sb)) { + manifest.Write (writer); + } + + manifest = AndroidAppManifest.Load (XDocument.Parse (sb.ToString ()), versions); + Assert.AreEqual (1, manifest.AndroidPermissions.Count (), "#1"); + Assert.AreEqual ("FOO", manifest.AndroidPermissions.ElementAt (0)); + } + + [Test] + public void CanonicalizePackageName () + { + Assert.Throws(() => AndroidAppManifest.CanonicalizePackageName (null)); + Assert.Throws(() => AndroidAppManifest.CanonicalizePackageName ("")); + Assert.Throws(() => AndroidAppManifest.CanonicalizePackageName (" ")); + + Assert.AreEqual ("A.A", + AndroidAppManifest.CanonicalizePackageName ("A")); + Assert.AreEqual ("Foo.Bar", + AndroidAppManifest.CanonicalizePackageName ("Foo.Bar")); + Assert.AreEqual ("foo_bar.foo_bar", + AndroidAppManifest.CanonicalizePackageName ("foo-bar")); + Assert.AreEqual ("x1.x1", + AndroidAppManifest.CanonicalizePackageName ("1")); + Assert.AreEqual ("x_1.x_2", + AndroidAppManifest.CanonicalizePackageName ("_1._2")); + Assert.AreEqual ("mfa1.x0.x2_2", + AndroidAppManifest.CanonicalizePackageName ("mfa1.0.2_2")); + Assert.AreEqual ("My.Cool_Assembly", + AndroidAppManifest.CanonicalizePackageName ("My.Cool Assembly")); + Assert.AreEqual ("x7Cats.x7Cats", + AndroidAppManifest.CanonicalizePackageName ("7Cats")); + } + + [Test] + public void CanParseNonNumericSdkVersion () + { + var versions = new AndroidVersions (new AndroidVersion [0]); + var doc = XDocument.Parse (@" + + + + + "); + var manifest = AndroidAppManifest.Load (doc, versions); + + var mininum = manifest.MinSdkVersion; + var target = manifest.TargetSdkVersion; + + Assert.IsTrue (mininum.HasValue); + Assert.IsTrue (target.HasValue); + Assert.AreEqual (21, mininum.Value); + Assert.AreEqual (21, target.Value); + } + + [Test] + public void EnsureMinAndTargetSdkVersionsAreReadIndependently () + { + // Regression test for https://bugzilla.xamarin.com/show_bug.cgi?id=21296 + var versions = new AndroidVersions (new AndroidVersion [0]); + var doc = XDocument.Parse (@" + + + + + "); + var manifest = AndroidAppManifest.Load (doc, versions); + + var mininum = manifest.MinSdkVersion; + var target = manifest.TargetSdkVersion; + + Assert.IsTrue (mininum.HasValue); + Assert.IsTrue (target.HasValue); + Assert.AreEqual (8, mininum.Value); + Assert.AreEqual (12, target.Value); + } + + [Test] + public void EnsureUsesPermissionElementOrder () + { + var versions = new AndroidVersions (new AndroidVersion [0]); + var manifest = AndroidAppManifest.Create ("com.xamarin.test", "Xamarin Test", versions); + manifest.SetAndroidPermissions (new string[] { "FOO" }); + var sb = new StringBuilder (); + using (var writer = XmlWriter.Create (sb)) { + manifest.Write (writer); + } + + var doc = XDocument.Parse (sb.ToString ()); + var app = doc.Element ("manifest").Element ("application"); + Assert.IsNotNull (app, "Application element should exist"); + Assert.IsFalse (app.ElementsAfterSelf ().Any (x => x.Name == "uses-permission")); + Assert.IsTrue (app.ElementsBeforeSelf ().Any (x => x.Name == "uses-permission")); + } + + [Test] + public void CanGetAppTheme () + { + var versions = new AndroidVersions (new AndroidVersion [0]); + var doc = XDocument.Parse (@" + + + + + "); + var manifest = AndroidAppManifest.Load (doc, versions); + + Assert.AreEqual ("@android:style/Theme.Material.Light", manifest.ApplicationTheme); + } + } +} diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/Resources/manifest-simplewidget.xml b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/Resources/manifest-simplewidget.xml new file mode 100644 index 00000000000..966639d4e51 --- /dev/null +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/Resources/manifest-simplewidget.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/Xamarin.Android.Tools.AndroidSdk-Tests.csproj b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/Xamarin.Android.Tools.AndroidSdk-Tests.csproj index 9e65e90df02..3c56d14087b 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/Xamarin.Android.Tools.AndroidSdk-Tests.csproj +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/Xamarin.Android.Tools.AndroidSdk-Tests.csproj @@ -45,6 +45,7 @@ + @@ -58,5 +59,10 @@ + + + manifest-simplewidget.xml + + \ No newline at end of file diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj index d9514706978..dacb3b111a7 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj @@ -45,6 +45,8 @@ + + From 41d969bf99a07d4c50e6d057b0df10ea217e549d Mon Sep 17 00:00:00 2001 From: Jonathan Pryor Date: Tue, 12 Sep 2017 15:22:05 -0400 Subject: [PATCH 022/308] Rename assembly to Xamarin.Android.Tools.AndroidSdk.dll. `Xamarin.Android.Tools.dll` was *supposed* to be renamed to `Xamarin.Android.Tools.AndroidSdk.dll` in d4b3b0bb -- see the comment! -- but was overlooked. :-( Correct the oversight. Update the unit test assembly to follow suit: `Xamarin.Android.Tools.AndroidSdk-Tests.dll`. Update the unit tests, which were inexplicably failing when using: make run-all-tests --- external/xamarin-android-tools/Makefile | 2 +- .../AndroidAppManifest.cs | 10 ++++++++++ .../Tests/AndroidAppManifestTests.cs | 2 +- .../Xamarin.Android.Tools.AndroidSdk-Tests.csproj | 2 +- .../Xamarin.Android.Tools.AndroidSdk.csproj | 2 +- 5 files changed, 14 insertions(+), 4 deletions(-) diff --git a/external/xamarin-android-tools/Makefile b/external/xamarin-android-tools/Makefile index 7cc754fa447..e2af78b9a67 100644 --- a/external/xamarin-android-tools/Makefile +++ b/external/xamarin-android-tools/Makefile @@ -34,7 +34,7 @@ endef $(NUNIT_CONSOLE): prepare NUNIT_TESTS = \ - bin/Test$(CONFIGURATION)/Xamarin.Android.Tools-Tests.dll + bin/Test$(CONFIGURATION)/Xamarin.Android.Tools.AndroidSdk-Tests.dll run-nunit-tests: $(NUNIT_TESTS) $(foreach t,$(NUNIT_TESTS), $(call RUN_NUNIT_TEST,$(t),1)) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidAppManifest.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidAppManifest.cs index 88ee2cbe522..21f3bcb6acb 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidAppManifest.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidAppManifest.cs @@ -73,11 +73,21 @@ public static AndroidAppManifest Create (string packageName, string appLabel, An public static AndroidAppManifest Load (string filename, AndroidVersions versions) { + if (filename == null) + throw new ArgumentNullException (nameof (filename)); + if (versions == null) + throw new ArgumentNullException (nameof (versions)); + return Load (XDocument.Load (filename), versions); } public static AndroidAppManifest Load (XDocument doc, AndroidVersions versions) { + if (doc == null) + throw new ArgumentNullException (nameof (doc)); + if (versions == null) + throw new ArgumentNullException (nameof (versions)); + return new AndroidAppManifest (versions, doc); } diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/AndroidAppManifestTests.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/AndroidAppManifestTests.cs index cefd8e3e8aa..c387ce22851 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/AndroidAppManifestTests.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/AndroidAppManifestTests.cs @@ -21,7 +21,7 @@ public void Load () Assert.Throws (() => AndroidAppManifest.Load ((string) null, versions)); Assert.Throws (() => AndroidAppManifest.Load ("filename", null)); Assert.Throws (() => AndroidAppManifest.Load ((XDocument) null, versions)); - Assert.Throws (() => AndroidAppManifest.Load (GetTestAppManifest (), versions)); + Assert.Throws (() => AndroidAppManifest.Load (GetTestAppManifest (), null)); Assert.Throws (() => AndroidAppManifest.Load (XDocument.Parse (""), versions)); } diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/Xamarin.Android.Tools.AndroidSdk-Tests.csproj b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/Xamarin.Android.Tools.AndroidSdk-Tests.csproj index 3c56d14087b..b3ff7e16e41 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/Xamarin.Android.Tools.AndroidSdk-Tests.csproj +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/Xamarin.Android.Tools.AndroidSdk-Tests.csproj @@ -8,7 +8,7 @@ Library Properties Xamarin.Android.Tools.Tests - Xamarin.Android.Tools-Tests + Xamarin.Android.Tools.AndroidSdk-Tests v4.5.2 512 diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj index dacb3b111a7..07a1cab3393 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj @@ -6,7 +6,7 @@ {e34bcfa0-caa4-412c-aa1c-75db8d67d157} Library Xamarin.Android.Tools - Xamarin.Android.Tools + Xamarin.Android.Tools.AndroidSdk v4.5 8.0.30703 2.0 From 8022a58e9f808f4a8ac477d5e0aa0a85fca5d9ef Mon Sep 17 00:00:00 2001 From: Dean Ellis Date: Fri, 15 Sep 2017 20:15:02 +0100 Subject: [PATCH 023/308] Look for JDK 1.8 before JDK 1.6 (#1) Fixes: https://bugzilla.xamarin.com/show_bug.cgi?id=52928 The order of the default JDK search was incorrect. We should be searching for the latest JDK first since this is what the google tooling needs. This commit switches the order in which we do the search. --- .../Sdks/AndroidSdkWindows.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkWindows.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkWindows.cs index 330d82f3387..95c6f18071c 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkWindows.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkWindows.cs @@ -114,14 +114,14 @@ protected override string GetJavaSdkPath () Logger (TraceLevel.Info, $" Key {key_name} found."); // No matter what the CurrentVersion is, look for 1.6 or 1.7 or 1.8 - if (CheckRegistryKeyForExecutable (RegistryEx.LocalMachine, subkey + "\\" + "1.6", "JavaHome", wow64, "bin", JarSigner)) - return RegistryEx.GetValueString (RegistryEx.LocalMachine, subkey + "\\" + "1.6", "JavaHome", wow64); + if (CheckRegistryKeyForExecutable (RegistryEx.LocalMachine, subkey + "\\" + "1.8", "JavaHome", wow64, "bin", JarSigner)) + return RegistryEx.GetValueString (RegistryEx.LocalMachine, subkey + "\\" + "1.8", "JavaHome", wow64); if (CheckRegistryKeyForExecutable (RegistryEx.LocalMachine, subkey + "\\" + "1.7", "JavaHome", wow64, "bin", JarSigner)) return RegistryEx.GetValueString (RegistryEx.LocalMachine, subkey + "\\" + "1.7", "JavaHome", wow64); - if (CheckRegistryKeyForExecutable (RegistryEx.LocalMachine, subkey + "\\" + "1.8", "JavaHome", wow64, "bin", JarSigner)) - return RegistryEx.GetValueString (RegistryEx.LocalMachine, subkey + "\\" + "1.8", "JavaHome", wow64); + if (CheckRegistryKeyForExecutable (RegistryEx.LocalMachine, subkey + "\\" + "1.6", "JavaHome", wow64, "bin", JarSigner)) + return RegistryEx.GetValueString (RegistryEx.LocalMachine, subkey + "\\" + "1.6", "JavaHome", wow64); } Logger (TraceLevel.Info, $" Key {key_name} not found."); From efe15ceb181dac463f1f3363e7503d7e98a55476 Mon Sep 17 00:00:00 2001 From: Dean Ellis Date: Fri, 29 Sep 2017 15:49:04 +0100 Subject: [PATCH 024/308] Look for the SDK entries in the correct registry key. (#10) We were looking for the saved VS SDK entires in the wrong registry key. VS 2017 passes the registry key via the `%XAMARIN_ANDROID_REGKEY%` environment variable. --- .../Sdks/AndroidSdkWindows.cs | 45 ++++++++++++------- 1 file changed, 30 insertions(+), 15 deletions(-) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkWindows.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkWindows.cs index 95c6f18071c..a88cf6295b3 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkWindows.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkWindows.cs @@ -33,39 +33,49 @@ public AndroidSdkWindows (Action logger) public override string PreferedAndroidSdkPath { get { var wow = RegistryEx.Wow64.Key32; - if (CheckRegistryKeyForExecutable (RegistryEx.CurrentUser, MDREG_KEY, MDREG_ANDROID_SDK, wow, "platform-tools", Adb)) - return RegistryEx.GetValueString (RegistryEx.CurrentUser, MDREG_KEY, MDREG_ANDROID_SDK, wow); + var regKey = GetMDRegistryKey (); + if (CheckRegistryKeyForExecutable (RegistryEx.CurrentUser, regKey, MDREG_ANDROID_SDK, wow, "platform-tools", Adb)) + return RegistryEx.GetValueString (RegistryEx.CurrentUser, regKey, MDREG_ANDROID_SDK, wow); return null; } } public override string PreferedAndroidNdkPath { get { var wow = RegistryEx.Wow64.Key32; - if (CheckRegistryKeyForExecutable (RegistryEx.CurrentUser, MDREG_KEY, MDREG_ANDROID_NDK, wow, ".", NdkStack)) - return RegistryEx.GetValueString (RegistryEx.CurrentUser, MDREG_KEY, MDREG_ANDROID_NDK, wow); + var regKey = GetMDRegistryKey (); + if (CheckRegistryKeyForExecutable (RegistryEx.CurrentUser, regKey, MDREG_ANDROID_NDK, wow, ".", NdkStack)) + return RegistryEx.GetValueString (RegistryEx.CurrentUser, regKey, MDREG_ANDROID_NDK, wow); return null; } } public override string PreferedJavaSdkPath { get { var wow = RegistryEx.Wow64.Key32; - if (CheckRegistryKeyForExecutable (RegistryEx.CurrentUser, MDREG_KEY, MDREG_JAVA_SDK, wow, "bin", JarSigner)) - return RegistryEx.GetValueString (RegistryEx.CurrentUser, MDREG_KEY, MDREG_JAVA_SDK, wow); + var regKey = GetMDRegistryKey (); + if (CheckRegistryKeyForExecutable (RegistryEx.CurrentUser, regKey, MDREG_JAVA_SDK, wow, "bin", JarSigner)) + return RegistryEx.GetValueString (RegistryEx.CurrentUser, regKey, MDREG_JAVA_SDK, wow); return null; } } + string GetMDRegistryKey () + { + var regKey = Environment.GetEnvironmentVariable ("XAMARIN_ANDROID_REGKEY"); + return string.IsNullOrWhiteSpace (regKey) ? MDREG_KEY : regKey; + } + protected override IEnumerable GetAllAvailableAndroidSdks () { var roots = new[] { RegistryEx.CurrentUser, RegistryEx.LocalMachine }; var wow = RegistryEx.Wow64.Key32; + var regKey = GetMDRegistryKey (); Logger (TraceLevel.Info, "Looking for Android SDK..."); // Check for the key the user gave us in the VS/addin options foreach (var root in roots) - if (CheckRegistryKeyForExecutable (root, MDREG_KEY, MDREG_ANDROID_SDK, wow, "platform-tools", Adb)) - yield return RegistryEx.GetValueString (root, MDREG_KEY, MDREG_ANDROID_SDK, wow); + if (CheckRegistryKeyForExecutable (root, regKey, MDREG_ANDROID_SDK, wow, "platform-tools", Adb)) + yield return RegistryEx.GetValueString (root, regKey, MDREG_ANDROID_SDK, wow); // Check for the key written by the Xamarin installer if (CheckRegistryKeyForExecutable (RegistryEx.CurrentUser, XAMARIN_ANDROID_INSTALLER_PATH, XAMARIN_ANDROID_INSTALLER_KEY, wow, "platform-tools", Adb)) @@ -96,10 +106,11 @@ protected override string GetJavaSdkPath () // check the user specified path var roots = new[] { RegistryEx.CurrentUser, RegistryEx.LocalMachine }; const RegistryEx.Wow64 wow = RegistryEx.Wow64.Key32; + var regKey = GetMDRegistryKey (); foreach (var root in roots) { - if (CheckRegistryKeyForExecutable (root, MDREG_KEY, MDREG_JAVA_SDK, wow, "bin", JarSigner)) - return RegistryEx.GetValueString (root, MDREG_KEY, MDREG_JAVA_SDK, wow); + if (CheckRegistryKeyForExecutable (root, regKey, MDREG_JAVA_SDK, wow, "bin", JarSigner)) + return RegistryEx.GetValueString (root, regKey, MDREG_JAVA_SDK, wow); } string subkey = @"SOFTWARE\JavaSoft\Java Development Kit"; @@ -135,13 +146,14 @@ protected override IEnumerable GetAllAvailableAndroidNdks () { var roots = new[] { RegistryEx.CurrentUser, RegistryEx.LocalMachine }; var wow = RegistryEx.Wow64.Key32; + var regKey = GetMDRegistryKey (); Logger (TraceLevel.Info, "Looking for Android NDK..."); // Check for the key the user gave us in the VS/addin options foreach (var root in roots) - if (CheckRegistryKeyForExecutable (root, MDREG_KEY, MDREG_ANDROID_NDK, wow, ".", NdkStack)) - yield return RegistryEx.GetValueString (root, MDREG_KEY, MDREG_ANDROID_NDK, wow); + if (CheckRegistryKeyForExecutable (root, regKey, MDREG_ANDROID_NDK, wow, ".", NdkStack)) + yield return RegistryEx.GetValueString (root, regKey, MDREG_ANDROID_NDK, wow); /* // Check for the key written by the Xamarin installer @@ -169,17 +181,20 @@ protected override string GetShortFormPath (string path) public override void SetPreferredAndroidSdkPath (string path) { - RegistryEx.SetValueString (RegistryEx.CurrentUser, MDREG_KEY, MDREG_ANDROID_SDK, path ?? "", RegistryEx.Wow64.Key32); + var regKey = GetMDRegistryKey (); + RegistryEx.SetValueString (RegistryEx.CurrentUser, regKey, MDREG_ANDROID_SDK, path ?? "", RegistryEx.Wow64.Key32); } public override void SetPreferredJavaSdkPath (string path) { - RegistryEx.SetValueString (RegistryEx.CurrentUser, MDREG_KEY, MDREG_JAVA_SDK, path ?? "", RegistryEx.Wow64.Key32); + var regKey = GetMDRegistryKey (); + RegistryEx.SetValueString (RegistryEx.CurrentUser, regKey, MDREG_JAVA_SDK, path ?? "", RegistryEx.Wow64.Key32); } public override void SetPreferredAndroidNdkPath (string path) { - RegistryEx.SetValueString (RegistryEx.CurrentUser, MDREG_KEY, MDREG_ANDROID_NDK, path ?? "", RegistryEx.Wow64.Key32); + var regKey = GetMDRegistryKey (); + RegistryEx.SetValueString (RegistryEx.CurrentUser, regKey, MDREG_ANDROID_NDK, path ?? "", RegistryEx.Wow64.Key32); } #region Helper Methods From a60284d7913f690f86a4b396223e3778b7ffbf1f Mon Sep 17 00:00:00 2001 From: Dean Ellis Date: Wed, 11 Oct 2017 20:31:11 +0100 Subject: [PATCH 025/308] Add AndroidVersions.MinStableVersion (#11) Context: https://github.com/xamarin/xamarin-android/pull/928 We have `AndroidVersions.MaxStableVersion`, it is logical that we should have an `AndroidVersions.MinStableVersion` property as well. --- .../AndroidVersions.cs | 4 ++++ .../Tests/AndroidVersionsTests.cs | 19 ++++++++++++++++++- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidVersions.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidVersions.cs index bdcb736f9d0..0e51460e810 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidVersions.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidVersions.cs @@ -14,6 +14,7 @@ public class AndroidVersions public IReadOnlyList FrameworkDirectories { get; } public AndroidVersion MaxStableVersion { get; private set; } + public AndroidVersion MinStableVersion { get; private set; } public AndroidVersions (IEnumerable frameworkDirectories) { @@ -62,6 +63,9 @@ void LoadVersions (IEnumerable versions) if (MaxStableVersion == null || (version.Stable && MaxStableVersion.TargetFrameworkVersion < version.TargetFrameworkVersion)) { MaxStableVersion = version; } + if (MinStableVersion == null || (version.Stable && MinStableVersion.TargetFrameworkVersion > version.TargetFrameworkVersion)) { + MinStableVersion = version; + } } } diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/AndroidVersionsTests.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/AndroidVersionsTests.cs index 9d18c098237..f4a2afcfb19 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/AndroidVersionsTests.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/AndroidVersionsTests.cs @@ -47,6 +47,16 @@ public void Constructor_FrameworkDirectories () Directory.CreateDirectory (frameworkDir); try { Directory.CreateDirectory (Path.Combine (frameworkDir, "MonoAndroid")); + Directory.CreateDirectory (Path.Combine (frameworkDir, "MonoAndroid", "v5.1")); + File.WriteAllLines (Path.Combine (frameworkDir, "MonoAndroid", "v5.1", "AndroidApiInfo.xml"), new []{ + "", + " 22", + " 22", + " Marshmallow", + " v5.1", + " True", + "", + }); Directory.CreateDirectory (Path.Combine (frameworkDir, "MonoAndroid", "v6.0")); File.WriteAllLines (Path.Combine (frameworkDir, "MonoAndroid", "v6.0", "AndroidApiInfo.xml"), new []{ "", @@ -67,12 +77,17 @@ public void Constructor_FrameworkDirectories () " False", "", }); - var versions = new AndroidVersions (new [] { Path.Combine (frameworkDir, "MonoAndroid", "v6.0") }); + var versions = new AndroidVersions (new [] { + Path.Combine (frameworkDir, "MonoAndroid", "v5.1"), + Path.Combine (frameworkDir, "MonoAndroid", "v6.0") + }); Assert.IsNotNull (versions.FrameworkDirectories); Assert.AreEqual (1, versions.FrameworkDirectories.Count); Assert.AreEqual (Path.Combine (frameworkDir, "MonoAndroid"), versions.FrameworkDirectories [0]); Assert.IsNotNull (versions.MaxStableVersion); Assert.AreEqual (23, versions.MaxStableVersion.ApiLevel); + Assert.IsNotNull (versions.MinStableVersion); + Assert.AreEqual (22, versions.MinStableVersion.ApiLevel); } finally { Directory.Delete (frameworkDir, recursive: true); @@ -90,6 +105,8 @@ public void Constructor_Versions () Assert.AreEqual (0, versions.FrameworkDirectories.Count); Assert.IsNotNull (versions.MaxStableVersion); Assert.AreEqual (2, versions.MaxStableVersion.ApiLevel); + Assert.IsNotNull (versions.MinStableVersion); + Assert.AreEqual (1, versions.MinStableVersion.ApiLevel); } static AndroidVersions CreateTestVersions () From ca5745db26e0dc428fe31bc1196abedc02b518b3 Mon Sep 17 00:00:00 2001 From: Jonathan Pryor Date: Tue, 5 Dec 2017 11:10:07 -0500 Subject: [PATCH 026/308] Add members for legacy compatibility (#14) The internal `androidtools` repo needs to be updated to use the `xamarin-android-tools` versions of these types instead of its (older, original) versions of these types, and in order to do so we need to reintroduce some `AndroidSdkInfo` members which were removed. Update `AndroidAppManifest` so that `androidtools` functionality can be retained and reimplemented against the new `AndroidAppManifest`. --- .../Xamarin.Android.Tools.AndroidSdk/.DS_Store | Bin 6148 -> 0 bytes .../AndroidAppManifest.cs | 5 +++++ .../AndroidSdkInfo.cs | 15 +++++++++++++++ 3 files changed, 20 insertions(+) delete mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/.DS_Store diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/.DS_Store b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/.DS_Store deleted file mode 100644 index 5008ddfcf53c02e82d7eee2e57c38e5672ef89f6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeH~Jr2S!425mzP>H1@V-^m;4Wg<&0T*E43hX&L&p$$qDprKhvt+--jT7}7np#A3 zem<@ulZcFPQ@L2!n>{z**++&mCkOWA81W14cNZlEfg7;MkzE(HCqgga^y>{tEnwC%0;vJ&^%eQ zLs35+`xjp>T0 aNS; + public static XName NameXName => aName; + + public XDocument Document => doc; + AndroidAppManifest (AndroidVersions versions, XDocument doc) { if (versions == null) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidSdkInfo.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidSdkInfo.cs index cf02c5a38d5..2abb1e9b745 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidSdkInfo.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidSdkInfo.cs @@ -116,5 +116,20 @@ public string JavaSdkPath { public string AndroidNdkHostPlatform { get { return sdk.NdkHostPlatform; } } + + public void SetPreferredAndroidNdkPath (string path) + { + sdk.SetPreferredAndroidNdkPath(path); + } + + public void SetPreferredAndroidSdkPath (string path) + { + sdk.SetPreferredAndroidSdkPath (path); + } + + public void SetPreferredJavaSdkPath (string path) + { + sdk.SetPreferredJavaSdkPath (path); + } } } From 90cf38bc3ce6dc35b5c8ba9c6361da8a645ec693 Mon Sep 17 00:00:00 2001 From: Jonathan Pryor Date: Tue, 9 Jan 2018 07:54:52 -0500 Subject: [PATCH 027/308] Add AndroidVersions.InstalledBindingVersions property (#15) The `AndroidVersions.InstalledBindingVersions` property returns all `AndroidVersion`s for which we have a `Mono.Android.dll` binding assembly. (*Really*, it's for directories which contain an `AndroidApiInfo.xml`, which we *assume* to mean that there is a `Mono.Android.dll` binding assembly.) This property and an associated `AndroidSdkInfo.GetInstalledPlatformVersions()` method -- which returns `AndroidVersion` information for all *installed* API levels within the Android SDK -- allows the internal `androidtools` repo to reimplement the `AndroidSdk.GetInstalledPlatformVersions()` and related methods. --- .../Xamarin.Android.Tools.AndroidSdk/AndroidSdkInfo.cs | 8 ++++++++ .../Xamarin.Android.Tools.AndroidSdk/AndroidVersions.cs | 4 ++++ 2 files changed, 12 insertions(+) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidSdkInfo.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidSdkInfo.cs index 2abb1e9b745..b09e81de267 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidSdkInfo.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidSdkInfo.cs @@ -70,6 +70,14 @@ static Version TryParseVersion (string v) return null; } + public IEnumerable GetInstalledPlatformVersions (AndroidVersions versions) + { + if (versions == null) + throw new ArgumentNullException (nameof (versions)); + return versions.InstalledBindingVersions + .Where (p => TryGetPlatformDirectoryFromApiLevel (p.Id, versions) != null) ; + } + public string GetPlatformDirectory (int apiLevel) { return GetPlatformDirectoryFromId (apiLevel.ToString ()); diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidVersions.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidVersions.cs index 0e51460e810..bf07de022c4 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidVersions.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidVersions.cs @@ -16,6 +16,8 @@ public class AndroidVersions public AndroidVersion MaxStableVersion { get; private set; } public AndroidVersion MinStableVersion { get; private set; } + public IReadOnlyList InstalledBindingVersions { get; private set; } + public AndroidVersions (IEnumerable frameworkDirectories) { if (frameworkDirectories == null) @@ -67,6 +69,8 @@ void LoadVersions (IEnumerable versions) MinStableVersion = version; } } + + InstalledBindingVersions = new ReadOnlyCollection(installedVersions); } public int? GetApiLevelFromFrameworkVersion (string frameworkVersion) From 9921115556cb10e63a67a65cfef3995f2ad7f9ba Mon Sep 17 00:00:00 2001 From: Atsushi Eno Date: Tue, 9 Jan 2018 21:56:02 +0900 Subject: [PATCH 028/308] Add API Level 27. (#13) --- .../src/Xamarin.Android.Tools.AndroidSdk/AndroidVersions.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidVersions.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidVersions.cs index bf07de022c4..2fbfc6911c1 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidVersions.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidVersions.cs @@ -159,6 +159,7 @@ public string GetFrameworkVersionFromId (string id) new AndroidVersion (26, "8.0", "Oreo") { AlternateIds = new[]{ "O" }, }, + new AndroidVersion (27, "8.1", "Oreo"), }; } From 31f229b9e2c535e09f392c9c877b378465224302 Mon Sep 17 00:00:00 2001 From: Jonathan Pryor Date: Tue, 9 Jan 2018 09:16:27 -0500 Subject: [PATCH 029/308] Sign Xamarin.Android.Tools.AndroidSdk.dll. (#16) The `androidtools` repo requires that `Xamarin.Android.Tools.AndroidSdk.dll` be signed in order to use it. The `product.snk` which is used is the same file xamarin-android uses. --- external/xamarin-android-tools/product.snk | Bin 0 -> 596 bytes .../Xamarin.Android.Tools.AndroidSdk.csproj | 2 ++ 2 files changed, 2 insertions(+) create mode 100644 external/xamarin-android-tools/product.snk diff --git a/external/xamarin-android-tools/product.snk b/external/xamarin-android-tools/product.snk new file mode 100644 index 0000000000000000000000000000000000000000..8c04e53be9de10b0b93a858762a6ccde7dd1ca75 GIT binary patch literal 596 zcmV-a0;~N80ssI2Bme+XQ$aES1ONaL0000(io&I=`|)hOD=SB)Ct~t*-<+~}TTPF;b1{7hv6%mFR)5O_04Fitw-g>%N%Si&>{ zSRwtWdJE{F)db&GPArqMxOkFZLQp=PRNwEPm4?~o^NKK6X;^z5;2&%R5HgD_mowWH zMegva_k_~F+ptW3+aeX%(;%V0RKO-_AT*Xf+mET>`|XD5-PrCIW8%vbixYKqk}OM% z+&Yj$#9?yOJI>?x7e&Q(-V zj+t=o2V%=|T&Fia5DhX}Y06y!%W zJlB#>Aipg}OZEQMgqb=}Oq8Z$hvHz90}Mx}obUvcD6L53gpqN8K@*~L>|VSP*k}WC z5atuLdt0s#;|OACYKz^;jFE!EQf(>@>Ke zQg8oCN$DFQqP<^XPrUgvtZtCALBAjnC{g&EC6F_++=)Uo{56ouZv~$$M&=3gocLg1 ziBf^E>~2capQt02#+x<@96{-@XP0eB)+OEr_!lnYQOI_3liA{$Zkg-_A1Uoh5>|tQ ipO_W%UW4{Ol(<6x7pyuYYn@jSz!JD1zG&IMEAfC3aUh2P literal 0 HcmV?d00001 diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj index 07a1cab3393..df789690701 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj @@ -10,6 +10,8 @@ v4.5 8.0.30703 2.0 + true + ..\..\product.snk true From f8361783e0f2a9c75c52585589cfae7b7104c5a5 Mon Sep 17 00:00:00 2001 From: Jonathan Pryor Date: Mon, 15 Jan 2018 07:44:47 -0500 Subject: [PATCH 030/308] Allow AndroidSdkInfo.SetPreferred*Path() to work (#17) Commit ca5745db added `AndroidSdkInfo.SetPreferredAndroidNdkPath()`, `AndroidSdkInfo.SetPreferredAndroidSdkPath()`, and `AndroidSdkInfo.SetPreferredJavaSdkPath()`. The problem is, these methods are not usable in all scenarios. The purpose of these methods is to allow e.g. the IDE to allow the developer to alter the preferred Android NDK, SDK, or JDK paths. Implicit in this purpose is that the *existing* paths may be invalid. Thus, if we assume that there is no Android SDK or JDK known by `AndroidSdkInfo`, but "someone" knows of one and wants to use that in the future, the obvious thing to do would be to call e.g. `AndroidSdkInfo.SetPreferredAndroidSdkPath()`. Therein lines the problem: these are *instance* methods, and the `AndroidSdkInfo` constructor *requires* that *all* paths be valid, otherwise the constructor may throw an `InvalidOperationException`. This renders `SetPreferredAndroidSdkPath()` largely useless: you would need to construct `AndroidSdkInfo` with a known instance in order to set a preferred location, which is a tad backwards. Turn `AndroidSdkInfo.SetPreferredAndroidNdkPath()`, `AndroidSdkInfo.SetPreferredAndroidSdkPath()`, and `AndroidSdkInfo.SetPreferredJavaSdkPath()` into `static` methods, so that they may be called without constructing an `AndroidApiInfo`. --- .../AndroidSdkInfo.cs | 38 ++++++++++++++++--- 1 file changed, 32 insertions(+), 6 deletions(-) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidSdkInfo.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidSdkInfo.cs index b09e81de267..e1920fe98ff 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidSdkInfo.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidSdkInfo.cs @@ -15,9 +15,7 @@ public AndroidSdkInfo (Action logger, string androidSdkPath if (logger == null) throw new ArgumentNullException (nameof (logger)); - sdk = OS.IsWindows - ? (AndroidSdkBase) new AndroidSdkWindows (logger) - : (AndroidSdkBase) new AndroidSdkUnix (logger); + sdk = CreateSdk (logger); sdk.Initialize (androidSdkPath, androidNdkPath, javaSdkPath); // shouldn't happen, in that sdk.Initialize() should throw instead @@ -27,6 +25,13 @@ public AndroidSdkInfo (Action logger, string androidSdkPath throw new InvalidOperationException ($"Could not determine Java SDK location. Please provide `{nameof (javaSdkPath)}`."); } + static AndroidSdkBase CreateSdk (Action logger) + { + return OS.IsWindows + ? (AndroidSdkBase) new AndroidSdkWindows (logger) + : (AndroidSdkBase) new AndroidSdkUnix (logger); + } + public IEnumerable GetBuildToolsPaths (string preferredBuildToolsVersion) { if (!string.IsNullOrEmpty (preferredBuildToolsVersion)) { @@ -125,18 +130,39 @@ public string AndroidNdkHostPlatform { get { return sdk.NdkHostPlatform; } } - public void SetPreferredAndroidNdkPath (string path) + public static void SetPreferredAndroidNdkPath (string path, Action logger = null) { + logger = logger ?? DefaultConsoleLogger; + + var sdk = CreateSdk (logger); sdk.SetPreferredAndroidNdkPath(path); } - public void SetPreferredAndroidSdkPath (string path) + static void DefaultConsoleLogger (TraceLevel level, string message) { + switch (level) { + case TraceLevel.Error: + Console.Error.WriteLine (message); + break; + default: + Console.WriteLine (message); + break; + } + } + + public static void SetPreferredAndroidSdkPath (string path, Action logger = null) + { + logger = logger ?? DefaultConsoleLogger; + + var sdk = CreateSdk (logger); sdk.SetPreferredAndroidSdkPath (path); } - public void SetPreferredJavaSdkPath (string path) + public static void SetPreferredJavaSdkPath (string path, Action logger = null) { + logger = logger ?? DefaultConsoleLogger; + + var sdk = CreateSdk (logger); sdk.SetPreferredJavaSdkPath (path); } } From fb7a386f0b9e010c7aade73a6147003909ff1279 Mon Sep 17 00:00:00 2001 From: Jonathan Pryor Date: Thu, 18 Jan 2018 04:35:31 -0500 Subject: [PATCH 031/308] OSVersion is an alias for Framework Version (#18) Context: https://github.com/xamarin/androidtools/pull/87 Commit 07ca557c assumed that "OS Version" and "Framework Version" were two related yet distinct constructs, such that e.g. `AndroidVersions.GetIdFromFrameworkVersion()` only needed to worry about "Framework Versions". There was no functionality to look up *anything* by "OS Version", though such a thing could now be cobbled together via LINQ, `AndroidVersions.InstalledBindingVersions`, and `AndroidVersion.OSVersion`. Unfortunately, when it came time to update `androidtools` to use `xamarin-android-tools` (this repo), that overlooked assumption reared its head, cause many unit tests to fail *because* there was an underlying need to lookup API levels/IDs/etc. via OS Version. This leaves a choice: 1. "Unify" OS Version and Framework Version *here*, so that e.g. `AndroidVersions.GetIdFromFrameworkVersion()` can work with OS Versions, or 2. Keep them separate, and add a set of "overloads" to `AndroidVersions` which deal with OS Versions, or 3. Keep this repo "clean," and add the appropriate LINQ-fu to `androidtools` so that lookups by OS Name continue to work, while the xamarin-android-tools repo is unchanged. Given that OS Version and Framework Version are *already* related -- `AndroidVersion.FrameworkVersion` is `"v" + osVersion`! -- *and* that I have doubts about whether the LINQ approach will actually work, update the semantics of `AndroidVersions.Get*FromFrameworkVersion()` so that they accept OS Versions in addition to Framework Versions. --- .../AndroidVersions.cs | 14 ++++++++++---- .../Tests/AndroidVersionsTests.cs | 12 ++++++++++++ 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidVersions.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidVersions.cs index 2fbfc6911c1..f0cc974b6c3 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidVersions.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidVersions.cs @@ -75,8 +75,14 @@ void LoadVersions (IEnumerable versions) public int? GetApiLevelFromFrameworkVersion (string frameworkVersion) { - return installedVersions.FirstOrDefault (v => v.FrameworkVersion == frameworkVersion)?.ApiLevel ?? - KnownVersions.FirstOrDefault (v => v.FrameworkVersion == frameworkVersion)?.ApiLevel; + return installedVersions.FirstOrDefault (v => MatchesFrameworkVersion (v, frameworkVersion))?.ApiLevel ?? + KnownVersions.FirstOrDefault (v => MatchesFrameworkVersion (v, frameworkVersion))?.ApiLevel; + } + + static bool MatchesFrameworkVersion (AndroidVersion version, string frameworkVersion) + { + return version.FrameworkVersion == frameworkVersion || + version.OSVersion == frameworkVersion; } public int? GetApiLevelFromId (string id) @@ -110,8 +116,8 @@ public string GetIdFromApiLevel (string apiLevel) public string GetIdFromFrameworkVersion (string frameworkVersion) { - return installedVersions.FirstOrDefault (v => v.FrameworkVersion == frameworkVersion)?.Id ?? - KnownVersions.FirstOrDefault (v => v.FrameworkVersion == frameworkVersion)?.Id; + return installedVersions.FirstOrDefault (v => MatchesFrameworkVersion (v, frameworkVersion))?.Id ?? + KnownVersions.FirstOrDefault (v => MatchesFrameworkVersion (v, frameworkVersion))?.Id; } public string GetFrameworkVersionFromApiLevel (int apiLevel) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/AndroidVersionsTests.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/AndroidVersionsTests.cs index f4a2afcfb19..b9da4962aa2 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/AndroidVersionsTests.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/AndroidVersionsTests.cs @@ -127,13 +127,19 @@ public void GetApiLevelFromFrameworkVersion () Assert.AreEqual (null, versions.GetApiLevelFromFrameworkVersion (null)); Assert.AreEqual (1, versions.GetApiLevelFromFrameworkVersion ("v1.0")); + Assert.AreEqual (1, versions.GetApiLevelFromFrameworkVersion ("1.0")); Assert.AreEqual (2, versions.GetApiLevelFromFrameworkVersion ("v1.1")); + Assert.AreEqual (2, versions.GetApiLevelFromFrameworkVersion ("1.1")); Assert.AreEqual (3, versions.GetApiLevelFromFrameworkVersion ("v1.2")); + Assert.AreEqual (3, versions.GetApiLevelFromFrameworkVersion ("1.2")); Assert.AreEqual (null, versions.GetApiLevelFromFrameworkVersion ("v1.3")); + Assert.AreEqual (null, versions.GetApiLevelFromFrameworkVersion ("1.3")); Assert.AreEqual (14, versions.GetApiLevelFromFrameworkVersion ("v4.0")); + Assert.AreEqual (14, versions.GetApiLevelFromFrameworkVersion ("4.0")); // via KnownVersions Assert.AreEqual (4, versions.GetApiLevelFromFrameworkVersion ("v1.6")); + Assert.AreEqual (4, versions.GetApiLevelFromFrameworkVersion ("1.6")); } [Test] @@ -192,14 +198,20 @@ public void GetIdFromFrameworkVersion () Assert.AreEqual (null, versions.GetIdFromFrameworkVersion (null)); Assert.AreEqual ("A", versions.GetIdFromFrameworkVersion ("v1.0")); + Assert.AreEqual ("A", versions.GetIdFromFrameworkVersion ("1.0")); Assert.AreEqual ("B", versions.GetIdFromFrameworkVersion ("v1.1")); + Assert.AreEqual ("B", versions.GetIdFromFrameworkVersion ("1.1")); Assert.AreEqual ("C", versions.GetIdFromFrameworkVersion ("v1.2")); + Assert.AreEqual ("C", versions.GetIdFromFrameworkVersion ("1.2")); Assert.AreEqual ("II", versions.GetIdFromFrameworkVersion ("v4.0")); + Assert.AreEqual ("II", versions.GetIdFromFrameworkVersion ("4.0")); + Assert.AreEqual (null, versions.GetIdFromFrameworkVersion ("v0.99")); Assert.AreEqual (null, versions.GetIdFromFrameworkVersion ("0.99")); // via KnownVersions Assert.AreEqual ("10", versions.GetIdFromFrameworkVersion ("v2.3")); + Assert.AreEqual ("10", versions.GetIdFromFrameworkVersion ("2.3")); } [Test] From 2855fc9ee8a1f3e23332bc4d04df0732524ff96c Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Thu, 1 Mar 2018 15:01:56 -0600 Subject: [PATCH 032/308] [windows] fallback path for Android SDK (#20) In cases where `AndroidSdkInfo` is used outside of Visual Studio, such as Embeddinator-4000, a new fallback path for the Android SDK is needed on Windows. At some point in the past, Visual Studio (or Xamarin) Installer installed to: C:\Program Files (x86)\Android\android-sdk-windows Now it is installed to (VS 2017): C:\Program Files (x86)\Android\android-sdk Helps fix https://github.com/mono/Embeddinator-4000/issues/599 Changes: - Added another fallback path for the Android SDK, mirrored what I did in the past for VS 2017 and the Android NDK - Added .vs/ to the .gitignore for Windows developers --- external/xamarin-android-tools/.gitignore | 1 + .../Sdks/AndroidSdkWindows.cs | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/external/xamarin-android-tools/.gitignore b/external/xamarin-android-tools/.gitignore index dc27d996fb2..ff2e62b0164 100644 --- a/external/xamarin-android-tools/.gitignore +++ b/external/xamarin-android-tools/.gitignore @@ -12,3 +12,4 @@ bin **/obj packages/ TestResult-*.xml +.vs/ \ No newline at end of file diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkWindows.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkWindows.cs index a88cf6295b3..188b5672deb 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkWindows.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkWindows.cs @@ -89,7 +89,8 @@ protected override IEnumerable GetAllAvailableAndroidSdks () // Check some hardcoded paths for good measure var xamarin_private = Path.Combine (Environment.GetFolderPath (Environment.SpecialFolder.LocalApplicationData), "Xamarin", "MonoAndroid", "android-sdk-windows"); var android_default = Path.Combine (OS.ProgramFilesX86, "Android", "android-sdk-windows"); - var cdrive_default = @"C:\android-sdk-windows"; + var vs_2017_default = Path.Combine (OS.ProgramFilesX86, "Android", "android-sdk"); + var cdrive_default = @"C:\android-sdk-windows"; if (ValidateAndroidSdkLocation (xamarin_private)) yield return xamarin_private; @@ -97,6 +98,9 @@ protected override IEnumerable GetAllAvailableAndroidSdks () if (ValidateAndroidSdkLocation (android_default)) yield return android_default; + if (ValidateAndroidSdkLocation (vs_2017_default)) + yield return vs_2017_default; + if (ValidateAndroidSdkLocation (cdrive_default)) yield return cdrive_default; } From d8e2549532450695b89f73f41a84f567a4d7a4f3 Mon Sep 17 00:00:00 2001 From: Dean Ellis Date: Thu, 22 Mar 2018 17:35:02 +0000 Subject: [PATCH 033/308] Add back in missing default Paths (#21) During the implementation of xamarin-android-tools we lost the changes for [androidtools][1]. These are needed on windows to ensure we are using legacy install locations of the SDK/NDK if they exist. [1]: https://github.com/xamarin/androidtools/pull/57 --- .../Sdks/AndroidSdkWindows.cs | 35 +++++++++---------- 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkWindows.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkWindows.cs index 188b5672deb..a6bd209bdee 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkWindows.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkWindows.cs @@ -87,22 +87,19 @@ protected override IEnumerable GetAllAvailableAndroidSdks () yield return RegistryEx.GetValueString (root, ANDROID_INSTALLER_PATH, ANDROID_INSTALLER_KEY, wow); // Check some hardcoded paths for good measure - var xamarin_private = Path.Combine (Environment.GetFolderPath (Environment.SpecialFolder.LocalApplicationData), "Xamarin", "MonoAndroid", "android-sdk-windows"); - var android_default = Path.Combine (OS.ProgramFilesX86, "Android", "android-sdk-windows"); - var vs_2017_default = Path.Combine (OS.ProgramFilesX86, "Android", "android-sdk"); - var cdrive_default = @"C:\android-sdk-windows"; - - if (ValidateAndroidSdkLocation (xamarin_private)) - yield return xamarin_private; - - if (ValidateAndroidSdkLocation (android_default)) - yield return android_default; - - if (ValidateAndroidSdkLocation (vs_2017_default)) - yield return vs_2017_default; - - if (ValidateAndroidSdkLocation (cdrive_default)) - yield return cdrive_default; + var paths = new string [] { + Path.Combine (Environment.GetFolderPath (Environment.SpecialFolder.LocalApplicationData), "Xamarin", "MonoAndroid", "android-sdk-windows"), + Path.Combine (Environment.GetFolderPath (Environment.SpecialFolder.ProgramFilesX86), "Android", "android-sdk"), + Path.Combine (Environment.GetFolderPath (Environment.SpecialFolder.ProgramFilesX86), "Android", "android-sdk-windows"), + Path.Combine (Environment.GetFolderPath (Environment.SpecialFolder.ProgramFiles), "Android", "android-sdk"), + Path.Combine (Environment.GetFolderPath (Environment.SpecialFolder.LocalApplicationData), "Android", "android-sdk"), + Path.Combine (Environment.GetFolderPath (Environment.SpecialFolder.CommonApplicationData), "Android", "android-sdk"), + @"C:\android-sdk-windows" + }; + foreach (var basePath in paths) + if (Directory.Exists (basePath)) + if (ValidateAndroidSdkLocation (basePath)) + yield return basePath; } protected override string GetJavaSdkPath () @@ -167,11 +164,13 @@ protected override IEnumerable GetAllAvailableAndroidNdks () // Check some hardcoded paths for good measure var xamarin_private = Path.Combine (Environment.GetFolderPath (Environment.SpecialFolder.LocalApplicationData), "Xamarin", "MonoAndroid"); - var android_default = Path.Combine (OS.ProgramFilesX86, "Android"); + var vs_default = Path.Combine (Environment.GetFolderPath (Environment.SpecialFolder.CommonApplicationData), "Microsoft", "AndroidNDK"); + var vs_default32bit = Path.Combine (Environment.GetFolderPath (Environment.SpecialFolder.CommonApplicationData), "Microsoft", "AndroidNDK32"); var vs_2017_default = Path.Combine (Environment.GetFolderPath (Environment.SpecialFolder.CommonApplicationData), "Microsoft", "AndroidNDK64"); + var android_default = Path.Combine (OS.ProgramFilesX86, "Android"); var cdrive_default = @"C:\"; - foreach (var basePath in new string [] {xamarin_private, android_default, vs_2017_default, cdrive_default}) + foreach (var basePath in new string [] {xamarin_private, android_default, vs_default, vs_default32bit, vs_2017_default, cdrive_default}) if (Directory.Exists (basePath)) foreach (var dir in Directory.GetDirectories (basePath, "android-ndk-r*")) if (ValidateAndroidNdkLocation (dir)) From e551283665b3e7201e34bee6be8f662d0053b40f Mon Sep 17 00:00:00 2001 From: Dean Ellis Date: Wed, 4 Apr 2018 17:13:37 +0100 Subject: [PATCH 034/308] Add Missing code to check old paths --- .../Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkUnix.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkUnix.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkUnix.cs index f77079bbfe3..b6b54513328 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkUnix.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkUnix.cs @@ -80,6 +80,11 @@ protected override IEnumerable GetAllAvailableAndroidSdks () if (ValidateAndroidSdkLocation (dir)) yield return dir; } + + // Check some hardcoded paths for good measure + var macSdkPath = Path.Combine (Environment.GetFolderPath (Environment.SpecialFolder.Personal), "Library", "Android", "sdk"); + if (ValidateAndroidSdkLocation (macSdkPath)) + yield return macSdkPath; } protected override string GetJavaSdkPath () From c7cd10f22789371c869bd171354418ad07fda5fd Mon Sep 17 00:00:00 2001 From: Sergey Shakhnazarov Date: Fri, 6 Apr 2018 13:36:50 +0300 Subject: [PATCH 035/308] Expose AllAndroidSdks as VS4Mac Installer depends on it (#24) It is needed to allow user to select between existing/new SDK locations --- .../src/Xamarin.Android.Tools.AndroidSdk/AndroidSdkInfo.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidSdkInfo.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidSdkInfo.cs index e1920fe98ff..57dcf2dd493 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidSdkInfo.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidSdkInfo.cs @@ -122,6 +122,12 @@ public string AndroidSdkPath { get { return sdk.AndroidSdkPath; } } + public string [] AllAndroidSdkPaths { + get { + return sdk.AllAndroidSdks ?? new string [0]; + } + } + public string JavaSdkPath { get { return sdk.JavaSdkPath; } } From 9024cb924339c00f47f88fdd4724ecb0c3a07886 Mon Sep 17 00:00:00 2001 From: Dean Ellis Date: Mon, 16 Apr 2018 19:43:11 +0100 Subject: [PATCH 036/308] [Xamarin.Android.Tools.AndroidSdk] Unstable framework versions are used (#25) Context: https://github.com/xamarin/xamarin-android/issues/1511 Context: https://github.com/xamarin/xamarin-android/issues/1512 Within xamarin-android, when building with `$(AndroidUseLatestPlatformSdk)`=True -- which is supposed to use the latest *stable* API level -- builds would instead use the latest API level, [*even if unstable*][0]: [0]: http://xqa.blob.core.windows.net/gist/log-83bd0e75034940db9ae887a8f5c03d98.txt ResolveSdksTask: (TaskId:7) AndroidApiLevel: (TaskId:7) TargetFrameworkVersion: v8.0 (TaskId:7) UseLatestAndroidPlatformSdk: True (TaskId:7) ... ResolveSdksTask Outputs: (TaskId:7) AndroidApiLevel: 127 (TaskId:7) AndroidApiLevelName: Z (TaskId:7) TargetFrameworkVersion: v108.1.99 (TaskId:7) SupportedApiLevel: 127 (TaskId:7) ... Note that in the repro, API-127 ("Z") is *Unstable* (as per `AndroidApiInfo.xml`), yet it's used for the build. The cause is due to our logic to calculate `AndroidVersions.MaxStableVersion`. The first time through the `AndroidVersions.LoadVersions()` method the `AndroidVersions.MaxStableVersion` property will *always* be `null`. As a result the *first* `version` value would *always* be assigned to `AndroidVersions.MaxStableVersion`, even if not stable! // Old and busted foreach (var version in versions) { installedVersions.Add (version); if (MaxStableVersion == null || (version.Stable && MaxStableVersion.TargetFrameworkVersion < version.TargetFrameworkVersion)) { MaxStableVersion = version; ... Whether or not `AndroidVersions.MaxStableVersion` *could* be unstable was dependent on the order of directory traversal, which isn't sorted (nor should it be!), meaning behavior could *also be random*. (Fun!) If `AndroidVersions.MaxStableVersion` *were* an unstable API level, xamarin-android builds when `$(AndroidUseLatestPlatformSdk)`=True would be unpredictable, as an API binding with no API compatibility requirements could be used. The fix is to completely ignore unstable apis. This way the first stable api we find will be the first value we assign. --- .../AndroidVersions.cs | 6 +++-- .../Tests/AndroidVersionsTests.cs | 23 ++++++++++++++++++- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidVersions.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidVersions.cs index f0cc974b6c3..75cd2cdb93f 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidVersions.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidVersions.cs @@ -62,10 +62,12 @@ void LoadVersions (IEnumerable versions) { foreach (var version in versions) { installedVersions.Add (version); - if (MaxStableVersion == null || (version.Stable && MaxStableVersion.TargetFrameworkVersion < version.TargetFrameworkVersion)) { + if (!version.Stable) + continue; + if (MaxStableVersion == null || (MaxStableVersion.TargetFrameworkVersion < version.TargetFrameworkVersion)) { MaxStableVersion = version; } - if (MinStableVersion == null || (version.Stable && MinStableVersion.TargetFrameworkVersion > version.TargetFrameworkVersion)) { + if (MinStableVersion == null || (MinStableVersion.TargetFrameworkVersion > version.TargetFrameworkVersion)) { MinStableVersion = version; } } diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/AndroidVersionsTests.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/AndroidVersionsTests.cs index b9da4962aa2..a3cd293cb79 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/AndroidVersionsTests.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/AndroidVersionsTests.cs @@ -39,6 +39,16 @@ public void Constructor_NoVersions () Assert.AreEqual (0, versions.FrameworkDirectories.Count); } + [Test] + public void Contructor_UnstableVersions () + { + var versions = new AndroidVersions ( + new [] { new AndroidVersion (apiLevel: 100, osVersion: "100.0", codeName: "Test", id: "Z", stable: false) } + ); + Assert.IsNull (versions.MaxStableVersion); + Assert.IsNull (versions.MinStableVersion); + } + [Test] public void Constructor_FrameworkDirectories () { @@ -77,9 +87,20 @@ public void Constructor_FrameworkDirectories () " False", "", }); + Directory.CreateDirectory (Path.Combine (frameworkDir, "MonoAndroid", "v108.1.99")); + File.WriteAllLines (Path.Combine (frameworkDir, "MonoAndroid", "v108.1.99", "AndroidApiInfo.xml"), new []{ + "", + " Z", + " 127", + " Z", + " v108.1.99", + " False", + "", + }); var versions = new AndroidVersions (new [] { Path.Combine (frameworkDir, "MonoAndroid", "v5.1"), - Path.Combine (frameworkDir, "MonoAndroid", "v6.0") + Path.Combine (frameworkDir, "MonoAndroid", "v6.0"), + Path.Combine (frameworkDir, "MonoAndroid", "v108.1.99"), }); Assert.IsNotNull (versions.FrameworkDirectories); Assert.AreEqual (1, versions.FrameworkDirectories.Count); From 0efa4235847e77396a02edd74327a82ea63a3f4b Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Tue, 22 May 2018 17:06:14 +0200 Subject: [PATCH 037/308] Fix ~/.config/xbuild/* ownership on creation when under sudo Fixes: https://devdiv.visualstudio.com/DevDiv/_workitems/edit/597752 Whenever (for whatever reason) user runs the Visual Studio for Mac installer as root using `sudo`, ownership of the `~/.config/xbuild` directory as well as the configuration file created inside it will be set to root:root. This is due to a couple of facts: * `sudo` (without parameters) switches the user but doesn't reset the environment variables. In effect the `$HOME` variable will point to home directory of the user running `sudo`, not root * Mono's `Environment.GetFolderPath` uses `$HOME` to find the location of the user's home directory. The outcome of the above is that if Xamarin.Android.Tools create the directory and the config file from scratch, their ownership is going to be incorrect and that causes the Visual Studio for Mac installer to fail. In order to fix this issue we detect the above situation and make sure that the ownership of the directory/file is reset to the original user's ID. This is done by calling the `chown` Unix utility because this way we avoid the complexity of calling a number of Unix system functions (getpwent, stat, chown) and the ownership is reset in the most compatible way. --- .../Sdks/AndroidSdkUnix.cs | 112 ++++++++++++++++-- 1 file changed, 104 insertions(+), 8 deletions(-) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkUnix.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkUnix.cs index b6b54513328..0fb5cfda489 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkUnix.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkUnix.cs @@ -2,6 +2,8 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; +using System.Threading; +using System.Threading.Tasks; using System.Xml; using System.Xml.Linq; @@ -9,6 +11,23 @@ namespace Xamarin.Android.Tools { class AndroidSdkUnix : AndroidSdkBase { + // See comments above UnixConfigPath for explanation on why these are needed + static readonly string sudo_user; + static readonly string user; + static readonly bool need_chown; + + static AndroidSdkUnix () + { + sudo_user = Environment.GetEnvironmentVariable ("SUDO_USER"); + if (String.IsNullOrEmpty (sudo_user)) + return; + + user = Environment.GetEnvironmentVariable ("USER"); + if (String.IsNullOrEmpty (user) || String.Compare (user, sudo_user, StringComparison.Ordinal) == 0) + return; + need_chown = true; + } + public AndroidSdkUnix (Action logger) : base (logger) { @@ -166,7 +185,7 @@ public override void SetPreferredAndroidSdkPath (string path) } androidEl.SetAttributeValue ("path", path); - doc.Save (UnixConfigPath); + SaveConfig (doc); } public override void SetPreferredJavaSdkPath (string path) @@ -182,7 +201,7 @@ public override void SetPreferredJavaSdkPath (string path) } javaEl.SetAttributeValue ("path", path); - doc.Save (UnixConfigPath); + SaveConfig (doc); } public override void SetPreferredAndroidNdkPath (string path) @@ -198,9 +217,57 @@ public override void SetPreferredAndroidNdkPath (string path) } androidEl.SetAttributeValue ("path", path); - doc.Save (UnixConfigPath); + SaveConfig (doc); } + void SaveConfig (XDocument doc) + { + string cfg = UnixConfigPath; + List created = null; + + if (!File.Exists (cfg)) { + string dir = Path.GetDirectoryName (cfg); + if (!Directory.Exists (dir)) { + Directory.CreateDirectory (dir); + AddToList (dir); + } + AddToList (cfg); + } + doc.Save (cfg); + FixOwnership (created); + + void AddToList (string path) + { + if (created == null) + created = new List (); + created.Add (path); + } + } + + // There's a small problem with the code below. Namely, if it runs under `sudo` the folder location + // returned by Environment.GetFolderPath will depend on how sudo was invoked: + // + // 1. `sudo command` will not reset the environment and while the user running the command will be + // `root` (or any other user specified in the command), the `$HOME` environment variable will point + // to the original user's home. The effect will be that any files/directories created in this + // session will be owned by `root` (or any other user as above) and not the original user. Thus, on + // return, the original user will not have write (or read/write) access to the directory/file + // created. This causes https://devdiv.visualstudio.com/DevDiv/_workitems/edit/597752 + // + // 2. `sudo -i command` starts an "interactive" session which resets the environment (by reading shell + // startup scripts among other steps) and the above problem doesn't occur. + // + // The behavior of 1. is, arguably, a bug in Mono fixing of which may bring unknown side effects, + // however. Therefore we'll do our best below to work around the issue. `sudo` puts the original user's + // login name in the `SUDO_USER` environment variable and we can use its presence to both detect 1. + // above and work around the issue. We will do it in the simplest possible manner, by invoking chown(1) + // to set the proper ownership. + // Note that it will NOT fix situations when a mechanism other than `sudo`, but with similar effects, is + // used! The generic fix would require a number of more complicated checks as well as a number of + // p/invokes (with quite a bit of data marshaling) and it is likely that it would be mostly wasted + // effort, as the sudo situation appears to be the most common (while happening few and far between in + // general) + // private static string UnixConfigPath { get { var p = Environment.GetFolderPath (Environment.SpecialFolder.ApplicationData); @@ -212,11 +279,7 @@ private XDocument GetUnixConfigFile () { var file = UnixConfigPath; XDocument doc = null; - if (!File.Exists (file)) { - string dir = Path.GetDirectoryName (file); - if (!Directory.Exists (dir)) - Directory.CreateDirectory (dir); - } else { + if (File.Exists (file)) { try { doc = XDocument.Load (file); } catch (Exception ex) { @@ -240,5 +303,38 @@ private XDocument GetUnixConfigFile () return doc; } + void FixOwnership (List paths) + { + if (!need_chown || paths == null || paths.Count == 0) + return; + + var stdout = new StringWriter (); + var stderr = new StringWriter (); + var args = new List { + QuoteString (sudo_user) + }; + + foreach (string p in paths) + args.Add (QuoteString (p)); + + var psi = new ProcessStartInfo (OS.IsMac ? "/usr/sbin/chown" : "/bin/chown") { + CreateNoWindow = true, + Arguments = String.Join (" ", args), + }; + Logger (TraceLevel.Verbose, $"Changing filesystem object ownership: {psi.FileName} {psi.Arguments}"); + Task chown_task = ProcessUtils.StartProcess (psi, stdout, stderr, System.Threading.CancellationToken.None); + + if (chown_task.Result != 0) { + Logger (TraceLevel.Warning, $"Failed to change ownership of filesystem object(s)"); + Logger (TraceLevel.Verbose, $"standard output: {stdout}"); + Logger (TraceLevel.Verbose, $"standard error: {stderr}"); + } + + string QuoteString (string p) + { + return $"\"{p}\""; + } + } + } } From 17e218fbf4df0e6036a1493b417dd8df0feabff0 Mon Sep 17 00:00:00 2001 From: Juan Marcelo Tondato Date: Thu, 19 Jul 2018 16:28:45 -0300 Subject: [PATCH 038/308] [Xamarin.Android.Tools.AndroidSdk] Export JAVA_HOME, PATH on Windows (#30) Context: https://paper.dropbox.com/doc/OpenJDK-and-You--AH1yWKdVXgno~uXYfmcUAZTwAg-NoECAe2XkBQeoxFfGL6ea On Windows, set the `%JAVA_HOME%` and `%PATH%` environment variables so that they contain the `AndroidSdkInfo.JavaSdkPath` value. This ensures that related tools *launched from Visual Studio*, such as the Android Device Manager, use the same `AndroidSdkInfo.JavaSdkPath` value as the rest of the Xamarin.Android tooling, and not whatever system-wide `%JAVA_HOME%` value may have been previously set. --- .../Sdks/AndroidSdkWindows.cs | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkWindows.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkWindows.cs index a6bd209bdee..230a0c0c2b2 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkWindows.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkWindows.cs @@ -222,5 +222,26 @@ private bool CheckRegistryKeyForExecutable (UIntPtr key, string subkey, string v return true; } #endregion + + public override void Initialize (string androidSdkPath = null, string androidNdkPath = null, string javaSdkPath = null) + { + base.Initialize (androidSdkPath, androidNdkPath, javaSdkPath); + + var jdkPath = JavaSdkPath; + if (!string.IsNullOrEmpty (jdkPath)) { + var cur = Environment.GetEnvironmentVariable ("JAVA_HOME"); + if (!string.IsNullOrEmpty (cur)) + Environment.SetEnvironmentVariable ("JAVA_HOME", jdkPath); + + var javaBinPath = this.JavaBinPath; + if (!string.IsNullOrEmpty (javaBinPath)) { + var environmentPath = Environment.GetEnvironmentVariable ("PATH"); + if (!environmentPath.Contains (javaBinPath)) { + var processPath = string.Concat (javaBinPath, Path.PathSeparator, environmentPath); + Environment.SetEnvironmentVariable ("PATH", processPath); + } + } + } + } } } From c13f0655fd6ac9136c636084b125b623dfaad934 Mon Sep 17 00:00:00 2001 From: Juan Marcelo Tondato Date: Thu, 19 Jul 2018 16:32:44 -0300 Subject: [PATCH 039/308] [Xamarin.Android.Tools.AndroidSdk] Prefer OpenJDK path on Windows (#31) Context: https://paper.dropbox.com/doc/OpenJDK-and-You--AH1yWKdVXgno~uXYfmcUAZTwAg-NoECAe2XkBQeoxFfGL6ea On Windows, as part of the Visual Studio installer, we can install a Microsoft-maintained OpenJDK. (This is done principally because Google's Android SDK tooling still requires JDK 1.8, and Oracle will be making it more difficult to download and install JDK 1.8 at a future point in time. We have deemed it easier to maintain and install our own OpenJDK 1.8 build so that Google's Android SDK tooling can continue to run.) Microsoft's OpenJDK install has priority when determining a default JDK for developers. --- .../Sdks/AndroidSdkWindows.cs | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkWindows.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkWindows.cs index 230a0c0c2b2..5977ec21d81 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkWindows.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkWindows.cs @@ -103,6 +103,19 @@ protected override IEnumerable GetAllAvailableAndroidSdks () } protected override string GetJavaSdkPath () + { + var preferredJdkPath = GetPreferredJdkPath (); + if (!string.IsNullOrEmpty (preferredJdkPath)) + return preferredJdkPath; + + var openJdkPath = GetOpenJdkPath (); + if (!string.IsNullOrEmpty (openJdkPath)) + return openJdkPath; + + return GetOracleJdkPath (); + } + + private string GetPreferredJdkPath () { // check the user specified path var roots = new[] { RegistryEx.CurrentUser, RegistryEx.LocalMachine }; @@ -114,6 +127,26 @@ protected override string GetJavaSdkPath () return RegistryEx.GetValueString (root, regKey, MDREG_JAVA_SDK, wow); } + return null; + } + + private string GetOpenJdkPath () + { + var root = RegistryEx.LocalMachine; + var wows = new[] { RegistryEx.Wow64.Key32, RegistryEx.Wow64.Key64 }; + var subKey = @"SOFTWARE\Microsoft\VisualStudio\Android"; + var valueName = "JavaHome"; + + foreach (var wow in wows) { + if (CheckRegistryKeyForExecutable (root, subKey, valueName, wow, "bin", JarSigner)) + return RegistryEx.GetValueString (root, subKey, valueName, wow); + } + + return null; + } + + private string GetOracleJdkPath () + { string subkey = @"SOFTWARE\JavaSoft\Java Development Kit"; Logger (TraceLevel.Info, "Looking for Java 6 SDK..."); From 2f24c3f1bdb97f1ad0232933c0a0ea2fddd3e93a Mon Sep 17 00:00:00 2001 From: Jonathan Pryor Date: Mon, 23 Jul 2018 20:55:53 -0400 Subject: [PATCH 040/308] [Xamarin.Android.Tools.AndroidSdk] Add JdkInfo Fixes: https://github.com/xamarin/xamarin-android-tools/issues/26 Context: https://github.com/xamarin/xamarin-android-tools/pull/29#issuecomment-400048306 Refactor out the [underlying functionality][0] of the [`` task][1] so that it is more easily usable. [0]: https://github.com/xamarin/java.interop/commit/4bd9297f311b3e0fb3e07335507a38278b83d255 [1]: https://github.com/xamarin/java.interop/blob/master/src/Java.Interop.BootstrapTasks/Java.Interop.BootstrapTasks/JdkInfo.cs --- .../JdkInfo.cs | 418 ++++++++++++++++++ .../Xamarin.Android.Tools.AndroidSdk/OS.cs | 9 + .../ProcessUtils.cs | 69 ++- .../Sdks/AndroidSdkBase.cs | 45 +- .../Sdks/AndroidSdkUnix.cs | 24 +- .../Sdks/AndroidSdkWindows.cs | 85 ++-- .../Tests/JdkInfoTests.cs | 159 +++++++ ...arin.Android.Tools.AndroidSdk-Tests.csproj | 1 + .../Xamarin.Android.Tools.AndroidSdk.csproj | 1 + 9 files changed, 720 insertions(+), 91 deletions(-) create mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/JdkInfo.cs create mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/JdkInfoTests.cs diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/JdkInfo.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/JdkInfo.cs new file mode 100644 index 00000000000..a30f5182768 --- /dev/null +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/JdkInfo.cs @@ -0,0 +1,418 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading; +using System.Xml; +using System.Xml.Linq; + +namespace Xamarin.Android.Tools +{ + public class JdkInfo { + + public string HomePath {get;} + + public string JarPath {get;} + public string JavaPath {get;} + public string JavacPath {get;} + public string JdkJvmPath {get;} + public ReadOnlyCollection IncludePath {get;} + + public Version Version => javaVersion.Value; + public string Vendor { + get { + if (GetJavaSettingsPropertyValue ("java.vendor", out string vendor)) + return vendor; + return null; + } + } + + public ReadOnlyDictionary ReleaseProperties {get;} + public IEnumerable JavaSettingsPropertyKeys => javaProperties.Value.Keys; + + Lazy>> javaProperties; + Lazy javaVersion; + + public JdkInfo (string homePath) + { + if (homePath == null) + throw new ArgumentNullException (nameof (homePath)); + if (!Directory.Exists (homePath)) + throw new ArgumentException ("Not a directory", nameof (homePath)); + + HomePath = homePath; + + var binPath = Path.Combine (HomePath, "bin"); + JarPath = ProcessUtils.FindExecutablesInDirectory (binPath, "jar").FirstOrDefault (); + JavaPath = ProcessUtils.FindExecutablesInDirectory (binPath, "java").FirstOrDefault (); + JavacPath = ProcessUtils.FindExecutablesInDirectory (binPath, "javac").FirstOrDefault (); + JdkJvmPath = OS.IsMac + ? FindLibrariesInDirectory (HomePath, "jli").FirstOrDefault () + : FindLibrariesInDirectory (Path.Combine (HomePath, "jre"), "jvm").FirstOrDefault (); + + ValidateFile ("jar", JarPath); + ValidateFile ("java", JavaPath); + ValidateFile ("javac", JavacPath); + ValidateFile ("jvm", JdkJvmPath); + + var includes = new List (); + var jdkInclude = Path.Combine (HomePath, "include"); + + if (Directory.Exists (jdkInclude)) { + includes.Add (jdkInclude); + includes.AddRange (Directory.GetDirectories (jdkInclude)); + } + + + ReleaseProperties = GetReleaseProperties(); + + IncludePath = new ReadOnlyCollection (includes); + + javaProperties = new Lazy>> (GetJavaProperties, LazyThreadSafetyMode.ExecutionAndPublication); + javaVersion = new Lazy (GetJavaVersion, LazyThreadSafetyMode.ExecutionAndPublication); + } + + public override string ToString() + { + return $"JdkInfo(Version={Version}, Vendor=\"{Vendor}\", HomePath=\"{HomePath}\")"; + } + + public bool GetJavaSettingsPropertyValues (string key, out IEnumerable value) + { + value = null; + var props = javaProperties.Value; + if (props.TryGetValue (key, out var v)) { + value = v; + return true; + } + return false; + } + + public bool GetJavaSettingsPropertyValue (string key, out string value) + { + value = null; + var props = javaProperties.Value; + if (props.TryGetValue (key, out var v)) { + if (v.Count > 1) + throw new InvalidOperationException ($"Requested to get one string value when property `{key}` contains `{v.Count}` values."); + value = v [0]; + return true; + } + return false; + } + + static IEnumerable FindLibrariesInDirectory (string dir, string libraryName) + { + var library = string.Format (OS.NativeLibraryFormat, libraryName); + return Directory.EnumerateFiles (dir, library, SearchOption.AllDirectories); + } + + void ValidateFile (string name, string path) + { + if (path == null || !File.Exists (path)) + throw new ArgumentException ($"Could not find required file `{name}` within `{HomePath}`; is this a valid JDK?", "homePath"); + } + + static Regex VersionExtractor = new Regex (@"(?[\d]+(\.\d+)+)(_(?\d+))?", RegexOptions.Compiled); + + Version GetJavaVersion () + { + string version = null; + if (!ReleaseProperties.TryGetValue ("JAVA_VERSION", out version)) { + if (GetJavaSettingsPropertyValue ("java.version", out string vs)) + version = vs; + } + if (version == null) + throw new NotSupportedException ("Could not determine Java version"); + var m = VersionExtractor.Match (version); + if (!m.Success) + return null; + version = m.Groups ["version"].Value; + var patch = m.Groups ["patch"].Value; + if (!string.IsNullOrEmpty (patch)) + version += "." + patch; + if (!version.Contains (".")) + version += ".0"; + if (Version.TryParse (version, out Version v)) + return v; + return null; + } + + ReadOnlyDictionary GetReleaseProperties () + { + var releasePath = Path.Combine (HomePath, "release"); + if (!File.Exists (releasePath)) + return new ReadOnlyDictionary (new Dictionary ()); + + var props = new Dictionary (); + using (var release = File.OpenText (releasePath)) { + string line; + while ((line = release.ReadLine ()) != null) { + const string PropertyDelim = "=\""; + int delim = line.IndexOf (PropertyDelim, StringComparison.Ordinal); + if (delim < 0) { + props [line] = ""; + } + string key = line.Substring (0, delim); + string value = line.Substring (delim + PropertyDelim.Length, line.Length - delim - PropertyDelim.Length - 1); + props [key] = value; + } + } + return new ReadOnlyDictionary(props); + } + + Dictionary> GetJavaProperties () + { + return GetJavaProperties (ProcessUtils.FindExecutablesInDirectory (Path.Combine (HomePath, "bin"), "java").First ()); + } + + static Dictionary> GetJavaProperties (string java) + { + var javaProps = new ProcessStartInfo { + FileName = java, + Arguments = "-XshowSettings:properties -version", + }; + + var props = new Dictionary> (); + string curKey = null; + ProcessUtils.Exec (javaProps, (o, e) => { + const string ContinuedValuePrefix = " "; + const string NewValuePrefix = " "; + const string NameValueDelim = " = "; + if (string.IsNullOrEmpty (e.Data)) + return; + if (e.Data.StartsWith (ContinuedValuePrefix, StringComparison.Ordinal)) { + if (curKey == null) + throw new InvalidOperationException ($"Unknown property key for value {e.Data}!"); + props [curKey].Add (e.Data.Substring (ContinuedValuePrefix.Length)); + return; + } + if (e.Data.StartsWith (NewValuePrefix, StringComparison.Ordinal)) { + var delim = e.Data.IndexOf (NameValueDelim, StringComparison.Ordinal); + if (delim <= 0) + return; + curKey = e.Data.Substring (NewValuePrefix.Length, delim - NewValuePrefix.Length); + var value = e.Data.Substring (delim + NameValueDelim.Length); + List values; + if (!props.TryGetValue (curKey, out values)) + props.Add (curKey, values = new List ()); + values.Add (value); + } + }); + + return props; + } + + public static IEnumerable GetKnownSystemJdkInfos (Action logger) + { + return GetWindowsJdks (logger) + .Concat (GetConfiguredJdks (logger)) + .Concat (GetMacOSMicrosoftJdks (logger)) + .Concat (GetJavaHomeEnvironmentJdks (logger)) + .Concat (GetLibexecJdks (logger)) + .Concat (GetPathEnvironmentJdks (logger)) + .Concat (GetJavaAlternativesJdks (logger)) + ; + } + + static IEnumerable GetConfiguredJdks (Action logger) + { + return GetConfiguredJdkPaths (logger) + .Select (p => TryGetJdkInfo (p, logger)) + .Where (jdk => jdk != null) + .OrderByDescending (jdk => jdk, JdkInfoVersionComparer.Default); + } + + static IEnumerable GetConfiguredJdkPaths (Action logger) + { + var config = AndroidSdkUnix.GetUnixConfigFile (logger); + foreach (var java_sdk in config.Root.Elements ("java-sdk")) { + var path = (string) java_sdk.Attribute ("path"); + yield return path; + } + } + + static IEnumerable GetMacOSMicrosoftJdks (Action logger) + { + return GetMacOSMicrosoftJdkPaths () + .Select (p => TryGetJdkInfo (p, logger)) + .Where (jdk => jdk != null) + .OrderByDescending (jdk => jdk, JdkInfoVersionComparer.Default); + } + + static IEnumerable GetMacOSMicrosoftJdkPaths () + { + var home = Environment.GetFolderPath (Environment.SpecialFolder.Personal); + var jdks = Path.Combine (home, "Library", "Developer", "Xamarin", "jdk"); + if (!Directory.Exists (jdks)) + return Enumerable.Empty (); + + return Directory.EnumerateDirectories (jdks); + } + + static JdkInfo TryGetJdkInfo (string path, Action logger) + { + JdkInfo jdk = null; + try { + jdk = new JdkInfo (path); + } + catch (Exception e) { + logger (TraceLevel.Warning, $"Not a valid JDK directory: `{path}`"); + logger (TraceLevel.Verbose, e.ToString ()); + } + return jdk; + } + + static IEnumerable GetWindowsJdks (Action logger) + { + if (!OS.IsWindows) + return Enumerable.Empty (); + return AndroidSdkWindows.GetJdkInfos (logger); + } + + static IEnumerable GetJavaHomeEnvironmentJdks (Action logger) + { + var java_home = Environment.GetEnvironmentVariable ("JAVA_HOME"); + if (string.IsNullOrEmpty (java_home)) + yield break; + var jdk = TryGetJdkInfo (java_home, logger); + if (jdk != null) + yield return jdk; + } + + // macOS + static IEnumerable GetLibexecJdks (Action logger) + { + return GetLibexecJdkPaths (logger) + .Distinct () + .Select (p => TryGetJdkInfo (p, logger)) + .Where (jdk => jdk != null) + .OrderByDescending (jdk => jdk, JdkInfoVersionComparer.Default); + } + + static IEnumerable GetLibexecJdkPaths (Action logger) + { + var java_home = Path.GetFullPath ("/usr/libexec/java_home"); + if (!File.Exists (java_home)) { + yield break; + } + var jhp = new ProcessStartInfo { + FileName = java_home, + Arguments = "-X", + }; + var xml = new StringBuilder (); + ProcessUtils.Exec (jhp, (o, e) => { + if (string.IsNullOrEmpty (e.Data)) + return; + xml.Append (e.Data); + }); + var plist = XElement.Parse (xml.ToString ()); + foreach (var info in plist.Elements ("array").Elements ("dict")) { + var JVMHomePath = (XNode) info.Elements ("key").FirstOrDefault (e => e.Value == "JVMHomePath"); + if (JVMHomePath == null) + continue; + while (JVMHomePath.NextNode.NodeType != XmlNodeType.Element) + JVMHomePath = JVMHomePath.NextNode; + var strElement = (XElement) JVMHomePath.NextNode; + var path = strElement.Value; + yield return path; + } + } + + // Linux; Ubuntu & Derivatives + static IEnumerable GetJavaAlternativesJdks (Action logger) + { + return GetJavaAlternativesJdkPaths () + .Distinct () + .Select (p => TryGetJdkInfo (p, logger)) + .Where (jdk => jdk != null); + } + + static IEnumerable GetJavaAlternativesJdkPaths () + { + var alternatives = Path.GetFullPath ("/usr/sbin/update-java-alternatives"); + if (!File.Exists (alternatives)) + return Enumerable.Empty (); + + var psi = new ProcessStartInfo { + FileName = alternatives, + Arguments = "-l", + }; + var paths = new List (); + ProcessUtils.Exec (psi, (o, e) => { + if (string.IsNullOrWhiteSpace (e.Data)) + return; + // Example line: + // java-1.8.0-openjdk-amd64 1081 /usr/lib/jvm/java-1.8.0-openjdk-amd64 + var columns = e.Data.Split (new[]{ ' ' }, StringSplitOptions.RemoveEmptyEntries); + if (columns.Length <= 2) + return; + paths.Add (columns [2]); + }); + return paths; + } + + // Linux; Fedora + static IEnumerable GetLibJvmJdks (Action logger) + { + return GetLibJvmJdkPaths () + .Distinct () + .Select (p => TryGetJdkInfo (p, logger)) + .Where (jdk => jdk != null) + .OrderByDescending (jdk => jdk, JdkInfoVersionComparer.Default); + } + + static IEnumerable GetLibJvmJdkPaths () + { + var jvm = "/usr/lib/jvm"; + if (!Directory.Exists (jvm)) + yield break; + + foreach (var jdk in Directory.EnumerateDirectories (jvm)) { + var release = Path.Combine (jdk, "release"); + if (File.Exists (release)) + yield return jdk; + } + } + + // Last-ditch fallback! + static IEnumerable GetPathEnvironmentJdks (Action logger) + { + return GetPathEnvironmentJdkPaths () + .Select (p => TryGetJdkInfo (p, logger)) + .Where (jdk => jdk != null); + } + + static IEnumerable GetPathEnvironmentJdkPaths () + { + foreach (var java in ProcessUtils.FindExecutablesInPath ("java")) { + var props = GetJavaProperties (java); + if (props.TryGetValue ("java.home", out var java_homes)) { + var java_home = java_homes [0]; + // `java -XshowSettings:properties -version 2>&1 | grep java.home` ends with `/jre` on macOS. + // We need the parent dir so we can properly lookup the `include` directories + if (java_home.EndsWith ("jre", StringComparison.OrdinalIgnoreCase)) { + java_home = Path.GetDirectoryName (java_home); + } + yield return java_home; + } + } + } + } + + class JdkInfoVersionComparer : IComparer + { + public static readonly IComparer Default = new JdkInfoVersionComparer (); + + public int Compare (JdkInfo x, JdkInfo y) + { + if (x.Version != null && y.Version != null) + return x.Version.CompareTo (y.Version); + return 0; + } + } +} diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/OS.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/OS.cs index 7c04c471934..81e776618aa 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/OS.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/OS.cs @@ -12,6 +12,8 @@ public class OS internal readonly static string ProgramFilesX86; + internal readonly static string NativeLibraryFormat; + static OS () { IsWindows = Path.DirectorySeparatorChar == '\\'; @@ -20,6 +22,13 @@ static OS () if (IsWindows) { ProgramFilesX86 = GetProgramFilesX86 (); } + + if (IsWindows) + NativeLibraryFormat = "{0}.dll"; + if (IsMac) + NativeLibraryFormat = "lib{0}.dylib"; + if (!IsWindows && !IsMac) + NativeLibraryFormat = "lib{0}.so"; } //From Managed.Windows.Forms/XplatUI diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/ProcessUtils.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/ProcessUtils.cs index 3dc244fde0c..87b02ca6e6b 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/ProcessUtils.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/ProcessUtils.cs @@ -1,13 +1,24 @@ using System; +using System.Collections.Generic; using System.Diagnostics; -using System.Threading.Tasks; using System.IO; using System.Threading; +using System.Threading.Tasks; namespace Xamarin.Android.Tools { public static class ProcessUtils { + static string[] ExecutableFileExtensions; + + static ProcessUtils () + { + var pathExt = Environment.GetEnvironmentVariable ("PATHEXT"); + var pathExts = pathExt?.Split (new char [] { Path.PathSeparator }, StringSplitOptions.RemoveEmptyEntries) ?? new string [0]; + + ExecutableFileExtensions = pathExts; + } + public static async Task StartProcess (ProcessStartInfo psi, TextWriter stdout, TextWriter stderr, CancellationToken cancellationToken, Action onStarted = null) { cancellationToken.ThrowIfCancellationRequested (); @@ -119,6 +130,62 @@ public static Task ExecuteToolAsync (string exe, Func FindExecutablesInPath (string executable) + { + var path = Environment.GetEnvironmentVariable ("PATH"); + var pathDirs = path.Split (new char[] { Path.PathSeparator }, StringSplitOptions.RemoveEmptyEntries); + + foreach (var dir in pathDirs) { + foreach (var exe in FindExecutablesInDirectory (dir, executable)) { + yield return exe; + } + } + } + + internal static IEnumerable FindExecutablesInDirectory (string dir, string executable) + { + foreach (var exe in ExecutableFiles (executable)) { + var exePath = Path.Combine (dir, exe); + if (File.Exists (exePath)) + yield return exePath; + } + } + + internal static IEnumerable ExecutableFiles (string executable) + { + if (ExecutableFileExtensions == null || ExecutableFileExtensions.Length == 0) { + yield return executable; + yield break; + } + + foreach (var ext in ExecutableFileExtensions) + yield return Path.ChangeExtension (executable, ext); + yield return executable; + } } } diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkBase.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkBase.cs index b317863e93b..1537351d7f4 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkBase.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkBase.cs @@ -122,7 +122,7 @@ public string NdkHostPlatform { /// public bool ValidateAndroidSdkLocation (string loc) { - return !string.IsNullOrEmpty (loc) && FindExecutableInDirectory (Adb, Path.Combine (loc, "platform-tools")).Any (); + return !string.IsNullOrEmpty (loc) && ProcessUtils.FindExecutablesInDirectory (Path.Combine (loc, "platform-tools"), Adb).Any (); } /// @@ -130,7 +130,7 @@ public bool ValidateAndroidSdkLocation (string loc) /// public virtual bool ValidateJavaSdkLocation (string loc) { - return !string.IsNullOrEmpty (loc) && FindExecutableInDirectory (JarSigner, Path.Combine (loc, "bin")).Any (); + return !string.IsNullOrEmpty (loc) && ProcessUtils.FindExecutablesInDirectory (Path.Combine (loc, "bin"), JarSigner).Any (); } /// @@ -138,42 +138,10 @@ public virtual bool ValidateJavaSdkLocation (string loc) /// public bool ValidateAndroidNdkLocation (string loc) { - return !string.IsNullOrEmpty (loc) && FindExecutableInDirectory (NdkStack, loc).Any (); + return !string.IsNullOrEmpty (loc) && ProcessUtils.FindExecutablesInDirectory (loc, NdkStack).Any (); } - protected IEnumerable FindExecutableInPath (string executable) - { - var path = Environment.GetEnvironmentVariable ("PATH"); - var pathDirs = path.Split (new char[] { Path.PathSeparator }, StringSplitOptions.RemoveEmptyEntries); - - foreach (var dir in pathDirs) { - foreach (var directory in FindExecutableInDirectory (executable, dir)) { - yield return directory; - } - } - } - - protected IEnumerable FindExecutableInDirectory (string executable, string dir) - { - foreach (var exe in Executables (executable)) - if (File.Exists (Path.Combine (dir, exe))) - yield return dir; - } - - IEnumerable Executables (string executable) - { - yield return executable; - var pathExt = Environment.GetEnvironmentVariable ("PATHEXT"); - var pathExts = pathExt?.Split (new char [] { Path.PathSeparator }, StringSplitOptions.RemoveEmptyEntries); - - if (pathExts == null) - yield break; - - foreach (var ext in pathExts) - yield return Path.ChangeExtension (executable, ext); - } - - protected string NullIfEmpty (string s) + protected static string NullIfEmpty (string s) { if (s == null || s.Length != 0) return s; @@ -181,11 +149,12 @@ protected string NullIfEmpty (string s) return null; } - string GetExecutablePath (string dir, string exe) + static string GetExecutablePath (string dir, string exe) { if (string.IsNullOrEmpty (dir)) return exe; - foreach (var e in Executables (exe)) + + foreach (var e in ProcessUtils.ExecutableFiles (exe)) if (File.Exists (Path.Combine (dir, e))) return e; return exe; diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkUnix.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkUnix.cs index 0fb5cfda489..db43e895b85 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkUnix.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkUnix.cs @@ -42,7 +42,7 @@ public override string NdkHostPlatform64Bit { public override string PreferedAndroidSdkPath { get { - var config_file = GetUnixConfigFile (); + var config_file = GetUnixConfigFile (Logger); var androidEl = config_file.Root.Element ("android-sdk"); if (androidEl != null) { @@ -57,7 +57,7 @@ public override string PreferedAndroidSdkPath { public override string PreferedAndroidNdkPath { get { - var config_file = GetUnixConfigFile (); + var config_file = GetUnixConfigFile (Logger); var androidEl = config_file.Root.Element ("android-ndk"); if (androidEl != null) { @@ -72,7 +72,7 @@ public override string PreferedAndroidNdkPath { public override string PreferedJavaSdkPath { get { - var config_file = GetUnixConfigFile (); + var config_file = GetUnixConfigFile (Logger); var javaEl = config_file.Root.Element ("java-sdk"); if (javaEl != null) { @@ -92,7 +92,7 @@ protected override IEnumerable GetAllAvailableAndroidSdks () yield return preferedSdkPath; // Look in PATH - foreach (var path in FindExecutableInPath (Adb)) { + foreach (var path in ProcessUtils.FindExecutablesInPath (Adb)) { // Strip off "platform-tools" var dir = Path.GetDirectoryName (path); @@ -113,7 +113,7 @@ protected override string GetJavaSdkPath () return preferedJavaSdkPath; // Look in PATH - foreach (var path in FindExecutableInPath (JarSigner)) { + foreach (var path in ProcessUtils.FindExecutablesInPath (JarSigner)) { // Strip off "bin" var dir = Path.GetDirectoryName (path); @@ -160,7 +160,7 @@ protected override IEnumerable GetAllAvailableAndroidNdks () yield return preferedNdkPath; // Look in PATH - foreach (var path in FindExecutableInPath (NdkStack)) { + foreach (var path in ProcessUtils.FindExecutablesInPath (NdkStack)) { if (ValidateAndroidNdkLocation (path)) yield return path; } @@ -176,7 +176,7 @@ public override void SetPreferredAndroidSdkPath (string path) { path = NullIfEmpty (path); - var doc = GetUnixConfigFile (); + var doc = GetUnixConfigFile (Logger); var androidEl = doc.Root.Element ("android-sdk"); if (androidEl == null) { @@ -192,7 +192,7 @@ public override void SetPreferredJavaSdkPath (string path) { path = NullIfEmpty (path); - var doc = GetUnixConfigFile (); + var doc = GetUnixConfigFile (Logger); var javaEl = doc.Root.Element ("java-sdk"); if (javaEl == null) { @@ -208,7 +208,7 @@ public override void SetPreferredAndroidNdkPath (string path) { path = NullIfEmpty (path); - var doc = GetUnixConfigFile (); + var doc = GetUnixConfigFile (Logger); var androidEl = doc.Root.Element ("android-ndk"); if (androidEl == null) { @@ -275,7 +275,7 @@ private static string UnixConfigPath { } } - private XDocument GetUnixConfigFile () + internal static XDocument GetUnixConfigFile (Action logger) { var file = UnixConfigPath; XDocument doc = null; @@ -283,8 +283,8 @@ private XDocument GetUnixConfigFile () try { doc = XDocument.Load (file); } catch (Exception ex) { - Logger (TraceLevel.Error, "Could not load monodroid configuration file"); - Logger (TraceLevel.Verbose, ex.ToString ()); + logger (TraceLevel.Error, "Could not load monodroid configuration file"); + logger (TraceLevel.Verbose, ex.ToString ()); // move out of the way and create a new one doc = new XDocument (new XElement ("monodroid")); diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkWindows.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkWindows.cs index 5977ec21d81..32a0bdc93be 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkWindows.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkWindows.cs @@ -22,8 +22,10 @@ public AndroidSdkWindows (Action logger) { } + static readonly string _JarSigner = "jarsigner.exe"; + public override string ZipAlign { get; protected set; } = "zipalign.exe"; - public override string JarSigner { get; protected set; } = "jarsigner.exe"; + public override string JarSigner { get; protected set; } = _JarSigner; public override string KeyTool { get; protected set; } = "keytool.exe"; public override string NdkHostPlatform32Bit { get { return "windows"; } } @@ -58,7 +60,7 @@ public override string PreferedJavaSdkPath { } } - string GetMDRegistryKey () + static string GetMDRegistryKey () { var regKey = Environment.GetEnvironmentVariable ("XAMARIN_ANDROID_REGKEY"); return string.IsNullOrWhiteSpace (regKey) ? MDREG_KEY : regKey; @@ -104,18 +106,37 @@ protected override IEnumerable GetAllAvailableAndroidSdks () protected override string GetJavaSdkPath () { - var preferredJdkPath = GetPreferredJdkPath (); - if (!string.IsNullOrEmpty (preferredJdkPath)) - return preferredJdkPath; + var jdk = GetJdkInfos (Logger).FirstOrDefault (); + return jdk?.HomePath; + } + + internal static IEnumerable GetJdkInfos (Action logger) + { + JdkInfo TryGetJdkInfo (string path) + { + JdkInfo jdk = null; + try { + jdk = new JdkInfo (path); + } + catch (Exception e) { + logger (TraceLevel.Warning, e.ToString ()); + } + return jdk; + } - var openJdkPath = GetOpenJdkPath (); - if (!string.IsNullOrEmpty (openJdkPath)) - return openJdkPath; + IEnumerable ToJdkInfos (IEnumerable paths) + { + return paths.Select (TryGetJdkInfo) + .Where (jdk => jdk != null) + .OrderByDescending (jdk => jdk, JdkInfoVersionComparer.Default); + } - return GetOracleJdkPath (); + return ToJdkInfos (GetPreferredJdkPaths ()) + .Concat (ToJdkInfos (GetOpenJdkPaths ())) + .Concat (ToJdkInfos (GetOracleJdkPaths ())); } - private string GetPreferredJdkPath () + private static IEnumerable GetPreferredJdkPaths () { // check the user specified path var roots = new[] { RegistryEx.CurrentUser, RegistryEx.LocalMachine }; @@ -123,14 +144,12 @@ private string GetPreferredJdkPath () var regKey = GetMDRegistryKey (); foreach (var root in roots) { - if (CheckRegistryKeyForExecutable (root, regKey, MDREG_JAVA_SDK, wow, "bin", JarSigner)) - return RegistryEx.GetValueString (root, regKey, MDREG_JAVA_SDK, wow); + if (CheckRegistryKeyForExecutable (root, regKey, MDREG_JAVA_SDK, wow, "bin", _JarSigner)) + yield return RegistryEx.GetValueString (root, regKey, MDREG_JAVA_SDK, wow); } - - return null; } - private string GetOpenJdkPath () + private static IEnumerable GetOpenJdkPaths () { var root = RegistryEx.LocalMachine; var wows = new[] { RegistryEx.Wow64.Key32, RegistryEx.Wow64.Key64 }; @@ -138,42 +157,32 @@ private string GetOpenJdkPath () var valueName = "JavaHome"; foreach (var wow in wows) { - if (CheckRegistryKeyForExecutable (root, subKey, valueName, wow, "bin", JarSigner)) - return RegistryEx.GetValueString (root, subKey, valueName, wow); + if (CheckRegistryKeyForExecutable (root, subKey, valueName, wow, "bin", _JarSigner)) + yield return RegistryEx.GetValueString (root, subKey, valueName, wow); } - - return null; } - private string GetOracleJdkPath () + private static IEnumerable GetOracleJdkPaths () { string subkey = @"SOFTWARE\JavaSoft\Java Development Kit"; - Logger (TraceLevel.Info, "Looking for Java 6 SDK..."); - foreach (var wow64 in new[] { RegistryEx.Wow64.Key32, RegistryEx.Wow64.Key64 }) { string key_name = string.Format (@"{0}\{1}\{2}", "HKLM", subkey, "CurrentVersion"); var currentVersion = RegistryEx.GetValueString (RegistryEx.LocalMachine, subkey, "CurrentVersion", wow64); if (!string.IsNullOrEmpty (currentVersion)) { - Logger (TraceLevel.Info, $" Key {key_name} found."); // No matter what the CurrentVersion is, look for 1.6 or 1.7 or 1.8 - if (CheckRegistryKeyForExecutable (RegistryEx.LocalMachine, subkey + "\\" + "1.8", "JavaHome", wow64, "bin", JarSigner)) - return RegistryEx.GetValueString (RegistryEx.LocalMachine, subkey + "\\" + "1.8", "JavaHome", wow64); + if (CheckRegistryKeyForExecutable (RegistryEx.LocalMachine, subkey + "\\" + "1.8", "JavaHome", wow64, "bin", _JarSigner)) + yield return RegistryEx.GetValueString (RegistryEx.LocalMachine, subkey + "\\" + "1.8", "JavaHome", wow64); - if (CheckRegistryKeyForExecutable (RegistryEx.LocalMachine, subkey + "\\" + "1.7", "JavaHome", wow64, "bin", JarSigner)) - return RegistryEx.GetValueString (RegistryEx.LocalMachine, subkey + "\\" + "1.7", "JavaHome", wow64); + if (CheckRegistryKeyForExecutable (RegistryEx.LocalMachine, subkey + "\\" + "1.7", "JavaHome", wow64, "bin", _JarSigner)) + yield return RegistryEx.GetValueString (RegistryEx.LocalMachine, subkey + "\\" + "1.7", "JavaHome", wow64); - if (CheckRegistryKeyForExecutable (RegistryEx.LocalMachine, subkey + "\\" + "1.6", "JavaHome", wow64, "bin", JarSigner)) - return RegistryEx.GetValueString (RegistryEx.LocalMachine, subkey + "\\" + "1.6", "JavaHome", wow64); + if (CheckRegistryKeyForExecutable (RegistryEx.LocalMachine, subkey + "\\" + "1.6", "JavaHome", wow64, "bin", _JarSigner)) + yield return RegistryEx.GetValueString (RegistryEx.LocalMachine, subkey + "\\" + "1.6", "JavaHome", wow64); } - - Logger (TraceLevel.Info, $" Key {key_name} not found."); } - - // We ran out of things to check.. - return null; } protected override IEnumerable GetAllAvailableAndroidNdks () @@ -234,24 +243,20 @@ public override void SetPreferredAndroidNdkPath (string path) } #region Helper Methods - private bool CheckRegistryKeyForExecutable (UIntPtr key, string subkey, string valueName, RegistryEx.Wow64 wow64, string subdir, string exe) + private static bool CheckRegistryKeyForExecutable (UIntPtr key, string subkey, string valueName, RegistryEx.Wow64 wow64, string subdir, string exe) { string key_name = string.Format (@"{0}\{1}\{2}", key == RegistryEx.CurrentUser ? "HKCU" : "HKLM", subkey, valueName); var path = NullIfEmpty (RegistryEx.GetValueString (key, subkey, valueName, wow64)); if (path == null) { - Logger (TraceLevel.Info, $" Key {key_name} not found."); return false; } - if (!FindExecutableInDirectory (exe, Path.Combine (path, subdir)).Any ()) { - Logger (TraceLevel.Info, $" Key {key_name} found:\n Path does not contain {exe} in \\{subdir} ({path})."); + if (!ProcessUtils.FindExecutablesInDirectory (Path.Combine (path, subdir), exe).Any ()) { return false; } - Logger (TraceLevel.Info, $" Key {key_name} found:\n Path contains {exe} in \\{subdir} ({path})."); - return true; } #endregion diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/JdkInfoTests.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/JdkInfoTests.cs new file mode 100644 index 00000000000..babac4f231d --- /dev/null +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/JdkInfoTests.cs @@ -0,0 +1,159 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.IO; + +using NUnit.Framework; + +namespace Xamarin.Android.Tools.Tests +{ + [TestFixture] + public class JdkInfoTests + { + [Test] + public void Constructor_NullPath () + { + Assert.Throws(() => new JdkInfo (null)); + } + + [Test] + public void Constructor_InvalidPath () + { + var dir = Path.GetTempFileName (); + File.Delete (dir); + Directory.CreateDirectory (dir); + Assert.Throws(() => new JdkInfo (dir)); + Directory.Delete (dir); + } + + string FauxJdkDir; + + [OneTimeSetUp] + public void CreateFauxJdk () + { + var dir = Path.GetTempFileName(); + File.Delete (dir); + Directory.CreateDirectory (dir); + + using (var release = new StreamWriter (Path.Combine (dir, "release"))) { + release.WriteLine ("JAVA_VERSION=\"1.2.3.4\""); + } + + var bin = Path.Combine (dir, "bin"); + var inc = Path.Combine (dir, "include"); + var jli = Path.Combine (dir, "jli"); + var jre = Path.Combine (dir, "jre"); + + Directory.CreateDirectory (bin); + Directory.CreateDirectory (inc); + Directory.CreateDirectory (jli); + Directory.CreateDirectory (jre); + + CreateShellScript (Path.Combine (bin, "jar"), ""); + CreateShellScript (Path.Combine (bin, "java"), JavaScript); + CreateShellScript (Path.Combine (bin, "javac"), ""); + CreateShellScript (Path.Combine (dir, "jli", "libjli.dylib"), ""); + CreateShellScript (Path.Combine (jre, "libjvm.so"), ""); + CreateShellScript (Path.Combine (jre, "jvm.dll"), ""); + + FauxJdkDir = dir; + } + + [OneTimeTearDown] + public void DeleteFauxJdk () + { + Directory.Delete (FauxJdkDir, recursive: true); + } + + static readonly string JavaScript = + $"echo Property settings:{Environment.NewLine}" + + $"echo \" java.vendor = Xamarin.Android Unit Tests\"{Environment.NewLine}" + + $"echo \" java.version = 100.100.100\"{Environment.NewLine}" + + $"echo \" xamarin.multi-line = line the first\"{Environment.NewLine}" + + $"echo \" line the second\"{Environment.NewLine}" + + $"echo \" .\"{Environment.NewLine}"; + + static void CreateShellScript (string path, string contents) + { + if (OS.IsWindows) + path += ".cmd"; + using (var script = new StreamWriter (path)) { + if (!OS.IsWindows) { + script.WriteLine ("#!/bin/sh"); + } + script.WriteLine (contents); + } + if (OS.IsWindows) + return; + var chmod = new ProcessStartInfo { + FileName = "chmod", + Arguments = $"+x \"{path}\"", + UseShellExecute = false, + RedirectStandardInput = false, + RedirectStandardOutput = true, + RedirectStandardError = true, + CreateNoWindow = true, + WindowStyle = ProcessWindowStyle.Hidden, + }; + var p = Process.Start (chmod); + p.WaitForExit (); + } + + [Test] + public void PathPropertyValidation () + { + var jdk = new JdkInfo (FauxJdkDir); + + Assert.AreEqual (jdk.HomePath, FauxJdkDir); + Assert.IsTrue (File.Exists (jdk.JarPath)); + Assert.IsTrue (File.Exists (jdk.JavaPath)); + Assert.IsTrue (File.Exists (jdk.JavacPath)); + Assert.IsTrue (File.Exists (jdk.JdkJvmPath)); + Assert.IsTrue (Directory.Exists (jdk.IncludePath [0])); + } + + [Test] + public void VersionPrefersRelease () + { + var jdk = new JdkInfo (FauxJdkDir); + // Note: `release` has JAVA_VERSION=1.2.3.4, while `java` prints java.version=100.100.100. + // We prefer the value within `releas`. + Assert.AreEqual (jdk.Version, new Version ("1.2.3.4")); + } + + [Test] + public void ReleaseProperties () + { + var jdk = new JdkInfo (FauxJdkDir); + + Assert.AreEqual (1, jdk.ReleaseProperties.Count); + Assert.AreEqual ("1.2.3.4", jdk.ReleaseProperties ["JAVA_VERSION"]); + } + + [Test] + public void JavaSettingsProperties () + { + var jdk = new JdkInfo (FauxJdkDir); + + Assert.AreEqual (3, jdk.JavaSettingsPropertyKeys.Count ()); + + Assert.IsFalse(jdk.GetJavaSettingsPropertyValue ("does-not-exist", out var _)); + Assert.IsFalse(jdk.GetJavaSettingsPropertyValues ("does-not-exist", out var _)); + + Assert.IsTrue (jdk.GetJavaSettingsPropertyValue ("java.version", out var version)); + Assert.AreEqual ("100.100.100", version); + + Assert.IsTrue (jdk.GetJavaSettingsPropertyValue ("java.vendor", out var vendor)); + Assert.AreEqual ("Xamarin.Android Unit Tests", vendor); + Assert.AreEqual (vendor, jdk.Vendor); + + Assert.Throws(() => jdk.GetJavaSettingsPropertyValue ("xamarin.multi-line", out var _)); + Assert.IsTrue (jdk.GetJavaSettingsPropertyValues ("xamarin.multi-line", out var lines)); + Assert.AreEqual (3, lines.Count ()); + Assert.AreEqual ("line the first", lines.ElementAt (0)); + Assert.AreEqual ("line the second", lines.ElementAt (1)); + Assert.AreEqual (".", lines.ElementAt (2)); + } + } +} diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/Xamarin.Android.Tools.AndroidSdk-Tests.csproj b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/Xamarin.Android.Tools.AndroidSdk-Tests.csproj index b3ff7e16e41..7cb376d4af6 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/Xamarin.Android.Tools.AndroidSdk-Tests.csproj +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/Xamarin.Android.Tools.AndroidSdk-Tests.csproj @@ -46,6 +46,7 @@ + diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj index df789690701..5adcf4fe8f2 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj @@ -43,6 +43,7 @@ + From 32ca672d5a234acf7fc4e36c778e8ea9b581ad66 Mon Sep 17 00:00:00 2001 From: Cody Russell Date: Thu, 26 Jul 2018 14:07:29 -0700 Subject: [PATCH 041/308] [unix] Use JdkInfo to fetch the JDK path (#33) Fixes: http://work.devdiv.io/646086 Fixes: http://work.devdiv.io/652760 Context: https://paper.dropbox.com/doc/OpenJDK-and-You--AH1yWKdVXgno~uXYfmcUAZTwAg-NoECAe2XkBQeoxFfGL6ea Update `AndroidSdkInfo.JavaSdkPath` so that it now uses `JdkInfo.GetKnownSystemJdkInfos()` to determine the JDK path to use, instead of separately reading from `monodroid-config.xml` or probing `$PATH` for `jarsigner`. --- .../Sdks/AndroidSdkUnix.cs | 38 ++----------------- 1 file changed, 4 insertions(+), 34 deletions(-) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkUnix.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkUnix.cs index db43e895b85..eba51845891 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkUnix.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkUnix.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using System.Collections.Generic; using System.Diagnostics; using System.IO; @@ -108,46 +109,15 @@ protected override IEnumerable GetAllAvailableAndroidSdks () protected override string GetJavaSdkPath () { - var preferedJavaSdkPath = PreferedJavaSdkPath; - if (!string.IsNullOrEmpty (preferedJavaSdkPath)) - return preferedJavaSdkPath; - - // Look in PATH - foreach (var path in ProcessUtils.FindExecutablesInPath (JarSigner)) { - // Strip off "bin" - var dir = Path.GetDirectoryName (path); - - if (ValidateJavaSdkLocation (dir)) - return dir; - } - - return null; + return JdkInfo.GetKnownSystemJdkInfos (Logger).FirstOrDefault ()?.HomePath; } - public override bool ValidateJavaSdkLocation (string loc) + public override bool ValidateJavaSdkLocation (string loc) { var result = base.ValidateJavaSdkLocation (loc); if (result) { - // handle apple's java stub - const string javaHomeExe = "/usr/libexec/java_home"; - - if (File.Exists (javaHomeExe)) { - // returns true if there is a java installed - var javaHomeTask = ProcessUtils.ExecuteToolAsync (javaHomeExe, - (output) => { - if (output.Contains ("(null)")) { - return false; - } - - return true; - }, System.Threading.CancellationToken.None - ); - - if (!javaHomeTask.Result) { - return false; - } - } + return File.Exists (Path.Combine (loc, "bin", "javac")); } return result; From 5907c819c68f6fc0f4d88213a5f0ad6e7799dbc6 Mon Sep 17 00:00:00 2001 From: Jonathan Pryor Date: Fri, 27 Jul 2018 15:46:10 -0400 Subject: [PATCH 042/308] [Xamarin.Android.Tools.AndroidSdk] Revert JDK validation (#34) Context: https://github.com/xamarin/xamarin-android/pull/2004 Context: https://jenkins.mono-project.com/job/xamarin-android-pr-builder/3656/testReport/junit/Xamarin.Android.Build.Tests/ResolveSdksTaskTests/ResolveSdkTiming___Debug/ Before commit 32ca672d, the only validation performed on the `AndroidSdkInfo`'s `javaSdkPath` parameter was "does `{javaSdkPath}/bin/jarsigner` exist? This is the world the [xamarin-android unit tests assumed][0], as it would create `{javaSdkPath}/bin/jarsigner` and `{javaSdkPath}/bin/javac.bash` (among others), but *not* `{javaSdkPath/bin/javac`. Then came commit 32ca672d, which altered `AndroidSdkUnix.ValidateJavaSdkLocation()` semantics so that it now *required* the presence of `{javaSdkPath}/bin/javac`. This changed caused the xamarin-android unit tests to fail: JavaSdkPath should be /Users/builder/jenkins/workspace/xamarin-android-pr-builder/xamarin-android/bin/TestDebug/temp/ResolveSdkTiming/jdk Expected string length 63 but was 115. Strings differ at index 1. Expected: "/Library/Java/JavaVirtualMachines/jdk1.8.0_91.jdk/Contents/Home" But was: "/Users/builder/jenkins/workspace/xamarin-android-pr-builder/x..." ------------^ The "expected" and "actual" values are reversed; the actual problem is that the `javaSdkPath` value provided to the `AndroidSdkInfo` constructor -- `/Users/builder/jenkins/workspace/xamarin-android-pr-builder/xamarin-android/bin/TestDebug/temp/ResolveSdkTiming/jdk` -- is "ignored", because it doesn't pass validation, prompting `AndroidSdkInfo` to call `AndroidSdkUnix.GetJavaSdkPath()`, which returns a "system" location, `/Library/Java/JavaVirtualMachines/jdk1.8.0_91.jdk/Contents/Home`. As such, the original `javaSdkPath` value is not preserved and returned by the `AndroidSdkInfo.JavaSdkPath` property. Fix this by *removing* the `AndroidSdkUnix.ValidateJavaSdkLocation()` method: it doesn't appear to actually be useful in this instance, as the base `AndroidSdkBase.ValidateJavaSdkLocation()` already checks for the presence of `{javaSdkPath}/bin/jarsigner`. [0]: https://github.com/xamarin/xamarin-android/blob/997b8904889d371610026c1802a8e3403d7d2c96/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Utilities/BaseTest.cs#L220-L235 --- .../Sdks/AndroidSdkUnix.cs | 11 -- .../Tests/AndroidSdkInfoTests.cs | 139 ++++++++++++++++++ 2 files changed, 139 insertions(+), 11 deletions(-) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkUnix.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkUnix.cs index eba51845891..be0d5fb5af0 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkUnix.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkUnix.cs @@ -112,17 +112,6 @@ protected override string GetJavaSdkPath () return JdkInfo.GetKnownSystemJdkInfos (Logger).FirstOrDefault ()?.HomePath; } - public override bool ValidateJavaSdkLocation (string loc) - { - var result = base.ValidateJavaSdkLocation (loc); - - if (result) { - return File.Exists (Path.Combine (loc, "bin", "javac")); - } - - return result; - } - protected override IEnumerable GetAllAvailableAndroidNdks () { var preferedNdkPath = PreferedAndroidNdkPath; diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/AndroidSdkInfoTests.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/AndroidSdkInfoTests.cs index 2190f23be0d..887e4e85108 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/AndroidSdkInfoTests.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/AndroidSdkInfoTests.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.IO; +using System.Text; using NUnit.Framework; @@ -15,5 +17,142 @@ public void Constructor_NullLogger () Action logger = null; Assert.Throws (() => new AndroidSdkInfo (logger)); } + + [Test] + public void Constructor_Paths () + { + var root = Path.GetTempFileName (); + File.Delete (root); + Directory.CreateDirectory (root); + + var sdk = Path.Combine (root, "sdk"); + var jdk = Path.Combine (root, "jdk"); + + Directory.CreateDirectory (sdk); + Directory.CreateDirectory (jdk); + + CreateFauxAndroidSdkDirectory (sdk, "26.0.0"); + CreateFauxJavaSdkDirectory (jdk, "1.8.0", out var _, out var _); + + var logs = new StringWriter (); + Action logger = (level, message) => { + logs.WriteLine ($"[{level}] {message}"); + }; + var info = new AndroidSdkInfo (logger, androidSdkPath: sdk, javaSdkPath: jdk, androidNdkPath: null); + + Assert.AreEqual (sdk, info.AndroidSdkPath, "AndroidSdkPath not preserved!"); + Assert.AreEqual (jdk, info.JavaSdkPath, "JavaSdkPath not preserved!"); + } + + static bool IsWindows => OS.IsWindows; + + static void CreateFauxAndroidSdkDirectory (string androidSdkDirectory, string buildToolsVersion, ApiInfo [] apiLevels = null) + { + var androidSdkToolsPath = Path.Combine (androidSdkDirectory, "tools"); + var androidSdkBinPath = Path.Combine (androidSdkToolsPath, "bin"); + var androidSdkPlatformToolsPath = Path.Combine (androidSdkDirectory, "platform-tools"); + var androidSdkPlatformsPath = Path.Combine (androidSdkDirectory, "platforms"); + var androidSdkBuildToolsPath = Path.Combine (androidSdkDirectory, "build-tools", buildToolsVersion); + + Directory.CreateDirectory (androidSdkDirectory); + Directory.CreateDirectory (androidSdkToolsPath); + Directory.CreateDirectory (androidSdkBinPath); + Directory.CreateDirectory (androidSdkPlatformToolsPath); + Directory.CreateDirectory (androidSdkPlatformsPath); + Directory.CreateDirectory (androidSdkBuildToolsPath); + + File.WriteAllText (Path.Combine (androidSdkPlatformToolsPath, IsWindows ? "adb.exe" : "adb"), ""); + File.WriteAllText (Path.Combine (androidSdkBuildToolsPath, IsWindows ? "zipalign.exe" : "zipalign"), ""); + File.WriteAllText (Path.Combine (androidSdkBuildToolsPath, IsWindows ? "aapt.exe" : "aapt"), ""); + File.WriteAllText (Path.Combine (androidSdkToolsPath, IsWindows ? "lint.bat" : "lint"), ""); + + List defaults = new List (); + for (int i = 10; i < 26; i++) { + defaults.Add (new ApiInfo () { + Id = i.ToString (), + }); + } + foreach (var level in ((IEnumerable) apiLevels) ?? defaults) { + var dir = Path.Combine (androidSdkPlatformsPath, $"android-{level.Id}"); + Directory.CreateDirectory(dir); + File.WriteAllText (Path.Combine (dir, "android.jar"), ""); + } + } + + struct ApiInfo { + public string Id; + } + + protected string CreateFauxJavaSdkDirectory (string javaPath, string javaVersion, out string javaExe, out string javacExe) + { + javaExe = IsWindows ? "Java.cmd" : "java.bash"; + javacExe = IsWindows ? "Javac.cmd" : "javac.bash"; + var jarSigner = IsWindows ? "jarsigner.exe" : "jarsigner"; + var javaBinPath = Path.Combine (javaPath, "bin"); + + Directory.CreateDirectory (javaBinPath); + + CreateFauxJavaExe (Path.Combine (javaBinPath, javaExe), javaVersion); + CreateFauxJavacExe (Path.Combine (javaBinPath, javacExe), javaVersion); + + File.WriteAllText (Path.Combine (javaBinPath, jarSigner), ""); + return javaPath; + } + + void CreateFauxJavaExe (string javaExeFullPath, string version) + { + var sb = new StringBuilder (); + if (IsWindows) { + sb.AppendLine ("@echo off"); + sb.AppendLine ($"echo java version \"{version}\""); + sb.AppendLine ($"echo Java(TM) SE Runtime Environment (build {version}-b13)"); + sb.AppendLine ($"echo Java HotSpot(TM) 64-Bit Server VM (build 25.101-b13, mixed mode)"); + } else { + sb.AppendLine ("#!/bin/bash"); + sb.AppendLine ($"echo \"java version \\\"{version}\\\"\""); + sb.AppendLine ($"echo \"Java(TM) SE Runtime Environment (build {version}-b13)\""); + sb.AppendLine ($"echo \"Java HotSpot(TM) 64-Bit Server VM (build 25.101-b13, mixed mode)\""); + } + File.WriteAllText (javaExeFullPath, sb.ToString ()); + if (!IsWindows) { + Exec ("chmod", $"u+x \"{javaExeFullPath}\""); + } + } + + void CreateFauxJavacExe (string javacExeFullPath, string version) + { + var sb = new StringBuilder (); + if (IsWindows) { + sb.AppendLine ("@echo off"); + sb.AppendLine ($"echo javac {version}"); + } else { + sb.AppendLine ("#!/bin/bash"); + sb.AppendLine ($"echo \"javac {version}\""); + } + File.WriteAllText (javacExeFullPath, sb.ToString ()); + if (!IsWindows) { + Exec ("chmod", $"u+x \"{javacExeFullPath}\""); + } + } + + protected void Exec (string exe, string args) + { + var psi = new ProcessStartInfo { + FileName = exe, + Arguments = args, + UseShellExecute = false, + RedirectStandardInput = false, + RedirectStandardOutput = false, + RedirectStandardError = false, + CreateNoWindow = true, + WindowStyle = ProcessWindowStyle.Hidden, + + }; + var proc = Process.Start (psi); + if (!proc.WaitForExit ((int) TimeSpan.FromSeconds(30).TotalMilliseconds)) { + proc.Kill (); + proc.WaitForExit (); + } + } } } From f633232085b601cc6fa7873ff94881c65d8685a2 Mon Sep 17 00:00:00 2001 From: Jonathan Pryor Date: Sat, 28 Jul 2018 11:01:04 -0400 Subject: [PATCH 043/308] [tests] Remove temporary directories (#36) Improve the `AndroidSdkInfoTests.Constructor_Paths()` test to remove the temporary Android SDK & JDK directories that it creates. --- .../Tests/AndroidSdkInfoTests.cs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/AndroidSdkInfoTests.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/AndroidSdkInfoTests.cs index 887e4e85108..1d38e9f988a 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/AndroidSdkInfoTests.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/AndroidSdkInfoTests.cs @@ -38,10 +38,16 @@ public void Constructor_Paths () Action logger = (level, message) => { logs.WriteLine ($"[{level}] {message}"); }; - var info = new AndroidSdkInfo (logger, androidSdkPath: sdk, javaSdkPath: jdk, androidNdkPath: null); - Assert.AreEqual (sdk, info.AndroidSdkPath, "AndroidSdkPath not preserved!"); - Assert.AreEqual (jdk, info.JavaSdkPath, "JavaSdkPath not preserved!"); + try { + var info = new AndroidSdkInfo (logger, androidSdkPath: sdk, javaSdkPath: jdk, androidNdkPath: null); + + Assert.AreEqual (sdk, info.AndroidSdkPath, "AndroidSdkPath not preserved!"); + Assert.AreEqual (jdk, info.JavaSdkPath, "JavaSdkPath not preserved!"); + } + finally { + Directory.Delete (root, recursive: true); + } } static bool IsWindows => OS.IsWindows; From 5fd3df8093ad9bc971513b2c8cdf91fd1e7351af Mon Sep 17 00:00:00 2001 From: Jonathan Pryor Date: Mon, 30 Jul 2018 09:21:59 -0400 Subject: [PATCH 044/308] Add AndroidSdkInfo.DetectAndSetPreferredJavaSdkPathToLatest() (#35) Context: https://paper.dropbox.com/doc/OpenJDK-and-You--AH1yWKdVXgno~uXYfmcUAZTwAg-NoECAe2XkBQeoxFfGL6ea Add `AndroidSdkInfo.DetectAndSetPreferredJavaSdkPathToLatest()`. --- .../AndroidSdkInfo.cs | 15 ++++++ .../JdkInfo.cs | 10 ++-- .../Tests/AndroidSdkInfoTests.cs | 52 +++++++++++++++++++ .../Tests/JdkInfoTests.cs | 12 +++-- 4 files changed, 83 insertions(+), 6 deletions(-) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidSdkInfo.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidSdkInfo.cs index 57dcf2dd493..df13020f646 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidSdkInfo.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidSdkInfo.cs @@ -171,5 +171,20 @@ public static void SetPreferredJavaSdkPath (string path, Action logger = null) + { + if (OS.IsWindows) + throw new NotImplementedException ("Windows is not supported at this time."); + + logger = logger ?? DefaultConsoleLogger; + + var latestJdk = JdkInfo.GetMacOSMicrosoftJdks (logger).FirstOrDefault (); + if (latestJdk == null) + throw new NotSupportedException ("No Microsoft OpenJDK could be found. Please re-run the Visual Studio installer or manually specify the JDK path in settings."); + + var sdk = CreateSdk (logger); + sdk.SetPreferredJavaSdkPath (latestJdk.HomePath); + } } } diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/JdkInfo.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/JdkInfo.cs index a30f5182768..09cc75d7e29 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/JdkInfo.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/JdkInfo.cs @@ -236,7 +236,7 @@ static IEnumerable GetConfiguredJdkPaths (Action log } } - static IEnumerable GetMacOSMicrosoftJdks (Action logger) + internal static IEnumerable GetMacOSMicrosoftJdks (Action logger) { return GetMacOSMicrosoftJdkPaths () .Select (p => TryGetJdkInfo (p, logger)) @@ -246,8 +246,12 @@ static IEnumerable GetMacOSMicrosoftJdks (Action lo static IEnumerable GetMacOSMicrosoftJdkPaths () { - var home = Environment.GetFolderPath (Environment.SpecialFolder.Personal); - var jdks = Path.Combine (home, "Library", "Developer", "Xamarin", "jdk"); + var jdks = AppDomain.CurrentDomain.GetData ($"GetMacOSMicrosoftJdkPaths jdks override! {typeof (JdkInfo).AssemblyQualifiedName}") + ?.ToString (); + if (jdks == null) { + var home = Environment.GetFolderPath (Environment.SpecialFolder.Personal); + jdks = Path.Combine (home, "Library", "Developer", "Xamarin", "jdk"); + } if (!Directory.Exists (jdks)) return Enumerable.Empty (); diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/AndroidSdkInfoTests.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/AndroidSdkInfoTests.cs index 1d38e9f988a..21eeafbd253 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/AndroidSdkInfoTests.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/AndroidSdkInfoTests.cs @@ -3,6 +3,7 @@ using System.Diagnostics; using System.IO; using System.Text; +using System.Xml.Linq; using NUnit.Framework; @@ -160,5 +161,56 @@ protected void Exec (string exe, string args) proc.WaitForExit (); } } + + [Test] + public void DetectAndSetPreferredJavaSdkPathToLatest () + { + Action logger = (level, message) => { + Console.WriteLine ($"[{level}] {message}"); + }; + var jdks = Path.GetTempFileName (); + File.Delete (jdks); + Directory.CreateDirectory (jdks); + AppDomain.CurrentDomain.SetData ($"GetMacOSMicrosoftJdkPaths jdks override! {typeof (JdkInfo).AssemblyQualifiedName}", jdks); + + var backupConfig = UnixConfigPath + "." + Path.GetRandomFileName (); + try { + if (OS.IsWindows) { + Assert.Throws(() => AndroidSdkInfo.DetectAndSetPreferredJavaSdkPathToLatest (logger)); + return; + } + Assert.Throws(() => AndroidSdkInfo.DetectAndSetPreferredJavaSdkPathToLatest (logger)); + var newJdkPath = Path.Combine (jdks, "microsoft_dist_openjdk_1.8.999"); + JdkInfoTests.CreateFauxJdk (newJdkPath, "1.8.999"); + + if (File.Exists (UnixConfigPath)) + File.Move (UnixConfigPath, backupConfig); + + AndroidSdkInfo.DetectAndSetPreferredJavaSdkPathToLatest (logger); + AssertJdkPath (newJdkPath); + } + finally { + AppDomain.CurrentDomain.SetData ($"GetMacOSMicrosoftJdkPaths jdks override! {typeof (JdkInfo).AssemblyQualifiedName}", null); + Directory.Delete (jdks, recursive: true); + if (File.Exists (backupConfig)) + File.Move (backupConfig, UnixConfigPath); + } + } + + static void AssertJdkPath (string expectedJdkPath) + { + var config_file = XDocument.Load (UnixConfigPath); + var javaEl = config_file.Root.Element ("java-sdk"); + var actualJdkPath = (string) javaEl.Attribute ("path"); + + Assert.AreEqual (expectedJdkPath, actualJdkPath); + } + + static string UnixConfigPath { + get { + var p = Environment.GetFolderPath (Environment.SpecialFolder.ApplicationData); + return Path.Combine (Path.Combine (p, "xbuild"), "monodroid-config.xml"); + } + } } } diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/JdkInfoTests.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/JdkInfoTests.cs index babac4f231d..105cf3d5151 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/JdkInfoTests.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/JdkInfoTests.cs @@ -34,10 +34,18 @@ public void CreateFauxJdk () { var dir = Path.GetTempFileName(); File.Delete (dir); + + CreateFauxJdk (dir, "1.2.3.4"); + + FauxJdkDir = dir; + } + + internal static void CreateFauxJdk (string dir, string version) + { Directory.CreateDirectory (dir); using (var release = new StreamWriter (Path.Combine (dir, "release"))) { - release.WriteLine ("JAVA_VERSION=\"1.2.3.4\""); + release.WriteLine ($"JAVA_VERSION=\"{version}\""); } var bin = Path.Combine (dir, "bin"); @@ -56,8 +64,6 @@ public void CreateFauxJdk () CreateShellScript (Path.Combine (dir, "jli", "libjli.dylib"), ""); CreateShellScript (Path.Combine (jre, "libjvm.so"), ""); CreateShellScript (Path.Combine (jre, "jvm.dll"), ""); - - FauxJdkDir = dir; } [OneTimeTearDown] From 4626d30979321cddec255045b3b59ac92c1cecc2 Mon Sep 17 00:00:00 2001 From: Cody Russell Date: Mon, 30 Jul 2018 20:03:06 -0500 Subject: [PATCH 045/308] [tests] Fix DetectAndSetPreferredJavaSdkPathToLatest() test (#37) The test backs up `monodroid-config.xml`, but when it tries to move the backup to its original place it fails because the version created during the test is in its place. --- .../Tests/AndroidSdkInfoTests.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/AndroidSdkInfoTests.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/AndroidSdkInfoTests.cs index 21eeafbd253..5975c68da39 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/AndroidSdkInfoTests.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/AndroidSdkInfoTests.cs @@ -192,8 +192,10 @@ public void DetectAndSetPreferredJavaSdkPathToLatest () finally { AppDomain.CurrentDomain.SetData ($"GetMacOSMicrosoftJdkPaths jdks override! {typeof (JdkInfo).AssemblyQualifiedName}", null); Directory.Delete (jdks, recursive: true); - if (File.Exists (backupConfig)) + if (File.Exists (backupConfig)) { + File.Delete (UnixConfigPath); File.Move (backupConfig, UnixConfigPath); + } } } From c5cb7b3a43537203aa9bd8299985ab927952e492 Mon Sep 17 00:00:00 2001 From: Jonathan Pryor Date: Tue, 31 Jul 2018 21:27:15 -0400 Subject: [PATCH 046/308] Allow finding NDK location based on $PATH Context: https://github.com/xamarin/androidtools/pull/119 Context: https://devdiv.visualstudio.com/DevDiv/DevDiv%20Team/_build?buildId=1908630 Context: https://github.com/xamarin/androidtools/pull/120 Context: https://devdiv.visualstudio.com/DevDiv/DevDiv%20Team/_build?buildId=1908776 How does `AndroidSdkInfo` determine the appropriate value to return from `AndroidSdkInfo.AndroidNdkPath`? Firstly, it checks `$HOME/.config/xbuild/monodroid-config.xml`. If that file doesn't exist, or it doesn't contain a `/monodroid/android-ndk/@path` attribute value, then `AndroidSdkInfo` checks `$PATH`, and attempts to find `ndk-stack`. If `ndk-stack` can be found in `$PATH`, the containing directory is used: $ mv $HOME/.config/xbuild/monodroid-config.xml{,.none} $ PATH=/path/to/my/ndk:$PATH csharp -r:Xamarin.Android.Tools.AndroidSdk.dll csharp> using System.Diagnostics; csharp> using Xamarin.Android.Tools; csharp> Action logger = (level, value) => Console.WriteLine ($"[{level}] {value}"); csharp> var info = new AndroidSdkInfo (logger); csharp> info.AndroidNdkPath; "/path/to/my/ndk" Unfortunately, commit 2f24c3f1 broke this behavior; afterward, `info.AndroidNdkPath` would return `null`, bceause commit 2f24c3f1 inadvertently broke `$PATH`-based probing for `ndk-stack`. Oops. :-( Specifically, it was the "cleanup" in 2f24c3f1, replacing `AndroidSdkBase.FindExecutableInPath()` with `ProcessUtils.FindExecutablesInPath()`. While these are similarly named, they had an important semantic difference: the former returned the *directory* containing an executable, while the latter returned the full path for the executable. Double oops. Review commit 2f24c3f1 and review all `ProcessUtils.FindExecutablesInPath()` use to ensure that when a directory is desired, we actually get a directory, not the path to `ndk-stack` itself. This allows `$PATH`-based NDK lookup to work again. --- .../Sdks/AndroidSdkUnix.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkUnix.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkUnix.cs index be0d5fb5af0..9aac42695d7 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkUnix.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkUnix.cs @@ -93,7 +93,8 @@ protected override IEnumerable GetAllAvailableAndroidSdks () yield return preferedSdkPath; // Look in PATH - foreach (var path in ProcessUtils.FindExecutablesInPath (Adb)) { + foreach (var adb in ProcessUtils.FindExecutablesInPath (Adb)) { + var path = Path.GetDirectoryName (adb); // Strip off "platform-tools" var dir = Path.GetDirectoryName (path); @@ -119,9 +120,10 @@ protected override IEnumerable GetAllAvailableAndroidNdks () yield return preferedNdkPath; // Look in PATH - foreach (var path in ProcessUtils.FindExecutablesInPath (NdkStack)) { - if (ValidateAndroidNdkLocation (path)) - yield return path; + foreach (var ndkStack in ProcessUtils.FindExecutablesInPath (NdkStack)) { + var ndkDir = Path.GetDirectoryName (ndkStack); + if (ValidateAndroidNdkLocation (ndkDir)) + yield return ndkDir; } } From 9e276fd065301ba800961e07cf1b7fe0004aa36e Mon Sep 17 00:00:00 2001 From: Jonathan Pryor Date: Wed, 1 Aug 2018 14:59:52 -0400 Subject: [PATCH 047/308] [tests] Unit tests for finding NDK location based on $PATH (#40) Commit c5cb7b3a didn't provide unit tests to ensure that the Android NDK could be found based on probing `$PATH` for `ndk-stack`. Update `AndroidSdkInfoTests` to ensure that the NDK to be found based on `$PATH`, as well as the Android SDK and JDK. Additionally, JDK-via-$PATH detection hit a few hiccups: 1. `JdkInfo.GetKnownSystemJdkInfos()` would always check `/usr/libexec/java_home` before `$PATH`, meaning it wasn't possible to test JDK-via-$PATH! Alter the ordering of `JdkInfo.GetKnownSystemJdkInfos()` so that we probe `$PATH` before hitting `/usr/libexec/java_home`. 2. `JdkInfo` requires that the `java.home` property be printed, which `JdkInfoTests.CreateFauxJdk()` didn't print, which caused the fake JDK to be skipped. Fix `JdkInfoTests.CreateFauxJdk()` so that the resulting `java` is acceptable by `JdkInfo`. Additionally, for greater `monodroid-config.xml` sanity (see also 5fd3df80 and 4626d309), instead of backing up the *global* `monodroid-config.xml` file, instead allow the location of `monodroid-config.xml` to be overridden by the unit tests by setting a value accessible by `AppDomain.GetData()`, a'la 5fd3df80. Finally, allow the `logger` parameter to `JdkInfo.GetKnownSystemJdkInfos()` to be optional; if not provided, it will print to the Console. --- .../AndroidSdkInfo.cs | 2 +- .../JdkInfo.cs | 6 +- .../Sdks/AndroidSdkUnix.cs | 10 +- .../Tests/AndroidSdkInfoTests.cs | 140 ++++++++++++++---- .../Tests/JdkInfoTests.cs | 25 ++-- 5 files changed, 140 insertions(+), 43 deletions(-) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidSdkInfo.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidSdkInfo.cs index df13020f646..405160ecdb2 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidSdkInfo.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidSdkInfo.cs @@ -144,7 +144,7 @@ public static void SetPreferredAndroidNdkPath (string path, Action> GetJavaProperties (string java) return props; } - public static IEnumerable GetKnownSystemJdkInfos (Action logger) + public static IEnumerable GetKnownSystemJdkInfos (Action logger = null) { + logger = logger ?? AndroidSdkInfo.DefaultConsoleLogger; + return GetWindowsJdks (logger) .Concat (GetConfiguredJdks (logger)) .Concat (GetMacOSMicrosoftJdks (logger)) .Concat (GetJavaHomeEnvironmentJdks (logger)) - .Concat (GetLibexecJdks (logger)) .Concat (GetPathEnvironmentJdks (logger)) + .Concat (GetLibexecJdks (logger)) .Concat (GetJavaAlternativesJdks (logger)) ; } diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkUnix.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkUnix.cs index 9aac42695d7..0d18f7bed62 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkUnix.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkUnix.cs @@ -205,6 +205,8 @@ void AddToList (string path) } } + static readonly string GetUnixConfigDirOverrideName = $"UnixConfigPath directory override! {typeof (AndroidSdkInfo).AssemblyQualifiedName}"; + // There's a small problem with the code below. Namely, if it runs under `sudo` the folder location // returned by Environment.GetFolderPath will depend on how sudo was invoked: // @@ -231,8 +233,12 @@ void AddToList (string path) // private static string UnixConfigPath { get { - var p = Environment.GetFolderPath (Environment.SpecialFolder.ApplicationData); - return Path.Combine (Path.Combine (p, "xbuild"), "monodroid-config.xml"); + var p = AppDomain.CurrentDomain.GetData (GetUnixConfigDirOverrideName)?.ToString (); + if (string.IsNullOrEmpty (p)) { + p = Environment.GetFolderPath (Environment.SpecialFolder.ApplicationData); + p = Path.Combine (p, "xbuild"); + } + return Path.Combine (p, "monodroid-config.xml"); } } diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/AndroidSdkInfoTests.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/AndroidSdkInfoTests.cs index 5975c68da39..87483ac0eaa 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/AndroidSdkInfoTests.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/AndroidSdkInfoTests.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; +using System.Linq; using System.Text; using System.Xml.Linq; @@ -12,6 +13,35 @@ namespace Xamarin.Android.Tools.Tests [TestFixture] public class AndroidSdkInfoTests { + string UnixConfigDirOverridePath; + string PreferredJdksOverridePath; + + static readonly string GetMacOSMicrosoftJdkPathsOverrideName = $"GetMacOSMicrosoftJdkPaths jdks override! {typeof (JdkInfo).AssemblyQualifiedName}"; + static readonly string GetUnixConfigDirOverrideName = $"UnixConfigPath directory override! {typeof (AndroidSdkInfo).AssemblyQualifiedName}"; + + [OneTimeSetUp] + public void FixtureSetUp () + { + UnixConfigDirOverridePath = Path.GetTempFileName (); + File.Delete (UnixConfigDirOverridePath); + Directory.CreateDirectory (UnixConfigDirOverridePath); + AppDomain.CurrentDomain.SetData (GetUnixConfigDirOverrideName, UnixConfigDirOverridePath); + + PreferredJdksOverridePath = Path.GetTempFileName (); + File.Delete (PreferredJdksOverridePath); + Directory.CreateDirectory (PreferredJdksOverridePath); + AppDomain.CurrentDomain.SetData (GetMacOSMicrosoftJdkPathsOverrideName, PreferredJdksOverridePath); + } + + [OneTimeTearDown] + public void FixtureTearDown () + { + AppDomain.CurrentDomain.SetData (GetMacOSMicrosoftJdkPathsOverrideName, null); + AppDomain.CurrentDomain.SetData (GetUnixConfigDirOverrideName, null); + Directory.Delete (UnixConfigDirOverridePath, recursive: true); + Directory.Delete (PreferredJdksOverridePath, recursive: true); + } + [Test] public void Constructor_NullLogger () { @@ -22,18 +52,7 @@ public void Constructor_NullLogger () [Test] public void Constructor_Paths () { - var root = Path.GetTempFileName (); - File.Delete (root); - Directory.CreateDirectory (root); - - var sdk = Path.Combine (root, "sdk"); - var jdk = Path.Combine (root, "jdk"); - - Directory.CreateDirectory (sdk); - Directory.CreateDirectory (jdk); - - CreateFauxAndroidSdkDirectory (sdk, "26.0.0"); - CreateFauxJavaSdkDirectory (jdk, "1.8.0", out var _, out var _); + CreateSdks (out string root, out string jdk, out string ndk, out string sdk); var logs = new StringWriter (); Action logger = (level, message) => { @@ -41,8 +60,9 @@ public void Constructor_Paths () }; try { - var info = new AndroidSdkInfo (logger, androidSdkPath: sdk, javaSdkPath: jdk, androidNdkPath: null); + var info = new AndroidSdkInfo (logger, androidSdkPath: sdk, androidNdkPath: ndk, javaSdkPath: jdk); + Assert.AreEqual (ndk, info.AndroidNdkPath, "AndroidNdkPath not preserved!"); Assert.AreEqual (sdk, info.AndroidSdkPath, "AndroidSdkPath not preserved!"); Assert.AreEqual (jdk, info.JavaSdkPath, "JavaSdkPath not preserved!"); } @@ -51,8 +71,58 @@ public void Constructor_Paths () } } + [Test] + public void Constructor_SetValuesFromPath () + { + CreateSdks (out string root, out string jdk, out string ndk, out string sdk); + JdkInfoTests.CreateFauxJdk (jdk, "1.8.0"); + + Action logger = (level, message) => { + Console.WriteLine ($"[{level}] {message}"); + }; + var oldPath = Environment.GetEnvironmentVariable ("PATH"); + try { + var paths = new List () { + Path.Combine (jdk, "bin"), + ndk, + Path.Combine (sdk, "platform-tools"), + }; + paths.AddRange (oldPath.Split (new[]{Path.PathSeparator}, StringSplitOptions.RemoveEmptyEntries)); + Environment.SetEnvironmentVariable ("PATH", string.Join (Path.PathSeparator.ToString (), paths)); + + var info = new AndroidSdkInfo (logger); + + Assert.AreEqual (ndk, info.AndroidNdkPath, "AndroidNdkPath not set from $PATH!"); + Assert.AreEqual (sdk, info.AndroidSdkPath, "AndroidSdkPath not set from $PATH!"); + Assert.AreEqual (jdk, info.JavaSdkPath, "JavaSdkPath not set from $PATH!"); + } + finally { + Environment.SetEnvironmentVariable ("PATH", oldPath); + Directory.Delete (root, recursive: true); + } + } + static bool IsWindows => OS.IsWindows; + static void CreateSdks (out string root, out string jdk, out string ndk, out string sdk) + { + root = Path.GetTempFileName (); + File.Delete (root); + Directory.CreateDirectory (root); + + ndk = Path.Combine (root, "ndk"); + sdk = Path.Combine (root, "sdk"); + jdk = Path.Combine (root, "jdk"); + + Directory.CreateDirectory (sdk); + Directory.CreateDirectory (ndk); + Directory.CreateDirectory (jdk); + + CreateFauxAndroidSdkDirectory (sdk, "26.0.0"); + CreateFauxAndroidNdkDirectory (ndk); + CreateFauxJavaSdkDirectory (jdk, "1.8.0", out var _, out var _); + } + static void CreateFauxAndroidSdkDirectory (string androidSdkDirectory, string buildToolsVersion, ApiInfo [] apiLevels = null) { var androidSdkToolsPath = Path.Combine (androidSdkDirectory, "tools"); @@ -90,7 +160,28 @@ struct ApiInfo { public string Id; } - protected string CreateFauxJavaSdkDirectory (string javaPath, string javaVersion, out string javaExe, out string javacExe) + static void CreateFauxAndroidNdkDirectory (string androidNdkDirectory) + { + File.WriteAllText (Path.Combine (androidNdkDirectory, "ndk-stack"), ""); + File.WriteAllText (Path.Combine (androidNdkDirectory, "ndk-stack.cmd"), ""); + + string prebuiltHostName = ""; + if (OS.IsWindows) + prebuiltHostName = "windows-x86_64"; + else if (OS.IsMac) + prebuiltHostName = "darwin-x86_64"; + else + prebuiltHostName = "linux-x86_64"; + + + var toolchainsDir = Path.Combine (androidNdkDirectory, "toolchains"); + var armToolchainDir = Path.Combine (toolchainsDir, "arm-linux-androideabi-4.9"); + var armPrebuiltDir = Path.Combine (armToolchainDir, "prebuilt", prebuiltHostName); + + Directory.CreateDirectory (armPrebuiltDir); + } + + static string CreateFauxJavaSdkDirectory (string javaPath, string javaVersion, out string javaExe, out string javacExe) { javaExe = IsWindows ? "Java.cmd" : "java.bash"; javacExe = IsWindows ? "Javac.cmd" : "javac.bash"; @@ -106,7 +197,7 @@ protected string CreateFauxJavaSdkDirectory (string javaPath, string javaVersion return javaPath; } - void CreateFauxJavaExe (string javaExeFullPath, string version) + static void CreateFauxJavaExe (string javaExeFullPath, string version) { var sb = new StringBuilder (); if (IsWindows) { @@ -126,7 +217,7 @@ void CreateFauxJavaExe (string javaExeFullPath, string version) } } - void CreateFauxJavacExe (string javacExeFullPath, string version) + static void CreateFauxJavacExe (string javacExeFullPath, string version) { var sb = new StringBuilder (); if (IsWindows) { @@ -142,7 +233,7 @@ void CreateFauxJavacExe (string javacExeFullPath, string version) } } - protected void Exec (string exe, string args) + static void Exec (string exe, string args) { var psi = new ProcessStartInfo { FileName = exe, @@ -168,10 +259,6 @@ public void DetectAndSetPreferredJavaSdkPathToLatest () Action logger = (level, message) => { Console.WriteLine ($"[{level}] {message}"); }; - var jdks = Path.GetTempFileName (); - File.Delete (jdks); - Directory.CreateDirectory (jdks); - AppDomain.CurrentDomain.SetData ($"GetMacOSMicrosoftJdkPaths jdks override! {typeof (JdkInfo).AssemblyQualifiedName}", jdks); var backupConfig = UnixConfigPath + "." + Path.GetRandomFileName (); try { @@ -180,7 +267,7 @@ public void DetectAndSetPreferredJavaSdkPathToLatest () return; } Assert.Throws(() => AndroidSdkInfo.DetectAndSetPreferredJavaSdkPathToLatest (logger)); - var newJdkPath = Path.Combine (jdks, "microsoft_dist_openjdk_1.8.999"); + var newJdkPath = Path.Combine (PreferredJdksOverridePath, "microsoft_dist_openjdk_1.8.999"); JdkInfoTests.CreateFauxJdk (newJdkPath, "1.8.999"); if (File.Exists (UnixConfigPath)) @@ -190,8 +277,6 @@ public void DetectAndSetPreferredJavaSdkPathToLatest () AssertJdkPath (newJdkPath); } finally { - AppDomain.CurrentDomain.SetData ($"GetMacOSMicrosoftJdkPaths jdks override! {typeof (JdkInfo).AssemblyQualifiedName}", null); - Directory.Delete (jdks, recursive: true); if (File.Exists (backupConfig)) { File.Delete (UnixConfigPath); File.Move (backupConfig, UnixConfigPath); @@ -199,7 +284,7 @@ public void DetectAndSetPreferredJavaSdkPathToLatest () } } - static void AssertJdkPath (string expectedJdkPath) + void AssertJdkPath (string expectedJdkPath) { var config_file = XDocument.Load (UnixConfigPath); var javaEl = config_file.Root.Element ("java-sdk"); @@ -208,10 +293,9 @@ static void AssertJdkPath (string expectedJdkPath) Assert.AreEqual (expectedJdkPath, actualJdkPath); } - static string UnixConfigPath { + string UnixConfigPath { get { - var p = Environment.GetFolderPath (Environment.SpecialFolder.ApplicationData); - return Path.Combine (Path.Combine (p, "xbuild"), "monodroid-config.xml"); + return Path.Combine (UnixConfigDirOverridePath, "monodroid-config.xml"); } } } diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/JdkInfoTests.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/JdkInfoTests.cs index 105cf3d5151..4fd475890e0 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/JdkInfoTests.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/JdkInfoTests.cs @@ -58,8 +58,18 @@ internal static void CreateFauxJdk (string dir, string version) Directory.CreateDirectory (jli); Directory.CreateDirectory (jre); + + string java = + $"echo Property settings:{Environment.NewLine}" + + $"echo \" java.home = {dir}\"{Environment.NewLine}" + + $"echo \" java.vendor = Xamarin.Android Unit Tests\"{Environment.NewLine}" + + $"echo \" java.version = 100.100.100\"{Environment.NewLine}" + + $"echo \" xamarin.multi-line = line the first\"{Environment.NewLine}" + + $"echo \" line the second\"{Environment.NewLine}" + + $"echo \" .\"{Environment.NewLine}"; + CreateShellScript (Path.Combine (bin, "jar"), ""); - CreateShellScript (Path.Combine (bin, "java"), JavaScript); + CreateShellScript (Path.Combine (bin, "java"), java); CreateShellScript (Path.Combine (bin, "javac"), ""); CreateShellScript (Path.Combine (dir, "jli", "libjli.dylib"), ""); CreateShellScript (Path.Combine (jre, "libjvm.so"), ""); @@ -72,14 +82,6 @@ public void DeleteFauxJdk () Directory.Delete (FauxJdkDir, recursive: true); } - static readonly string JavaScript = - $"echo Property settings:{Environment.NewLine}" + - $"echo \" java.vendor = Xamarin.Android Unit Tests\"{Environment.NewLine}" + - $"echo \" java.version = 100.100.100\"{Environment.NewLine}" + - $"echo \" xamarin.multi-line = line the first\"{Environment.NewLine}" + - $"echo \" line the second\"{Environment.NewLine}" + - $"echo \" .\"{Environment.NewLine}"; - static void CreateShellScript (string path, string contents) { if (OS.IsWindows) @@ -142,11 +144,14 @@ public void JavaSettingsProperties () { var jdk = new JdkInfo (FauxJdkDir); - Assert.AreEqual (3, jdk.JavaSettingsPropertyKeys.Count ()); + Assert.AreEqual (4, jdk.JavaSettingsPropertyKeys.Count ()); Assert.IsFalse(jdk.GetJavaSettingsPropertyValue ("does-not-exist", out var _)); Assert.IsFalse(jdk.GetJavaSettingsPropertyValues ("does-not-exist", out var _)); + Assert.IsTrue (jdk.GetJavaSettingsPropertyValue ("java.home", out var home)); + Assert.AreEqual (FauxJdkDir, home); + Assert.IsTrue (jdk.GetJavaSettingsPropertyValue ("java.version", out var version)); Assert.AreEqual ("100.100.100", version); From a29f82ac6b6bf1f80f36fb20eb46ad50c8f89aa8 Mon Sep 17 00:00:00 2001 From: Jonathan Pryor Date: Wed, 1 Aug 2018 16:51:36 -0400 Subject: [PATCH 048/308] Don't require quotes around `release` values (#41) Fixes: https://github.com/xamarin/xamarin-android-tools/issues/39 The `release` file contained within the [Microsoft OpenJDK Distribution Preview][0] contains an entry which broke the `release` parser within `JdkInfo`, an entry which doesn't contain double quotes around the value: BUILD_NUMBER=9 This causes an exception to be thrown when `JdkInfo` attempts to parse the `release` file: System.ArgumentOutOfRangeException : Length cannot be less than zero. Parameter name: length at System.String.Substring (System.Int32 startIndex, System.Int32 length) [0x0004a] in <7e59c4ad7b424f9eab0db1b78ec1804f>:0 at Xamarin.Android.Tools.JdkInfo.GetReleaseProperties () [0x0006b] in <3975ab851981476abded14492926b261>:0 at Xamarin.Android.Tools.JdkInfo..ctor (System.String homePath) [0x00159] in <3975ab851981476abded14492926b261>:0 Update `JdkInfo.GetReleaseProperties()` so that the double-quotes around values are *optional* and not required, so that this `release` file may be processed without throwing an exception. [0]: https://dl.xamarin.com/OpenJDK/mac/microsoft-dist-openjdk-1.8.0.9.zip --- .../Xamarin.Android.Tools.AndroidSdk/JdkInfo.cs | 15 ++++++++++----- .../Tests/JdkInfoTests.cs | 6 +++++- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/JdkInfo.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/JdkInfo.cs index a786492b2ba..9cf7fb67c20 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/JdkInfo.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/JdkInfo.cs @@ -144,21 +144,26 @@ Version GetJavaVersion () ReadOnlyDictionary GetReleaseProperties () { + var props = new Dictionary (); var releasePath = Path.Combine (HomePath, "release"); if (!File.Exists (releasePath)) - return new ReadOnlyDictionary (new Dictionary ()); + return new ReadOnlyDictionary(props); - var props = new Dictionary (); using (var release = File.OpenText (releasePath)) { string line; while ((line = release.ReadLine ()) != null) { - const string PropertyDelim = "=\""; + line = line.Trim (); + const string PropertyDelim = "="; int delim = line.IndexOf (PropertyDelim, StringComparison.Ordinal); if (delim < 0) { props [line] = ""; + continue; + } + string key = line.Substring (0, delim).Trim (); + string value = line.Substring (delim + PropertyDelim.Length).Trim (); + if (value.StartsWith ("\"", StringComparison.Ordinal) && value.EndsWith ("\"", StringComparison.Ordinal)) { + value = value.Substring (1, value.Length - 2); } - string key = line.Substring (0, delim); - string value = line.Substring (delim + PropertyDelim.Length, line.Length - delim - PropertyDelim.Length - 1); props [key] = value; } } diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/JdkInfoTests.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/JdkInfoTests.cs index 4fd475890e0..9f511b25ece 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/JdkInfoTests.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/JdkInfoTests.cs @@ -46,6 +46,8 @@ internal static void CreateFauxJdk (string dir, string version) using (var release = new StreamWriter (Path.Combine (dir, "release"))) { release.WriteLine ($"JAVA_VERSION=\"{version}\""); + release.WriteLine ($"BUILD_NUMBER=42"); + release.WriteLine ($"JUST_A_KEY"); } var bin = Path.Combine (dir, "bin"); @@ -135,8 +137,10 @@ public void ReleaseProperties () { var jdk = new JdkInfo (FauxJdkDir); - Assert.AreEqual (1, jdk.ReleaseProperties.Count); + Assert.AreEqual (3, jdk.ReleaseProperties.Count); Assert.AreEqual ("1.2.3.4", jdk.ReleaseProperties ["JAVA_VERSION"]); + Assert.AreEqual ("42", jdk.ReleaseProperties ["BUILD_NUMBER"]); + Assert.AreEqual ("", jdk.ReleaseProperties ["JUST_A_KEY"]); } [Test] From 3df65ff42fc6aa1d0c7bc63f757272b23ff90483 Mon Sep 17 00:00:00 2001 From: Jonathan Pryor Date: Fri, 3 Aug 2018 11:41:59 -0400 Subject: [PATCH 049/308] Allow an optional locator to be provided to JdkInfo (#43) Context: https://github.com/xamarin/xamarin-android/pull/2004#issuecomment-410243364 There are a two (related) "usability" issues with `JdkInfo.GetKnownSystemJdkInfos()`: 1. It returns duplicate paths, and 2. There's no way of knowing where a path "came from" For example: $ csharp -r:bin/Debug/Xamarin.Android.Tools.AndroidSdk.dll csharp> using Xamarin.Android.Tools; csharp> JdkInfo.GetKnownSystemJdkInfos(); { JdkInfo(Version=1.8.0.77, Vendor="Oracle Corporation", HomePath="/Library/Java/JavaVirtualMachines/jdk1.8.0_77.jdk/Contents/Home"), Not a valid JDK directory: `/Library/Java/JavaVirtualMachines/1.6.0_65-b14-462.jdk/Contents/Home` System.ArgumentException: Could not find required file `jvm` within `/Library/Java/JavaVirtualMachines/1.6.0_65-b14-462.jdk/Contents/Home`; is this a valid JDK? Parameter name: homePath at Xamarin.Android.Tools.JdkInfo.ValidateFile (System.String name, System.String path) [0x00025] in <087d9a26808b4d2d9c5d2844064ae952>:0 at Xamarin.Android.Tools.JdkInfo..ctor (System.String homePath, System.String locator) [0x00120] in <087d9a26808b4d2d9c5d2844064ae952>:0 at Xamarin.Android.Tools.JdkInfo.TryGetJdkInfo (System.String path, System.Action`2[T1,T2] logger, System.String locator) [0x00004] in <087d9a26808b4d2d9c5d2844064ae952>:0 JdkInfo(Version=1.8.0.77, Vendor="Oracle Corporation", HomePath="/Library/Java/JavaVirtualMachines/jdk1.8.0_77.jdk/Contents/Home"), } Notice that on my machine `jdk1.8.0_77.jdk` is listed *twice*. Why is it listed twice? It's listed twice because it comes from two different "locators": `JdkInfo.GetPathEnvironmentJdks()`, which reads `$PATH`, and `JdkInfo.GetLibexecJdks()`, which parses the output of `/usr/libexec/java_home -X`. If `$JAVA_HOME` were set to the same location, it would be listed three times. As-is, there's no way of knowing where a path is "coming from"; the "locator" -- what found the path -- isn't known. We could `.Distinct()` the output so that we don't have duplicates, but I *like* the idea of duplicates, as it shows that we're using multiple locators. Then we hit [xamarin-android PR #2004][0], in which the `xamarin-android-tools` bump within `xamarin-android` would fail, *depending on which machine ran the unit tests*: Xamarin.Android.Common.targets(691,2): warning : Not a valid JDK directory: `/var/folders/19/hjm21bn17913_0ck7nh3thdr0000gn/T/tmp731daee6.tmp/microsoft_dist_openjdk_1.8.999` Where did that path come from?! Update `JdkInfo.TryGetJdkInfo()` so that it takes an additional "locator" parameter, which is publicly visible as the new `JdkInfo.Locator` property, so that when we encounter an invalid JDK directory, we know where that path came from: Xamarin.Android.Common.targets(691,2): warning : Not a valid JDK directory: `/var/folders/19/hjm21bn17913_0ck7nh3thdr0000gn/T/tmp731daee6.tmp/microsoft_dist_openjdk_1.8.999`; via locator: monodroid-config.xml *Now* we can see that the invalid (bizarro!) JDK path is coming from `monodroid-config.xml`, allowing us to deduce that `monodroid-config.xml` is "bad" (because the unit tests in 5fd3df80 incorrectly restored the original `monodroid-config.xml` file, meaning the state of the machine after running that unit test is forever suspect!). The additional inclusion of Locator information makes it much easier to reason about things, and the inclusion of duplicates makes sense: $ csharp -r:bin/Debug/Xamarin.Android.Tools.AndroidSdk.dll csharp> using Xamarin.Android.Tools; csharp> JdkInfo.GetKnownSystemJdkInfos(); { JdkInfo(Version=1.8.0.77, Vendor="Oracle Corporation", HomePath="/Library/Java/JavaVirtualMachines/jdk1.8.0_77.jdk/Contents/Home", Locator="$PATH"), Not a valid JDK directory: `/Library/Java/JavaVirtualMachines/1.6.0_65-b14-462.jdk/Contents/Home` System.ArgumentException: Could not find required file `jvm` within `/Library/Java/JavaVirtualMachines/1.6.0_65-b14-462.jdk/Contents/Home`; is this a valid JDK? Parameter name: homePath at Xamarin.Android.Tools.JdkInfo.ValidateFile (System.String name, System.String path) [0x00025] in <087d9a26808b4d2d9c5d2844064ae952>:0 at Xamarin.Android.Tools.JdkInfo..ctor (System.String homePath, System.String locator) [0x00120] in <087d9a26808b4d2d9c5d2844064ae952>:0 at Xamarin.Android.Tools.JdkInfo.TryGetJdkInfo (System.String path, System.Action`2[T1,T2] logger, System.String locator) [0x00004] in <087d9a26808b4d2d9c5d2844064ae952>:0 JdkInfo(Version=1.8.0.77, Vendor="Oracle Corporation", HomePath="/Library/Java/JavaVirtualMachines/jdk1.8.0_77.jdk/Contents/Home", Locator="`/usr/libexec/java_home -X`") } [0]: https://github.com/xamarin/xamarin-android/pull/2004 --- .../JdkInfo.cs | 30 ++++++++++++------- .../Sdks/AndroidSdkWindows.cs | 17 ++++++----- 2 files changed, 28 insertions(+), 19 deletions(-) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/JdkInfo.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/JdkInfo.cs index 9cf7fb67c20..8aad2d2ab1f 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/JdkInfo.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/JdkInfo.cs @@ -16,6 +16,8 @@ public class JdkInfo { public string HomePath {get;} + public string Locator {get;} + public string JarPath {get;} public string JavaPath {get;} public string JavacPath {get;} @@ -76,9 +78,15 @@ public JdkInfo (string homePath) javaVersion = new Lazy (GetJavaVersion, LazyThreadSafetyMode.ExecutionAndPublication); } + public JdkInfo (string homePath, string locator) + : this (homePath) + { + Locator = locator; + } + public override string ToString() { - return $"JdkInfo(Version={Version}, Vendor=\"{Vendor}\", HomePath=\"{HomePath}\")"; + return $"JdkInfo(Version={Version}, Vendor=\"{Vendor}\", HomePath=\"{HomePath}\", Locator=\"{Locator}\")"; } public bool GetJavaSettingsPropertyValues (string key, out IEnumerable value) @@ -229,7 +237,7 @@ public static IEnumerable GetKnownSystemJdkInfos (Action GetConfiguredJdks (Action logger) { return GetConfiguredJdkPaths (logger) - .Select (p => TryGetJdkInfo (p, logger)) + .Select (p => TryGetJdkInfo (p, logger, "monodroid-config.xml")) .Where (jdk => jdk != null) .OrderByDescending (jdk => jdk, JdkInfoVersionComparer.Default); } @@ -246,7 +254,7 @@ static IEnumerable GetConfiguredJdkPaths (Action log internal static IEnumerable GetMacOSMicrosoftJdks (Action logger) { return GetMacOSMicrosoftJdkPaths () - .Select (p => TryGetJdkInfo (p, logger)) + .Select (p => TryGetJdkInfo (p, logger, "$HOME/Library/Developer/Xamarin/jdk")) .Where (jdk => jdk != null) .OrderByDescending (jdk => jdk, JdkInfoVersionComparer.Default); } @@ -265,14 +273,14 @@ static IEnumerable GetMacOSMicrosoftJdkPaths () return Directory.EnumerateDirectories (jdks); } - static JdkInfo TryGetJdkInfo (string path, Action logger) + static JdkInfo TryGetJdkInfo (string path, Action logger, string locator) { JdkInfo jdk = null; try { - jdk = new JdkInfo (path); + jdk = new JdkInfo (path, locator); } catch (Exception e) { - logger (TraceLevel.Warning, $"Not a valid JDK directory: `{path}`"); + logger (TraceLevel.Warning, $"Not a valid JDK directory: `{path}`; via locator: {locator}"); logger (TraceLevel.Verbose, e.ToString ()); } return jdk; @@ -290,7 +298,7 @@ static IEnumerable GetJavaHomeEnvironmentJdks (Action GetLibexecJdks (Action logger) { return GetLibexecJdkPaths (logger) .Distinct () - .Select (p => TryGetJdkInfo (p, logger)) + .Select (p => TryGetJdkInfo (p, logger, "`/usr/libexec/java_home -X`")) .Where (jdk => jdk != null) .OrderByDescending (jdk => jdk, JdkInfoVersionComparer.Default); } @@ -339,7 +347,7 @@ static IEnumerable GetJavaAlternativesJdks (Action { return GetJavaAlternativesJdkPaths () .Distinct () - .Select (p => TryGetJdkInfo (p, logger)) + .Select (p => TryGetJdkInfo (p, logger, "`/usr/sbin/update-java-alternatives -l`")) .Where (jdk => jdk != null); } @@ -372,7 +380,7 @@ static IEnumerable GetLibJvmJdks (Action logger) { return GetLibJvmJdkPaths () .Distinct () - .Select (p => TryGetJdkInfo (p, logger)) + .Select (p => TryGetJdkInfo (p, logger, "`ls /usr/lib/jvm/*`")) .Where (jdk => jdk != null) .OrderByDescending (jdk => jdk, JdkInfoVersionComparer.Default); } @@ -394,7 +402,7 @@ static IEnumerable GetLibJvmJdkPaths () static IEnumerable GetPathEnvironmentJdks (Action logger) { return GetPathEnvironmentJdkPaths () - .Select (p => TryGetJdkInfo (p, logger)) + .Select (p => TryGetJdkInfo (p, logger, "$PATH")) .Where (jdk => jdk != null); } diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkWindows.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkWindows.cs index 32a0bdc93be..f05a5b6ec8e 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkWindows.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkWindows.cs @@ -112,28 +112,29 @@ protected override string GetJavaSdkPath () internal static IEnumerable GetJdkInfos (Action logger) { - JdkInfo TryGetJdkInfo (string path) + JdkInfo TryGetJdkInfo (string path, string locator) { JdkInfo jdk = null; try { - jdk = new JdkInfo (path); + jdk = new JdkInfo (path, locator); } catch (Exception e) { - logger (TraceLevel.Warning, e.ToString ()); + logger (TraceLevel.Warning, $"Not a valid JDK directory: `{path}`; via category: {locator}"); + logger (TraceLevel.Verbose, e.ToString ()); } return jdk; } - IEnumerable ToJdkInfos (IEnumerable paths) + IEnumerable ToJdkInfos (IEnumerable paths, string locator) { - return paths.Select (TryGetJdkInfo) + return paths.Select (p => TryGetJdkInfo (p, locator)) .Where (jdk => jdk != null) .OrderByDescending (jdk => jdk, JdkInfoVersionComparer.Default); } - return ToJdkInfos (GetPreferredJdkPaths ()) - .Concat (ToJdkInfos (GetOpenJdkPaths ())) - .Concat (ToJdkInfos (GetOracleJdkPaths ())); + return ToJdkInfos (GetPreferredJdkPaths (), "Preferred Registry") + .Concat (ToJdkInfos (GetOpenJdkPaths (), "OpenJDK")) + .Concat (ToJdkInfos (GetOracleJdkPaths (), "Oracle JDK")); } private static IEnumerable GetPreferredJdkPaths () From 81a5f4c936317574043d31322767e498991b8159 Mon Sep 17 00:00:00 2001 From: Jonathan Pryor Date: Fri, 10 Aug 2018 08:37:31 -0400 Subject: [PATCH 050/308] Take BUILD_NUMBER into consideration for Version sorting (#45) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes: http://work.devdiv.io/661401 The version numbers which the Microsoft OpenJDK packages provide was not consistent with what `JdkInfo` expected: the `release` file would have a `JAVA_VERSION` value which was a three-tuple, and didn't include the `BUILD_NUMBER` value. For example: JAVA_VERSION="1.8.0" BUILD_NUMBER=7 Unfortunately, `JdkInfo.GetJavaVersion()` was only taking `JAVA_VERSION` into consideration, so when multiple different OpenJDK packages were processed which all had the *same* `JAVA_VERSION` value but *different* `BUILD_NUMBER` values, `JdkInfo.GetMacOSMicrosoftJdks()` returned the "wrong" one first. (In actuality, the one it returned first was not knowable, and was probably whatever `Directory.EnumerateDirectories()` returned first.) Simple enough, we just need to take `BUILD_NUMBER` into consideration as part of constructing `JdkInfo.Version`, right? Not so fast. Turns Out™ that the version value held within `JAVA_VERSION` or the `java.version` property -- which need not be identical! -- can also contain `-`, not just `_`. A "Java Version" is *really" (at least) 4 optional parts: JAVA_VERSION : VERSION ('_' UPDATE)? ('-' MILESTONE)? ('-' SUFFIX)? Which means Java version values such as `1.8.0_1-9-microsoft-b00` are possible, from various different locations, e.g. the `version` value vs. the `build` value in `java -version` output: $ bin/java -version openjdk version "1.8.0-9" OpenJDK Runtime Environment (build 1.8.0-9-microsoft-b00) OpenJDK 64-Bit Server VM (build 25.71-b00, mixed mode) Instead of trying to top-down parse a version number, go for a "bottom-up" parsing: 1. Replace all *non-digit* characters with `.` 2. Split the value on (1) on `.`, creating an array, and remove all empty values. 3. Separately parse the values in (2) as part of `System.Version` construction. This allows at least a sane-ish, plausible, `Version` construction. `1.8.0_161-b12` will be parsed as `new Version(1, 8, 0, 161)` (as we'll just grab up to the first four values), and we'll gracefully ignore any other non-digit characters in the string. This allows us to better construct a `Version` value for Microsoft OpenJDK packages, allowing us in turn to *sort* the packages, which allows `JdkInfo>GetMacOSMicrosoftJdks()` to return the highest version *first*, as is desired. --- .../AndroidSdkInfo.cs | 2 +- .../JdkInfo.cs | 49 ++++++--- .../Tests/AndroidSdkInfoTests.cs | 6 +- .../Tests/JdkInfoTests.cs | 104 ++++++++++++++++-- 4 files changed, 129 insertions(+), 32 deletions(-) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidSdkInfo.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidSdkInfo.cs index 405160ecdb2..7e710bc599a 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidSdkInfo.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidSdkInfo.cs @@ -151,7 +151,7 @@ internal static void DefaultConsoleLogger (TraceLevel level, string message) Console.Error.WriteLine (message); break; default: - Console.WriteLine (message); + Console.WriteLine ($"[{level}] {message}"); break; } } diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/JdkInfo.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/JdkInfo.cs index 8aad2d2ab1f..6e1026a8844 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/JdkInfo.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/JdkInfo.cs @@ -53,7 +53,7 @@ public JdkInfo (string homePath) JavaPath = ProcessUtils.FindExecutablesInDirectory (binPath, "java").FirstOrDefault (); JavacPath = ProcessUtils.FindExecutablesInDirectory (binPath, "javac").FirstOrDefault (); JdkJvmPath = OS.IsMac - ? FindLibrariesInDirectory (HomePath, "jli").FirstOrDefault () + ? FindLibrariesInDirectory (Path.Combine (HomePath, "jre"), "jli").FirstOrDefault () : FindLibrariesInDirectory (Path.Combine (HomePath, "jre"), "jvm").FirstOrDefault (); ValidateFile ("jar", JarPath); @@ -115,6 +115,8 @@ public bool GetJavaSettingsPropertyValue (string key, out string value) static IEnumerable FindLibrariesInDirectory (string dir, string libraryName) { + if (!Directory.Exists (dir)) + return Enumerable.Empty (); var library = string.Format (OS.NativeLibraryFormat, libraryName); return Directory.EnumerateFiles (dir, library, SearchOption.AllDirectories); } @@ -125,29 +127,44 @@ void ValidateFile (string name, string path) throw new ArgumentException ($"Could not find required file `{name}` within `{HomePath}`; is this a valid JDK?", "homePath"); } - static Regex VersionExtractor = new Regex (@"(?[\d]+(\.\d+)+)(_(?\d+))?", RegexOptions.Compiled); + static Regex NonDigitMatcher = new Regex (@"[^\d]", RegexOptions.Compiled | RegexOptions.CultureInvariant); Version GetJavaVersion () { string version = null; - if (!ReleaseProperties.TryGetValue ("JAVA_VERSION", out version)) { - if (GetJavaSettingsPropertyValue ("java.version", out string vs)) - version = vs; + if (ReleaseProperties.TryGetValue ("JAVA_VERSION", out version) && !string.IsNullOrEmpty (version)) { + version = GetParsableVersion (version); + if (ReleaseProperties.TryGetValue ("BUILD_NUMBER", out var build) && !string.IsNullOrEmpty (build)) + version += "." + build; + } + else if (GetJavaSettingsPropertyValue ("java.version", out version) && !string.IsNullOrEmpty (version)) { + version = GetParsableVersion (version); + } + if (string.IsNullOrEmpty (version)) + throw new NotSupportedException ("Could not determine Java version."); + var normalizedVersion = NonDigitMatcher.Replace (version, "."); + var versionParts = normalizedVersion.Split (new[]{"."}, StringSplitOptions.RemoveEmptyEntries); + + try { + if (versionParts.Length < 2) + return null; + if (versionParts.Length == 2) + return new Version (major: int.Parse (versionParts [0]), minor: int.Parse (versionParts [1])); + if (versionParts.Length == 3) + return new Version (major: int.Parse (versionParts [0]), minor: int.Parse (versionParts [1]), build: int.Parse (versionParts [2])); + // We just ignore elements 4+ + return new Version (major: int.Parse (versionParts [0]), minor: int.Parse (versionParts [1]), build: int.Parse (versionParts [2]), revision: int.Parse (versionParts [3])); } - if (version == null) - throw new NotSupportedException ("Could not determine Java version"); - var m = VersionExtractor.Match (version); - if (!m.Success) + catch (Exception) { return null; - version = m.Groups ["version"].Value; - var patch = m.Groups ["patch"].Value; - if (!string.IsNullOrEmpty (patch)) - version += "." + patch; + } + } + + static string GetParsableVersion (string version) + { if (!version.Contains (".")) version += ".0"; - if (Version.TryParse (version, out Version v)) - return v; - return null; + return version; } ReadOnlyDictionary GetReleaseProperties () diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/AndroidSdkInfoTests.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/AndroidSdkInfoTests.cs index 87483ac0eaa..3f71a3e8813 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/AndroidSdkInfoTests.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/AndroidSdkInfoTests.cs @@ -75,7 +75,7 @@ public void Constructor_Paths () public void Constructor_SetValuesFromPath () { CreateSdks (out string root, out string jdk, out string ndk, out string sdk); - JdkInfoTests.CreateFauxJdk (jdk, "1.8.0"); + JdkInfoTests.CreateFauxJdk (jdk, releaseVersion: "1.8.0", releaseBuildNumber: "42", javaVersion: "100.100.100_100"); Action logger = (level, message) => { Console.WriteLine ($"[{level}] {message}"); @@ -267,8 +267,8 @@ public void DetectAndSetPreferredJavaSdkPathToLatest () return; } Assert.Throws(() => AndroidSdkInfo.DetectAndSetPreferredJavaSdkPathToLatest (logger)); - var newJdkPath = Path.Combine (PreferredJdksOverridePath, "microsoft_dist_openjdk_1.8.999"); - JdkInfoTests.CreateFauxJdk (newJdkPath, "1.8.999"); + var newJdkPath = Path.Combine (PreferredJdksOverridePath, "microsoft_dist_openjdk_1.8.999.9"); + JdkInfoTests.CreateFauxJdk (newJdkPath, releaseVersion: "1.8.999", releaseBuildNumber: "9", javaVersion: "1.8.999-9"); if (File.Exists (UnixConfigPath)) File.Move (UnixConfigPath, backupConfig); diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/JdkInfoTests.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/JdkInfoTests.cs index 9f511b25ece..c8ad585128e 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/JdkInfoTests.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/JdkInfoTests.cs @@ -35,25 +35,25 @@ public void CreateFauxJdk () var dir = Path.GetTempFileName(); File.Delete (dir); - CreateFauxJdk (dir, "1.2.3.4"); + CreateFauxJdk (dir, releaseVersion: "1.2.3", releaseBuildNumber: "42", javaVersion: "100.100.100-100"); FauxJdkDir = dir; } - internal static void CreateFauxJdk (string dir, string version) + internal static void CreateFauxJdk (string dir, string releaseVersion, string releaseBuildNumber, string javaVersion) { Directory.CreateDirectory (dir); using (var release = new StreamWriter (Path.Combine (dir, "release"))) { - release.WriteLine ($"JAVA_VERSION=\"{version}\""); - release.WriteLine ($"BUILD_NUMBER=42"); + release.WriteLine ($"JAVA_VERSION=\"{releaseVersion}\""); + release.WriteLine ($"BUILD_NUMBER={releaseBuildNumber}"); release.WriteLine ($"JUST_A_KEY"); } var bin = Path.Combine (dir, "bin"); var inc = Path.Combine (dir, "include"); - var jli = Path.Combine (dir, "jli"); var jre = Path.Combine (dir, "jre"); + var jli = Path.Combine (jre, "lib", "jli"); Directory.CreateDirectory (bin); Directory.CreateDirectory (inc); @@ -65,7 +65,7 @@ internal static void CreateFauxJdk (string dir, string version) $"echo Property settings:{Environment.NewLine}" + $"echo \" java.home = {dir}\"{Environment.NewLine}" + $"echo \" java.vendor = Xamarin.Android Unit Tests\"{Environment.NewLine}" + - $"echo \" java.version = 100.100.100\"{Environment.NewLine}" + + $"echo \" java.version = {javaVersion}\"{Environment.NewLine}" + $"echo \" xamarin.multi-line = line the first\"{Environment.NewLine}" + $"echo \" line the second\"{Environment.NewLine}" + $"echo \" .\"{Environment.NewLine}"; @@ -73,7 +73,7 @@ internal static void CreateFauxJdk (string dir, string version) CreateShellScript (Path.Combine (bin, "jar"), ""); CreateShellScript (Path.Combine (bin, "java"), java); CreateShellScript (Path.Combine (bin, "javac"), ""); - CreateShellScript (Path.Combine (dir, "jli", "libjli.dylib"), ""); + CreateShellScript (Path.Combine (jli, "libjli.dylib"), ""); CreateShellScript (Path.Combine (jre, "libjvm.so"), ""); CreateShellScript (Path.Combine (jre, "jvm.dll"), ""); } @@ -127,9 +127,9 @@ public void PathPropertyValidation () public void VersionPrefersRelease () { var jdk = new JdkInfo (FauxJdkDir); - // Note: `release` has JAVA_VERSION=1.2.3.4, while `java` prints java.version=100.100.100. - // We prefer the value within `releas`. - Assert.AreEqual (jdk.Version, new Version ("1.2.3.4")); + // Note: `release` has JAVA_VERSION=1.2.3 + BUILD_NUMBER=42, while `java` prints java.version=100.100.100. + // We prefer the value constructed from `release`. + Assert.AreEqual (jdk.Version, new Version ("1.2.3.42")); } [Test] @@ -138,7 +138,7 @@ public void ReleaseProperties () var jdk = new JdkInfo (FauxJdkDir); Assert.AreEqual (3, jdk.ReleaseProperties.Count); - Assert.AreEqual ("1.2.3.4", jdk.ReleaseProperties ["JAVA_VERSION"]); + Assert.AreEqual ("1.2.3", jdk.ReleaseProperties ["JAVA_VERSION"]); Assert.AreEqual ("42", jdk.ReleaseProperties ["BUILD_NUMBER"]); Assert.AreEqual ("", jdk.ReleaseProperties ["JUST_A_KEY"]); } @@ -157,7 +157,7 @@ public void JavaSettingsProperties () Assert.AreEqual (FauxJdkDir, home); Assert.IsTrue (jdk.GetJavaSettingsPropertyValue ("java.version", out var version)); - Assert.AreEqual ("100.100.100", version); + Assert.AreEqual ("100.100.100-100", version); Assert.IsTrue (jdk.GetJavaSettingsPropertyValue ("java.vendor", out var vendor)); Assert.AreEqual ("Xamarin.Android Unit Tests", vendor); @@ -170,5 +170,85 @@ public void JavaSettingsProperties () Assert.AreEqual ("line the second", lines.ElementAt (1)); Assert.AreEqual (".", lines.ElementAt (2)); } + + [Test] + public void ParseOracleReleaseVersion () + { + var dir = Path.GetTempFileName(); + File.Delete (dir); + + try { + CreateFauxJdk (dir, releaseVersion: "1.2.3_4", releaseBuildNumber: "", javaVersion: "100.100.100_100"); + var jdk = new JdkInfo (dir); + Assert.AreEqual (new Version (1, 2, 3, 4), jdk.Version); + } + finally { + Directory.Delete (dir, recursive: true); + } + } + + [Test] + public void ParseOracleJavaVersion () + { + var dir = Path.GetTempFileName(); + File.Delete (dir); + + try { + CreateFauxJdk (dir, releaseVersion: "", releaseBuildNumber: "", javaVersion: "101.102.103_104"); + var jdk = new JdkInfo (dir); + Assert.AreEqual (new Version (101, 102, 103, 104), jdk.Version); + } + finally { + Directory.Delete (dir, recursive: true); + } + } + + [Test] + public void ParseMicrosoftReleaseVersion () + { + var dir = Path.GetTempFileName(); + File.Delete (dir); + + try { + CreateFauxJdk (dir, releaseVersion: "1.2.3", releaseBuildNumber: "4", javaVersion: "100.100.100_100"); + var jdk = new JdkInfo (dir); + Assert.AreEqual (new Version (1, 2, 3, 4), jdk.Version); + } + finally { + Directory.Delete (dir, recursive: true); + } + } + + [Test] + public void ParseMicrosoftJavaVersion() + { + var dir = Path.GetTempFileName(); + File.Delete (dir); + + try { + CreateFauxJdk (dir, releaseVersion: "", releaseBuildNumber: "", javaVersion: "1.2.3-4"); + var jdk = new JdkInfo (dir); + Assert.AreEqual (new Version (1, 2, 3, 4), jdk.Version); + } + finally { + Directory.Delete (dir, recursive: true); + } + } + + [Test] + public void Version_ThrowsNotSupportedException () + { + var dir = Path.GetTempFileName(); + File.Delete (dir); + + try { + CreateFauxJdk (dir, releaseVersion: "", releaseBuildNumber: "", javaVersion: ""); + var jdk = new JdkInfo (dir); + Assert.Throws (() => { var _ = jdk.Version; }); + } + finally { + Directory.Delete (dir, recursive: true); + } + } } } From 6dec8e20bbd9adeb8421a10e855158debdb478bd Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Tue, 14 Aug 2018 16:21:56 -0500 Subject: [PATCH 051/308] Log what is happening during path selection (#46) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Downstream, in the proprietary parts of Xamarin.Android, there are some strange errors occurring, such as: Task "InstallPackageAssemblies" InstallPackageAssemblies Task ... AndroidSdkPath: /Users/builder/android-toolchain/sdk AndroidNdkPath: /Users/builder/android-toolchain/ndk JavaSdkPath: /usr Xamarin.Android.Common.Debugging.targets(479,2): error ADB0000: Error finding Android/Java SDKs […/monodroid/tests/runtime/Xamarin.Android.RuntimeTests.csproj] Xamarin.Android.Common.Debugging.targets(479,2): error ADB0000: System.InvalidOperationException: Could not determine Android SDK location. Please provide `androidSdkPath`. […/monodroid/tests/runtime/Xamarin.Android.RuntimeTests.csproj] Xamarin.Android.Common.Debugging.targets(479,2): error ADB0000: at Xamarin.Android.Tools.AndroidSdkInfo..ctor (System.Action`2[T1,T2] logger, System.String androidSdkPath, System.String androidNdkPath, System.String javaSdkPath) [0x0004b] in <5fd439f4454d4cc18bc8c35d2b4c840c>:0 […/monodroid-pr/monodroid/tests/runtime/Xamarin.Android.RuntimeTests.csproj] Xamarin.Android.Common.Debugging.targets(479,2): error ADB0000: at Xamarin.AndroidTools.AndroidSdk.Refresh (System.String androidSdkPath, System.String androidNdkPath, System.String javaSdkPath) [0x00000] in <9cb77bd2eef54f10a4aa7622ee40470d>:0 […/monodroid-pr/monodroid/tests/runtime/Xamarin.Android.RuntimeTests.csproj] We are having a lot of trouble even figuring out what is going on, since the EXACT SAME paths worked earlier in the same build. Earlier in the log `ResolveSdks` worked just fine! I'm hoping this logging information will help us troubleshoot this issue and other scenarios in the future. --- .../Sdks/AndroidSdkBase.cs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkBase.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkBase.cs index 1537351d7f4..1ea9121ae0b 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkBase.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkBase.cs @@ -122,7 +122,9 @@ public string NdkHostPlatform { /// public bool ValidateAndroidSdkLocation (string loc) { - return !string.IsNullOrEmpty (loc) && ProcessUtils.FindExecutablesInDirectory (Path.Combine (loc, "platform-tools"), Adb).Any (); + bool result = !string.IsNullOrEmpty (loc) && ProcessUtils.FindExecutablesInDirectory (Path.Combine (loc, "platform-tools"), Adb).Any (); + Logger (TraceLevel.Verbose, $"{nameof (ValidateAndroidSdkLocation)}: `{loc}`, result={result}"); + return result; } /// @@ -130,7 +132,9 @@ public bool ValidateAndroidSdkLocation (string loc) /// public virtual bool ValidateJavaSdkLocation (string loc) { - return !string.IsNullOrEmpty (loc) && ProcessUtils.FindExecutablesInDirectory (Path.Combine (loc, "bin"), JarSigner).Any (); + bool result = !string.IsNullOrEmpty (loc) && ProcessUtils.FindExecutablesInDirectory (Path.Combine (loc, "bin"), JarSigner).Any (); + Logger (TraceLevel.Verbose, $"{nameof (ValidateJavaSdkLocation)}: `{loc}`, result={result}"); + return result; } /// @@ -138,7 +142,9 @@ public virtual bool ValidateJavaSdkLocation (string loc) /// public bool ValidateAndroidNdkLocation (string loc) { - return !string.IsNullOrEmpty (loc) && ProcessUtils.FindExecutablesInDirectory (loc, NdkStack).Any (); + bool result = !string.IsNullOrEmpty (loc) && ProcessUtils.FindExecutablesInDirectory (loc, NdkStack).Any (); + Logger (TraceLevel.Verbose, $"{nameof (ValidateAndroidNdkLocation)}: `{loc}`, result={result}"); + return result; } protected static string NullIfEmpty (string s) From 66a6e93f8518262189ab1f518f93f495fe7e25f9 Mon Sep 17 00:00:00 2001 From: Jonathan Pryor Date: Tue, 14 Aug 2018 20:43:48 -0400 Subject: [PATCH 052/308] Better support no installed JDKs on macOS (#48) What happens if there is no JDK installed on macOS? $ mv ~/Library/Developer/Xamarin/jdk ~/Library/Developer/Xamarin/jdk.bk $ sudo mv /Library/Java/JavaVirtualMachines /Library/Java/JavaVirtualMachines.bk `JdkInfo` doesn't like this, which is to be expected, but *how* it doesn't like it is...bad: $ csharp -r:bin/Debug/Xamarin.Android.Tools.AndroidSdk.dll csharp> Xamarin.Android.Tools.JdkInfo.GetKnownSystemJdkInfos(); Error getting SDK info: System.Xml.XmlException: Data at the root level is invalid. Line 1, position 1. at System.Xml.XmlTextReaderImpl.Throw (System.Exception e) at System.Xml.XmlTextReaderImpl.Throw (System.String res, System.String arg) at System.Xml.XmlTextReaderImpl.Throw (System.String res) at System.Xml.XmlTextReaderImpl.ParseRootLevelWhitespace () at System.Xml.XmlTextReaderImpl.ParseDocumentContent () at System.Xml.XmlTextReaderImpl.Read () at System.Xml.XmlReader.MoveToContent () at System.Xml.Linq.XElement.Load (System.Xml.XmlReader reader, System.Xml.Linq.LoadOptions options) at System.Xml.Linq.XElement.Parse (System.String text, System.Xml.Linq.LoadOptions options) at System.Xml.Linq.XElement.Parse (System.String text) at Xamarin.Android.Tools.JdkInfo+d__54.MoveNext () ... Er, what? The problem is the output of `/usr/libexec/java_home -X`: $ /usr/libexec/java_home -X Unable to find any JVMs matching version "(null)". No Java runtime present, try --request to install. What's happening is that `java_home` is writing XML to stdout, and an error message to stderr. `ProcessUtils.Exec()`, meanwhile, was *merging **both*** `stdout` and `stderr` into *one* stream, and the result was *not* valid XML, hence the `XmlException`. The fix? Allow `stderr` to be separate from `stdout`, so that `JdkInfo.GetLibexecJdkPaths()` can get valid XML. Additionally, because I'm finding it increasingly annoying needing to provide the `logger` parameter to `AndroidSdkInfo`/etc., update the `AndroidSdkInfo` constructor so that `logger` is *optional*. If it isn't provided, we'll write our log messages to `System.Console`. --- .../src/Xamarin.Android.Tools.AndroidSdk/AndroidSdkInfo.cs | 5 ++--- .../src/Xamarin.Android.Tools.AndroidSdk/JdkInfo.cs | 2 +- .../src/Xamarin.Android.Tools.AndroidSdk/ProcessUtils.cs | 6 ++++-- .../Tests/AndroidSdkInfoTests.cs | 7 ------- 4 files changed, 7 insertions(+), 13 deletions(-) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidSdkInfo.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidSdkInfo.cs index 7e710bc599a..5738fb1ff84 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidSdkInfo.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidSdkInfo.cs @@ -10,10 +10,9 @@ public class AndroidSdkInfo { AndroidSdkBase sdk; - public AndroidSdkInfo (Action logger, string androidSdkPath = null, string androidNdkPath = null, string javaSdkPath = null) + public AndroidSdkInfo (Action logger = null, string androidSdkPath = null, string androidNdkPath = null, string javaSdkPath = null) { - if (logger == null) - throw new ArgumentNullException (nameof (logger)); + logger = logger ?? DefaultConsoleLogger; sdk = CreateSdk (logger); sdk.Initialize (androidSdkPath, androidNdkPath, javaSdkPath); diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/JdkInfo.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/JdkInfo.cs index 6e1026a8844..d73b748b075 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/JdkInfo.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/JdkInfo.cs @@ -345,7 +345,7 @@ static IEnumerable GetLibexecJdkPaths (Action logger if (string.IsNullOrEmpty (e.Data)) return; xml.Append (e.Data); - }); + }, includeStderr: false); var plist = XElement.Parse (xml.ToString ()); foreach (var info in plist.Elements ("array").Elements ("dict")) { var JVMHomePath = (XNode) info.Elements ("key").FirstOrDefault (e => e.Value == "JVMHomePath"); diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/ProcessUtils.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/ProcessUtils.cs index 87b02ca6e6b..352eb775d4e 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/ProcessUtils.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/ProcessUtils.cs @@ -131,7 +131,7 @@ public static Task ExecuteToolAsync (string exe, Func logger = null; - Assert.Throws (() => new AndroidSdkInfo (logger)); - } - [Test] public void Constructor_Paths () { From c7e321c25c756780e7aaee5e8695fb30d8f36aa6 Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Thu, 16 Aug 2018 08:22:35 -0500 Subject: [PATCH 053/308] [tests] fix test failures on Windows (#47) Context: https://github.com/xamarin/xamarin-android/blob/b0e6c579f773e55419573e210b648d81b78360ef/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/Utilities/BaseTest.cs#L237 Just a few minor things to get these working: - We need `@echo off` on Windows - `echo` doesn't need quotes, or it echos the quotes - We don't need to emit `jvm.dll` as `jvm.dll.cmd` - There is one test checking `PATH`, which doesn't occur on Windows. It doesn't look in `%PATH%`, so we should ignore it on Windows. --- .../Tests/AndroidSdkInfoTests.cs | 3 +++ .../Tests/JdkInfoTests.cs | 20 +++++++++++-------- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/AndroidSdkInfoTests.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/AndroidSdkInfoTests.cs index 90e9a7a006c..52637245e5b 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/AndroidSdkInfoTests.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/AndroidSdkInfoTests.cs @@ -67,6 +67,9 @@ public void Constructor_Paths () [Test] public void Constructor_SetValuesFromPath () { + if (OS.IsWindows) + Assert.Ignore ("Windows does not look for values in %PATH%"); + CreateSdks (out string root, out string jdk, out string ndk, out string sdk); JdkInfoTests.CreateFauxJdk (jdk, releaseVersion: "1.8.0", releaseBuildNumber: "42", javaVersion: "100.100.100_100"); diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/JdkInfoTests.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/JdkInfoTests.cs index c8ad585128e..122deed28d8 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/JdkInfoTests.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/JdkInfoTests.cs @@ -60,15 +60,19 @@ internal static void CreateFauxJdk (string dir, string releaseVersion, string re Directory.CreateDirectory (jli); Directory.CreateDirectory (jre); - + string quote = OS.IsWindows ? "" : "\""; string java = $"echo Property settings:{Environment.NewLine}" + - $"echo \" java.home = {dir}\"{Environment.NewLine}" + - $"echo \" java.vendor = Xamarin.Android Unit Tests\"{Environment.NewLine}" + - $"echo \" java.version = {javaVersion}\"{Environment.NewLine}" + - $"echo \" xamarin.multi-line = line the first\"{Environment.NewLine}" + - $"echo \" line the second\"{Environment.NewLine}" + - $"echo \" .\"{Environment.NewLine}"; + $"echo {quote} java.home = {dir}{quote}{Environment.NewLine}" + + $"echo {quote} java.vendor = Xamarin.Android Unit Tests{quote}{Environment.NewLine}" + + $"echo {quote} java.version = {javaVersion}{quote}{Environment.NewLine}" + + $"echo {quote} xamarin.multi-line = line the first{quote}{Environment.NewLine}" + + $"echo {quote} line the second{quote}{Environment.NewLine}" + + $"echo {quote} .{quote}{Environment.NewLine}"; + + if (OS.IsWindows) { + java = $"@echo off{Environment.NewLine}{java}"; + } CreateShellScript (Path.Combine (bin, "jar"), ""); CreateShellScript (Path.Combine (bin, "java"), java); @@ -86,7 +90,7 @@ public void DeleteFauxJdk () static void CreateShellScript (string path, string contents) { - if (OS.IsWindows) + if (OS.IsWindows && string.Compare (Path.GetExtension (path), ".dll", true) != 0) path += ".cmd"; using (var script = new StreamWriter (path)) { if (!OS.IsWindows) { From 9970e52fae16318e643d5a6672f124f18d7173d8 Mon Sep 17 00:00:00 2001 From: Connor Adsit Date: Thu, 29 Nov 2018 06:21:34 -0500 Subject: [PATCH 054/308] Add MicroBuild reference + Sign target (#51) * Add MicroBuild reference + Sign target * typo --- .../Xamarin.Android.Tools.AndroidSdk.csproj | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj index 5adcf4fe8f2..68da4fc24bf 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj @@ -31,6 +31,18 @@ 4 false + + + 0.3.0 + runtime; build; native; contentfiles; analyzers + all + + + + + Microsoft + + From 13f1b8de3819304e0cddfbde2e5318d6fc8412cb Mon Sep 17 00:00:00 2001 From: Cody Russell Date: Tue, 11 Dec 2018 14:12:01 -0600 Subject: [PATCH 055/308] Check if System Java is installed (#52) Fixes: http://work.devdiv.io/735545 When Visual Studio for Mac is installed on a "clean" machine which doesn't have a Java installed, two dialog boxes about Java are shown when launching Visual Studio for Mac: > To use the "java" command-line tool you need to install a JDK. This happens because `java` is found via `$PATH` (`JdkInfo.GetPathEnvironmentJdkPaths()`) -- because `/usr/bin/java` exists -- but the `java` which is available is "bad", because executing `java` results in the aforementioned dialog box. Avoid calling `java` from `JdkInfo.GetPathEnvironmentJdkPaths()` by introducing a macOS-specific check *requiring* that `/System/Library/Java/JavaVirtualMachines` both *exist* and contain *sub-directories* before `/usr/bin/java` or `java` are executed. --- .../JdkInfo.cs | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/JdkInfo.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/JdkInfo.cs index d73b748b075..9ec28ed29d9 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/JdkInfo.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/JdkInfo.cs @@ -200,6 +200,23 @@ Dictionary> GetJavaProperties () return GetJavaProperties (ProcessUtils.FindExecutablesInDirectory (Path.Combine (HomePath, "bin"), "java").First ()); } + static bool AnySystemJavasInstalled () + { + if (OS.IsMac) { + string path = Path.Combine (Path.DirectorySeparatorChar + "System", "Library", "Java", "JavaVirtualMachines"); + if (!Directory.Exists (path)) { + return false; + } + + string[] dirs = Directory.GetDirectories (path); + if (dirs == null || dirs.Length == 0) { + return false; + } + } + + return true; + } + static Dictionary> GetJavaProperties (string java) { var javaProps = new ProcessStartInfo { @@ -209,6 +226,10 @@ static Dictionary> GetJavaProperties (string java) var props = new Dictionary> (); string curKey = null; + + if (!AnySystemJavasInstalled () && (java == "/usr/bin/java" || java == "java")) + return props; + ProcessUtils.Exec (javaProps, (o, e) => { const string ContinuedValuePrefix = " "; const string NewValuePrefix = " "; From 6fa650961b1841cc8bbf803d4d2a5574dfa8206d Mon Sep 17 00:00:00 2001 From: Peter Collins Date: Thu, 17 Jan 2019 12:53:14 -0500 Subject: [PATCH 056/308] Add an Azure DevOps project to build, test, and package (#56) An [Azure DevOps build pipeline][0] has been configured in the public Xamarin project to build and test Xamarin.Android.Tools on both Windows and macOS. The steps and environments used in this new build pipeline are defined in the new `azure-pipelines.yaml` file, and are described below. A build status badge has also been added to the README.md file which links to the latest successful build. This link can be used to quickly fetch the latest NuGet package produced by this build. **Windows Steps**: 1. Build `Xamarin.Android.Tools.sln`. 2. Run all unit tests matching the pattern `*-Tests.dll`. 3. Create a NuGet package based on Major, Minor, and Patch versions defined in `azure-pipelines.yaml`. The Patch value is the number of commits since the `nuget.version` file changed. 4. Saves the NuGet package as a build artifact. This build artifact can be downloaded directly from the Azure DevOps build info page. Additionally, these artifact can be published to a public NuGet feed by running the corresponding [Azure DevOps release pipeline][1]. **macOS Steps** 1. Build `Xamarin.Android.Tools.sln`. 2. Run all unit tests contained in `Xamarin.Android.Tools.AndroidSdk-Tests.dll`. This PR also fixes a small bug in an existing unit test which was uncovered by the environments used for the Azure DevOps macOS build pipeline. `JdkInfo.GetKnownSystemJdkInfos()` prefers `$JAVA_HOME` over any valid JDK values in the `$PATH` environment variable. In order for this test to accurately test initializing from `$PATH`, `$JAVA_HOME` must not point to a valid JDK. [0]: https://dev.azure.com/xamarin/Xamarin/_build?definitionId=2&_a=summary [1]: https://dev.azure.com/xamarin/Xamarin/_release?definitionId=1&view=mine --- external/xamarin-android-tools/README.md | 38 +++++++++++++ .../azure-pipelines.yaml | 53 +++++++++++++++++++ external/xamarin-android-tools/nuget.version | 1 + .../Tests/AndroidSdkInfoTests.cs | 7 +++ .../Tests/packages.config | 3 +- .../Xamarin.Android.Tools.AndroidSdk.nuspec | 16 ++++++ 6 files changed, 117 insertions(+), 1 deletion(-) create mode 100644 external/xamarin-android-tools/azure-pipelines.yaml create mode 100644 external/xamarin-android-tools/nuget.version create mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.nuspec diff --git a/external/xamarin-android-tools/README.md b/external/xamarin-android-tools/README.md index aeaf9f10ae1..3fa13ad2c15 100644 --- a/external/xamarin-android-tools/README.md +++ b/external/xamarin-android-tools/README.md @@ -7,6 +7,20 @@ submodule the entire **xamarin-android** repo, which is gigantic. [x-a]: https://github.com/xamarin/xamarin-android +# Build Status + +| Platform | Status | +|-----------------------|--------| +| **macOS** | [![macOS Build Status][azure-macOS-icon]][azure-macOS-status] | +| **Windows** | [![Windows Build Status][azure-Windows-icon]][azure-Windows-status] | + + +[azure-macOS-icon]: https://dev.azure.com/xamarin/Xamarin/_apis/build/status/Xamarin.Android.Tools +[azure-macOS-status]: https://dev.azure.com/xamarin/Xamarin/_build/latest?definitionId=2 +[azure-Windows-icon]: https://dev.azure.com/xamarin/Xamarin/_apis/build/status/Xamarin.Android.Tools +[azure-Windows-status]: https://dev.azure.com/xamarin/Xamarin/_build/latest?definitionId=2 + + # Build Requirements **xamarin-android-tools** requires Mono 4.8 or later and `nuget`. @@ -55,10 +69,34 @@ e.g. `bin\TestDebug`, contains unit tests and related files. # Contributing +Package versioning follows [Semantic Versioning 2.0.0](https://semver.org/). +The major version in the `nuget.version` file should be updated when a breaking change is introduced. +The minor version should be updated when new functionality is added. +The patch version will be automatically determined by the number of commits since the last version change. + This project has adopted the code of conduct defined by the Contributor Covenant to clarify expected behavior in our community. For more information, see the [.NET Foundation Code of Conduct](http://www.dotnetfoundation.org/code-of-conduct). +# Distribution + +Xamarin.Android.Tools.AndroidSdk nupkg files are produced for every build which occurrs on [Azure Devops](https://dev.azure.com/xamarin/Xamarin/_build?definitionId=2&_a=summary). +To download one of these packages, navigate to the build you are interested in and click on the `Artifacts` button. + +Alternatively, "unofficial" releases are currently hosted on MyGet. +Add the feed to your project's `NuGet.config` to reference these packages: + +```xml + + + + + +``` + # Mailing Lists To discuss this project, and participate in the design, we use the diff --git a/external/xamarin-android-tools/azure-pipelines.yaml b/external/xamarin-android-tools/azure-pipelines.yaml new file mode 100644 index 00000000000..33cafcff5e8 --- /dev/null +++ b/external/xamarin-android-tools/azure-pipelines.yaml @@ -0,0 +1,53 @@ +name: Xamarin.Android.Tools $(Rev:r) +jobs: +- job: windows + displayName: windows + pool: Hosted VS2017 + steps: + - task: NuGetCommand@2 + displayName: 'NuGet restore' + inputs: + restoreSolution: Xamarin.Android.Tools.sln + - task: MSBuild@1 + displayName: 'Build solution Xamarin.Android.Tools.sln' + inputs: + solution: Xamarin.Android.Tools.sln + - task: VSTest@2 + displayName: 'VsTest - NUnit3' + inputs: + testAssemblyVer2: 'bin\TestDebug\*-Tests.dll' + pathtoCustomTestAdapters: 'packages\NUnit3TestAdapter.3.12.0\build\net35' + - powershell: | + $hashOfLastVersionChange = & "git" "log" "--follow" "-1" "--pretty=%H" "nuget.version" + $commitsSinceVersionChange = & "git" "rev-list" "--count" "$hashOfLastVersionChange..HEAD" + $majorMinor = Get-Content "nuget.version" + $version = "$majorMinor.$commitsSinceVersionChange" + Write-Host "##vso[task.setvariable variable=xat.nuget.version]$version" + - task: NuGetCommand@2 + displayName: NuGet pack + inputs: + command: custom + arguments: pack src\Xamarin.Android.Tools.AndroidSdk\Xamarin.Android.Tools.AndroidSdk.csproj -Version $(xat.nuget.version) -OutputDirectory $(Build.ArtifactStagingDirectory) + - task: PublishBuildArtifacts@1 + displayName: upload artifacts + inputs: + pathtoPublish: $(Build.ArtifactStagingDirectory) +- job: mac + displayName: mac + pool: Hosted macOS + steps: + - task: NuGetCommand@2 + displayName: 'NuGet restore' + inputs: + restoreSolution: Xamarin.Android.Tools.sln + - task: MSBuild@1 + displayName: 'Build solution Xamarin.Android.Tools.sln' + inputs: + solution: Xamarin.Android.Tools.sln + - script: mono packages/NUnit.ConsoleRunner.3.2.1/tools/nunit3-console.exe bin/TestDebug/Xamarin.Android.Tools.AndroidSdk-Tests.dll + displayName: Run NUnit Tests + - task: PublishTestResults@2 + condition: always() + inputs: + testResultsFormat: NUnit + testResultsFiles: TestResult.xml diff --git a/external/xamarin-android-tools/nuget.version b/external/xamarin-android-tools/nuget.version new file mode 100644 index 00000000000..9f8e9b69a33 --- /dev/null +++ b/external/xamarin-android-tools/nuget.version @@ -0,0 +1 @@ +1.0 \ No newline at end of file diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/AndroidSdkInfoTests.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/AndroidSdkInfoTests.cs index 52637245e5b..b857eadfaca 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/AndroidSdkInfoTests.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/AndroidSdkInfoTests.cs @@ -77,6 +77,7 @@ public void Constructor_SetValuesFromPath () Console.WriteLine ($"[{level}] {message}"); }; var oldPath = Environment.GetEnvironmentVariable ("PATH"); + var oldJavaHome = Environment.GetEnvironmentVariable ("JAVA_HOME"); try { var paths = new List () { Path.Combine (jdk, "bin"), @@ -85,6 +86,9 @@ public void Constructor_SetValuesFromPath () }; paths.AddRange (oldPath.Split (new[]{Path.PathSeparator}, StringSplitOptions.RemoveEmptyEntries)); Environment.SetEnvironmentVariable ("PATH", string.Join (Path.PathSeparator.ToString (), paths)); + if (!string.IsNullOrEmpty (oldJavaHome)) { + Environment.SetEnvironmentVariable ("JAVA_HOME", string.Empty); + } var info = new AndroidSdkInfo (logger); @@ -94,6 +98,9 @@ public void Constructor_SetValuesFromPath () } finally { Environment.SetEnvironmentVariable ("PATH", oldPath); + if (!string.IsNullOrEmpty (oldJavaHome)) { + Environment.SetEnvironmentVariable ("JAVA_HOME", oldJavaHome); + } Directory.Delete (root, recursive: true); } } diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/packages.config b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/packages.config index 482431befdc..904b3a886f6 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/packages.config +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/packages.config @@ -1,8 +1,9 @@  - + + \ No newline at end of file diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.nuspec b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.nuspec new file mode 100644 index 00000000000..85a4f9a4ef5 --- /dev/null +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.nuspec @@ -0,0 +1,16 @@ + + + + $id$ + $version$ + $title$ + $author$ + $author$ + https://github.com/xamarin/xamarin-android-tools/blob/master/LICENSE + https://github.com/xamarin/xamarin-android-tools + false + Xamarin tools for interacting with the Android SDK. + $copyright$ + Xamarin Xamarin.Android + + \ No newline at end of file From 7d2cb2005e786f77a1e42f923fd7b8dd875f6ea0 Mon Sep 17 00:00:00 2001 From: joj Date: Fri, 25 Jan 2019 09:35:36 -0300 Subject: [PATCH 057/308] Made KnownVersions public KnownVersions should be the only place we modify when new versions are published. AndroidVersions right now provides information on what's installed and what's supported, but not what exists. For Android Manifest we also need to know what exists because Minimum Android Version and Target may be anything (regardless of what the compile version is). This is related to: https://devdiv.visualstudio.com/DefaultCollection/DevDiv/_workitems/edit/777180 --- .../src/Xamarin.Android.Tools.AndroidSdk/AndroidVersions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidVersions.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidVersions.cs index 75cd2cdb93f..5911ff09d0a 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidVersions.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidVersions.cs @@ -134,7 +134,7 @@ public string GetFrameworkVersionFromId (string id) KnownVersions.FirstOrDefault (v => MatchesId (v, id))?.FrameworkVersion; } - static readonly AndroidVersion [] KnownVersions = new [] { + public static readonly AndroidVersion [] KnownVersions = new [] { new AndroidVersion (4, "1.6", "Donut"), new AndroidVersion (5, "2.0", "Eclair"), new AndroidVersion (6, "2.0.1", "Eclair"), From 5fdcc36f9cde4f56529af4fa8a61a8827722f5a9 Mon Sep 17 00:00:00 2001 From: joj Date: Fri, 25 Jan 2019 09:35:36 -0300 Subject: [PATCH 058/308] Made KnownVersions public KnownVersions should be the only place we modify when new versions are published. AndroidVersions right now provides information on what's installed and what's supported, but not what exists. For Android Manifest we also need to know what exists because Minimum Android Version and Target may be anything (regardless of what the compile version is). This is related to: https://devdiv.visualstudio.com/DefaultCollection/DevDiv/_workitems/edit/777180 --- .../src/Xamarin.Android.Tools.AndroidSdk/AndroidVersions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidVersions.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidVersions.cs index 75cd2cdb93f..5911ff09d0a 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidVersions.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidVersions.cs @@ -134,7 +134,7 @@ public string GetFrameworkVersionFromId (string id) KnownVersions.FirstOrDefault (v => MatchesId (v, id))?.FrameworkVersion; } - static readonly AndroidVersion [] KnownVersions = new [] { + public static readonly AndroidVersion [] KnownVersions = new [] { new AndroidVersion (4, "1.6", "Donut"), new AndroidVersion (5, "2.0", "Eclair"), new AndroidVersion (6, "2.0.1", "Eclair"), From 4a2d1088f7cde272ddb230df9a45fe5f55315091 Mon Sep 17 00:00:00 2001 From: Joaquin Jares Date: Mon, 28 Jan 2019 12:57:35 -0300 Subject: [PATCH 059/308] Added ndk-bundle directories to ndk search in Windows (#55) A few versions ago, the ndk started shipping inside the sdk path. In VS we are now checking this directories, and only using the registry keys as fallbacks (meaning, if the ndk exists in the sdk it takes priority over anything else). According to our telemetry, 100% of the users are using the ndk this way. This commit adds that check so we can have parity between what Android does and what VS does. Also, it serves as preparation for VS removing that option (meaning, it will not be configurable and _always_ work in the way described in this commit). --- .../Sdks/AndroidSdkWindows.cs | 11 +++++++ .../Tests/AndroidSdkInfoTests.cs | 30 +++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkWindows.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkWindows.cs index f05a5b6ec8e..04a3371f849 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkWindows.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkWindows.cs @@ -194,6 +194,17 @@ protected override IEnumerable GetAllAvailableAndroidNdks () Logger (TraceLevel.Info, "Looking for Android NDK..."); + // Check for the "ndk-bundle" directory inside the SDK directories + string ndk; + + var sdks = GetAllAvailableAndroidSdks().ToList(); + sdks.Add(AndroidSdkPath); + + foreach(var sdk in sdks.Distinct()) + if (Directory.Exists(ndk = Path.Combine(sdk, "ndk-bundle"))) + if (ValidateAndroidNdkLocation(ndk)) + yield return ndk; + // Check for the key the user gave us in the VS/addin options foreach (var root in roots) if (CheckRegistryKeyForExecutable (root, regKey, MDREG_ANDROID_NDK, wow, ".", NdkStack)) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/AndroidSdkInfoTests.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/AndroidSdkInfoTests.cs index b857eadfaca..ff69f544266 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/AndroidSdkInfoTests.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/AndroidSdkInfoTests.cs @@ -64,6 +64,36 @@ public void Constructor_Paths () } } + [Test] + public void Ndk_PathInSdk() + { + if (!OS.IsWindows) + Assert.Ignore("This only works in Windows"); + + CreateSdks(out string root, out string jdk, out string ndk, out string sdk); + + var logs = new StringWriter(); + Action logger = (level, message) => { + logs.WriteLine($"[{level}] {message}"); + }; + + try + { + var ndkPath = Path.Combine(sdk, "ndk-bundle"); + Directory.CreateDirectory(ndkPath); + Directory.CreateDirectory(Path.Combine(ndkPath, "toolchains")); + File.WriteAllText(Path.Combine(ndkPath, "ndk-stack.cmd"), ""); + + var info = new AndroidSdkInfo(logger, androidSdkPath: sdk, androidNdkPath: null, javaSdkPath: jdk); + + Assert.AreEqual(ndkPath, info.AndroidNdkPath, "AndroidNdkPath not found inside sdk!"); + } + finally + { + Directory.Delete(root, recursive: true); + } + } + [Test] public void Constructor_SetValuesFromPath () { From 118872b45f569e4952d989f4718d9b78de1cac6d Mon Sep 17 00:00:00 2001 From: Kyle White Date: Thu, 7 Feb 2019 18:02:48 -0500 Subject: [PATCH 060/308] Update azure pipeline badge links These links have changed as we continue to find out way in AzDev. --- external/xamarin-android-tools/README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/external/xamarin-android-tools/README.md b/external/xamarin-android-tools/README.md index 3fa13ad2c15..419b3223ac8 100644 --- a/external/xamarin-android-tools/README.md +++ b/external/xamarin-android-tools/README.md @@ -15,10 +15,10 @@ submodule the entire **xamarin-android** repo, which is gigantic. | **Windows** | [![Windows Build Status][azure-Windows-icon]][azure-Windows-status] | -[azure-macOS-icon]: https://dev.azure.com/xamarin/Xamarin/_apis/build/status/Xamarin.Android.Tools -[azure-macOS-status]: https://dev.azure.com/xamarin/Xamarin/_build/latest?definitionId=2 -[azure-Windows-icon]: https://dev.azure.com/xamarin/Xamarin/_apis/build/status/Xamarin.Android.Tools -[azure-Windows-status]: https://dev.azure.com/xamarin/Xamarin/_build/latest?definitionId=2 +[azure-macOS-icon]: https://dev.azure.com/xamarin/public/_apis/build/status/xamarin-android-tools +[azure-macOS-status]: https://dev.azure.com/xamarin/public/_build/latest?definitionId=3 +[azure-Windows-icon]: https://dev.azure.com/xamarin/public/_apis/build/status/xamarin-android-tools +[azure-Windows-status]: https://dev.azure.com/xamarin/public/_build/latest?definitionId=3 # Build Requirements From 4b3b7584f347f2306ffc5c681a0d39649d6e058c Mon Sep 17 00:00:00 2001 From: Dean Ellis Date: Fri, 22 Mar 2019 18:40:21 +0000 Subject: [PATCH 061/308] [build] Upgrade to NUnit 3.11.0 and NUnit.ConsoleRunner 3.9.0 (#66) [Visual Studio 2019][0] requires an updated NUnit to load and run our NUnit tests. Update to NUnit 3.11.0, NUnit.ConsoleRunner 3.9.0, and NUnit3TestAdapter 3.13.0 to appease Visual Studio. Additionally, when trying to restore packages in the `monodroid` repo within Visual Studio for Mac, we get: Error NU1105 Unable to find project information for 'Xamarin.Android.Tools.AndroidSdk.csproj'. The project file may be invalid or missing targets required for restore. This is because this `$(TargetFrameworkVersion)` was set to `v4.5` and the ones in monodroid are set to `v4.7.2` now. We need to update the `$(TargetFrameworkVersions)` values to v4.7.1 as well. [0]: https://web.archive.org/web/20190307085042/https://visualstudio.microsoft.com/vs/preview --- external/xamarin-android-tools/Makefile | 2 +- .../xamarin-android-tools/azure-pipelines.yaml | 4 ++-- .../Xamarin.Android.Tools.AndroidSdk-Tests.csproj | 6 ++++-- .../Tests/packages.config | 14 +++++++++----- .../Xamarin.Android.Tools.AndroidSdk.csproj | 2 +- 5 files changed, 17 insertions(+), 11 deletions(-) diff --git a/external/xamarin-android-tools/Makefile b/external/xamarin-android-tools/Makefile index e2af78b9a67..ef6d107d30e 100644 --- a/external/xamarin-android-tools/Makefile +++ b/external/xamarin-android-tools/Makefile @@ -1,5 +1,5 @@ CONFIGURATION := Debug -NUNIT_CONSOLE := packages/NUnit.ConsoleRunner.3.2.1/tools/nunit3-console.exe +NUNIT_CONSOLE := packages/NUnit.ConsoleRunner.3.9.0/tools/nunit3-console.exe OS := $(shell uname) RUNTIME := mono --debug=casts V ?= 0 diff --git a/external/xamarin-android-tools/azure-pipelines.yaml b/external/xamarin-android-tools/azure-pipelines.yaml index 33cafcff5e8..01db5bb4585 100644 --- a/external/xamarin-android-tools/azure-pipelines.yaml +++ b/external/xamarin-android-tools/azure-pipelines.yaml @@ -16,7 +16,7 @@ jobs: displayName: 'VsTest - NUnit3' inputs: testAssemblyVer2: 'bin\TestDebug\*-Tests.dll' - pathtoCustomTestAdapters: 'packages\NUnit3TestAdapter.3.12.0\build\net35' + pathtoCustomTestAdapters: 'packages\NUnit3TestAdapter.3.13.0\build\net35' - powershell: | $hashOfLastVersionChange = & "git" "log" "--follow" "-1" "--pretty=%H" "nuget.version" $commitsSinceVersionChange = & "git" "rev-list" "--count" "$hashOfLastVersionChange..HEAD" @@ -44,7 +44,7 @@ jobs: displayName: 'Build solution Xamarin.Android.Tools.sln' inputs: solution: Xamarin.Android.Tools.sln - - script: mono packages/NUnit.ConsoleRunner.3.2.1/tools/nunit3-console.exe bin/TestDebug/Xamarin.Android.Tools.AndroidSdk-Tests.dll + - script: mono packages/NUnit.ConsoleRunner.3.9.0/tools/nunit3-console.exe bin/TestDebug/Xamarin.Android.Tools.AndroidSdk-Tests.dll displayName: Run NUnit Tests - task: PublishTestResults@2 condition: always() diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/Xamarin.Android.Tools.AndroidSdk-Tests.csproj b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/Xamarin.Android.Tools.AndroidSdk-Tests.csproj index 7cb376d4af6..5bef2b28971 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/Xamarin.Android.Tools.AndroidSdk-Tests.csproj +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/Xamarin.Android.Tools.AndroidSdk-Tests.csproj @@ -1,5 +1,7 @@  + + Debug @@ -9,7 +11,7 @@ Properties Xamarin.Android.Tools.Tests Xamarin.Android.Tools.AndroidSdk-Tests - v4.5.2 + v4.7.1 512 @@ -31,7 +33,7 @@ - ..\..\..\packages\NUnit.3.2.1\lib\net45\nunit.framework.dll + ..\..\..\packages\NUnit.3.11.0\lib\net45\nunit.framework.dll diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/packages.config b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/packages.config index 904b3a886f6..089d3ef1926 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/packages.config +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/packages.config @@ -1,9 +1,13 @@  - - - - - + + + + + + + + + \ No newline at end of file diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj index 68da4fc24bf..a71b39bc623 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj @@ -7,7 +7,7 @@ Library Xamarin.Android.Tools Xamarin.Android.Tools.AndroidSdk - v4.5 + v4.7.1 8.0.30703 2.0 true From be6333ed558dadf40a8172cbcf500541b58faa07 Mon Sep 17 00:00:00 2001 From: Cody Russell Date: Thu, 28 Mar 2019 05:03:48 -0500 Subject: [PATCH 062/308] Check if the path exists before trying to enumerate it. (#67) Fixes VSTS #826178 --- .../Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkBase.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkBase.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkBase.cs index 1ea9121ae0b..12892b9db0a 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkBase.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkBase.cs @@ -90,8 +90,10 @@ public virtual void Initialize (string androidSdkPath = null, string androidNdkP if (!string.IsNullOrEmpty (AndroidNdkPath)) { // It would be nice if .NET had real globbing support in System.IO... string toolchainsDir = Path.Combine (AndroidNdkPath, "toolchains"); - IsNdk64Bit = Directory.EnumerateDirectories (toolchainsDir, "arm-linux-androideabi-*") - .Any (dir => Directory.Exists (Path.Combine (dir, "prebuilt", NdkHostPlatform64Bit))); + if (Directory.Exists (toolchainsDir)) { + IsNdk64Bit = Directory.EnumerateDirectories (toolchainsDir, "arm-linux-androideabi-*") + .Any (dir => Directory.Exists (Path.Combine (dir, "prebuilt", NdkHostPlatform64Bit))); + } } // we need to look for extensions other than the default .exe|.bat // google have a habbit of changing them. From 7a51bd32fc38e4978ec24854619e2fa8e4b3ab15 Mon Sep 17 00:00:00 2001 From: Dean Ellis Date: Tue, 7 Aug 2018 15:26:07 +0100 Subject: [PATCH 063/308] [Xamarin.Android.Tools.AndroidSdk] Add 9.0 to KnownVersions. --- .../src/Xamarin.Android.Tools.AndroidSdk/AndroidVersions.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidVersions.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidVersions.cs index 5911ff09d0a..21066307f1d 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidVersions.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidVersions.cs @@ -168,6 +168,7 @@ public string GetFrameworkVersionFromId (string id) AlternateIds = new[]{ "O" }, }, new AndroidVersion (27, "8.1", "Oreo"), + new AndroidVersion (28, "9.0", "Pie"), }; } From aa8ccaef70b85aca5cbb25cf547ddd94d64e1ed6 Mon Sep 17 00:00:00 2001 From: Jonathan Pryor Date: Thu, 6 Sep 2018 12:54:26 -0400 Subject: [PATCH 064/308] Add AlternateIds for 9.0 --- .../src/Xamarin.Android.Tools.AndroidSdk/AndroidVersions.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidVersions.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidVersions.cs index 21066307f1d..a56dcf343d1 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidVersions.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidVersions.cs @@ -168,7 +168,9 @@ public string GetFrameworkVersionFromId (string id) AlternateIds = new[]{ "O" }, }, new AndroidVersion (27, "8.1", "Oreo"), - new AndroidVersion (28, "9.0", "Pie"), + new AndroidVersion (28, "9.0", "Pie") { + AlternateIds = new[]{ "P" }, + }, }; } From d518d6004aba7c4a838847b2b67ae0000c99ef08 Mon Sep 17 00:00:00 2001 From: Daniel Cazzulino Date: Tue, 4 Jun 2019 14:12:32 -0300 Subject: [PATCH 065/308] Downgrade to net46 for compatibility with VS2017+(#69) Under VS2017+, code must either target net46 or net472 and nothing in between. Otherwise, run-time errors might happen if the wrong assemblies (facades for NS2 support) are inadvertently bound at compile time and loaded at run-time. --- .../Tests/Xamarin.Android.Tools.AndroidSdk-Tests.csproj | 4 ++-- .../Xamarin.Android.Tools.AndroidSdk.csproj | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/Xamarin.Android.Tools.AndroidSdk-Tests.csproj b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/Xamarin.Android.Tools.AndroidSdk-Tests.csproj index 5bef2b28971..58d66d28e30 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/Xamarin.Android.Tools.AndroidSdk-Tests.csproj +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/Xamarin.Android.Tools.AndroidSdk-Tests.csproj @@ -11,7 +11,7 @@ Properties Xamarin.Android.Tools.Tests Xamarin.Android.Tools.AndroidSdk-Tests - v4.7.1 + v4.6 512 @@ -68,4 +68,4 @@ - \ No newline at end of file + diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj index a71b39bc623..cec95b31a1f 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj @@ -7,7 +7,7 @@ Library Xamarin.Android.Tools Xamarin.Android.Tools.AndroidSdk - v4.7.1 + v4.6 8.0.30703 2.0 true From 8726ef81672bdcf4b23a957e7e1212e01fa42470 Mon Sep 17 00:00:00 2001 From: Kyle White Date: Mon, 10 Jun 2019 13:39:14 -0400 Subject: [PATCH 066/308] Get OpenJDK from well-known paths, not just registry (#72) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Context: http://work.azdo.io/735565 Turns Out™ that certain anti-virus programs block or otherwise mitigate access to various Windows Registry keys. (No idea which anti-virus, or what the symptoms are; this is just What I've Heard.) Reduce reliance on the registry by looking for the JDK within the `%ProgramW6432%\Android\jdk\microsoft_dist_openjdk_*` directories, in which the `*` suffix is a `System.Version`-parsable value, and prefer the highest JDK found there. The Registry value is still preferred, if set. --- external/xamarin-android-tools/nuget.version | 2 +- .../Sdks/AndroidSdkWindows.cs | 31 +++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/external/xamarin-android-tools/nuget.version b/external/xamarin-android-tools/nuget.version index 9f8e9b69a33..b123147e2a1 100644 --- a/external/xamarin-android-tools/nuget.version +++ b/external/xamarin-android-tools/nuget.version @@ -1 +1 @@ -1.0 \ No newline at end of file +1.1 \ No newline at end of file diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkWindows.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkWindows.cs index 04a3371f849..549267e40d3 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkWindows.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkWindows.cs @@ -134,6 +134,7 @@ IEnumerable ToJdkInfos (IEnumerable paths, string locator) return ToJdkInfos (GetPreferredJdkPaths (), "Preferred Registry") .Concat (ToJdkInfos (GetOpenJdkPaths (), "OpenJDK")) + .Concat (ToJdkInfos (GetKnownOpenJdkPaths (), "Well-known OpenJDK paths")) .Concat (ToJdkInfos (GetOracleJdkPaths (), "Oracle JDK")); } @@ -163,6 +164,36 @@ private static IEnumerable GetOpenJdkPaths () } } + /// + /// Locate OpenJDK installations by well known path. + /// + /// List of valid OpenJDK paths in version descending order. + private static IEnumerable GetKnownOpenJdkPaths () + { + string JdkFolderNamePattern = "microsoft_dist_openjdk_"; + + var paths = new List> (); + var rootPaths = new List { + Path.Combine (Environment.ExpandEnvironmentVariables ("%ProgramW6432%"), "Android", "jdk"), + Path.Combine (Environment.GetFolderPath (Environment.SpecialFolder.ProgramFilesX86), "Android", "jdk"), + }; + + foreach (var rootPath in rootPaths) { + if (Directory.Exists (rootPath)) { + foreach (var directoryName in Directory.EnumerateDirectories (rootPath, $"{JdkFolderNamePattern}*").ToList ()) { + var versionString = directoryName.Replace ($"{rootPath}\\{JdkFolderNamePattern}", string.Empty); + if (Version.TryParse (versionString, out Version ver)) { + paths.Add (new Tuple(directoryName, ver)); + } + } + } + } + + return paths.OrderByDescending (v => v.Item2) + .Where (openJdk => ProcessUtils.FindExecutablesInDirectory (Path.Combine (openJdk.Item1, "bin"), _JarSigner).Any ()) + .Select (openJdk => openJdk.Item1); + } + private static IEnumerable GetOracleJdkPaths () { string subkey = @"SOFTWARE\JavaSoft\Java Development Kit"; From 3147b53285b63eed202df85b3bf513289d4ba467 Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Mon, 1 Jul 2019 22:07:41 -0500 Subject: [PATCH 067/308] [Windows] check %JAVA_HOME% for locating Jdk (#74) * [Windows] check %JAVA_HOME% for locating Jdk Fixes: http://feedback.devdiv.io/606218 Something has happened with Xamarin.Android builds running on Azure DevOps recently, builds are now failing with: ResolveSdks ... Errors C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\MSBuild\Xamarin\Android\Xamarin.Android.Common.targets(721,2): error XA5300: The Java SDK Directory could not be found. Please set via /p:JavaSdkDirectory. [d:\a\1\s\Xamarin.Android.Lite\Xamarin.Android.Lite.csproj] Azure DevOps has posted a "workaround" document: https://github.com/microsoft/azure-pipelines-image-generation/blob/1371aa8548aa135c3be783bd854681118ca10f78/images/win/Vs2019-Server2019-Readme.md#known-issues It suggests adding this property to MSBuild calls: /p:JavaSdkDirectory="$(JAVA_HOME_8_X64)" Reviewing a user's build log from ReactiveUI: https://dev.azure.com/dotnet/ReactiveUI/_build/results?buildId=18505 /p:JavaSdkDirectory=C:/Program%20Files/Java/zulu-8-azure-jdk_8.38.0.13-8.0.212-win_x64 So I tried this with my own repo that has Azure DevOps configured, sure enough a build from June 18, 2019 worked. A rebuild on July 1, 2019 failed with the reported error. * Worked: https://jopepper.visualstudio.com/Jon%20Peppers%20OSS/_build/results?buildId=208 * Failed: https://jopepper.visualstudio.com/Jon%20Peppers%20OSS/_build/results?buildId=209 I can read from the build logs that both `JAVA_HOME` and `JAVA_HOME_8_X64` are set to: C:/Program Files/Java/zulu-8-azure-jdk_8.38.0.13-8.0.212-win_x64 I think this means two things: * Microsoft Open JDK is no longer installed on the `Hosted Windows 2019 with VS2019` pool. * Instead, *yet another JDK* called the Azul Zulu JDK is present... https://docs.microsoft.com/en-us/java/azure/jdk/java-jdk-install?view=azure-java-stable I think we should add a "last resort" lookup for a valid JDK within `JAVA_HOME` on Windows. It does not currently do that. Reviewing the code, I am somewhat confused by `JdkInfo`. Its logic is not run on Windows, and instead we have Windows-specific JDK lookups in `AndroidSdkWindows`. To make things work properly we can just add a last chance lookup for `JAVA_HOME` in `AndroidSdkWindows`. I added a new test for verifying things locally, however... I can't actually run this test on CI, since our current CI machines have the Microsoft Open JDK installed. --- .../Sdks/AndroidSdkWindows.cs | 14 ++++++++- .../Tests/AndroidSdkInfoTests.cs | 29 +++++++++++++++++++ 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkWindows.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkWindows.cs index 549267e40d3..9ee716fd596 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkWindows.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkWindows.cs @@ -135,7 +135,19 @@ IEnumerable ToJdkInfos (IEnumerable paths, string locator) return ToJdkInfos (GetPreferredJdkPaths (), "Preferred Registry") .Concat (ToJdkInfos (GetOpenJdkPaths (), "OpenJDK")) .Concat (ToJdkInfos (GetKnownOpenJdkPaths (), "Well-known OpenJDK paths")) - .Concat (ToJdkInfos (GetOracleJdkPaths (), "Oracle JDK")); + .Concat (ToJdkInfos (GetOracleJdkPaths (), "Oracle JDK")) + .Concat (ToJdkInfos (GetEnvironmentJdkPaths (), "Environment Variables")); + } + + private static IEnumerable GetEnvironmentJdkPaths () + { + var environment = new [] { "JAVA_HOME" }; + foreach (var key in environment) { + var value = Environment.GetEnvironmentVariable (key); + if (!string.IsNullOrEmpty (value)) { + yield return value; + } + } } private static IEnumerable GetPreferredJdkPaths () diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/AndroidSdkInfoTests.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/AndroidSdkInfoTests.cs index ff69f544266..50c6d9a511d 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/AndroidSdkInfoTests.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/AndroidSdkInfoTests.cs @@ -135,6 +135,35 @@ public void Constructor_SetValuesFromPath () } } + [Test] + [Ignore ("This test will only work locally if you rename/remove your Open JDK directory.")] + public void JdkDirectory_JavaHome () + { + CreateSdks (out string root, out string jdk, out string ndk, out string sdk); + JdkInfoTests.CreateFauxJdk (jdk, releaseVersion: "1.8.999", releaseBuildNumber: "9", javaVersion: "1.8.999-9"); + + var logs = new StringWriter (); + Action logger = (level, message) => { + logs.WriteLine ($"[{level}] {message}"); + }; + + string java_home = null; + try { + // We only set via JAVA_HOME + java_home = Environment.GetEnvironmentVariable ("JAVA_HOME", EnvironmentVariableTarget.Process); + Environment.SetEnvironmentVariable ("JAVA_HOME", jdk); + var info = new AndroidSdkInfo (logger, androidSdkPath: sdk, androidNdkPath: ndk, javaSdkPath: ""); + + Assert.AreEqual (ndk, info.AndroidNdkPath, "AndroidNdkPath not preserved!"); + Assert.AreEqual (sdk, info.AndroidSdkPath, "AndroidSdkPath not preserved!"); + Assert.AreEqual (jdk, info.JavaSdkPath, "JavaSdkPath not preserved!"); + } finally { + if (java_home != null) + Environment.SetEnvironmentVariable ("JAVA_HOME", java_home, EnvironmentVariableTarget.Process); + Directory.Delete (root, recursive: true); + } + } + static bool IsWindows => OS.IsWindows; static void CreateSdks (out string root, out string jdk, out string ndk, out string sdk) From 5b926ff34dd6a6d5d236517cedced6884123b8dd Mon Sep 17 00:00:00 2001 From: Juan Marcelo Tondato Date: Wed, 21 Aug 2019 18:23:10 -0300 Subject: [PATCH 068/308] Prevent NRE when Android SDK is not found (#73) Fixes: https://devdiv.visualstudio.com/DevDiv/_workitems/edit/938504 In 4a2d1088f7, we added a search path for the NDK which is now *inside* the Android SDK. However, this change could produce a `NullReferenceException` if the Android SDK is not found. In cases where an Android SDK is not found, and the `AndroidSdkPath` property is null, we still go on to use the null property when locating the Android NDK: var sdks = GetAllAvailableAndroidSdks().ToList(); sdks.Add(AndroidSdkPath); foreach(var sdk in sdks.Distinct()) if (Directory.Exists(ndk = Path.Combine(sdk, "ndk-bundle"))) The `Path.Combine` will throw if you pass in a null. This fix doesn't *solve* the exception, as you will still get an `InvalidOperationException` for the missing Android SDK. However, that is certainly a more descriptive error message than a `NullReferenceException`. --- .../Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkWindows.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkWindows.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkWindows.cs index 9ee716fd596..f5b2021b862 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkWindows.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkWindows.cs @@ -241,7 +241,8 @@ protected override IEnumerable GetAllAvailableAndroidNdks () string ndk; var sdks = GetAllAvailableAndroidSdks().ToList(); - sdks.Add(AndroidSdkPath); + if (!string.IsNullOrEmpty(AndroidSdkPath)) + sdks.Add(AndroidSdkPath); foreach(var sdk in sdks.Distinct()) if (Directory.Exists(ndk = Path.Combine(sdk, "ndk-bundle"))) From 0a064f8b0f98dcac6e5d6e4eedc94fd85bba6308 Mon Sep 17 00:00:00 2001 From: Somesh Chatterjee Date: Thu, 22 Aug 2019 11:23:25 -0700 Subject: [PATCH 069/308] Fix authenticode certs --- .../Xamarin.Android.Tools.AndroidSdk.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj index cec95b31a1f..73f45550816 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj @@ -40,7 +40,7 @@ - Microsoft + Microsoft400 From e9cff74e31bca22c05c3edd7621e0bafaedb7cee Mon Sep 17 00:00:00 2001 From: Sergey Shakhnazarov Date: Tue, 27 Aug 2019 17:26:20 +0300 Subject: [PATCH 070/308] Use %ProgramW6432% path for 32-bit apps (#75) Context: https://docs.microsoft.com/en-us/windows/win32/winprog64/wow64-implementation-details#environment-variables Use the `%ProgramW6432%` environment variable (if available) so that both **Prefer 32-bit**=false and **Prefer 32-bit**=true apps can get the path. Otherwise `Environment.SpecialFolder.ProgramFiles` returns `"C:\Program Files (x86)"` for 32-bit apps on 64-bit OS, which is not where the Android SDK is installed, and thus path lookup fails. --- .../Sdks/AndroidSdkWindows.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkWindows.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkWindows.cs index f5b2021b862..ce68ee13ffd 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkWindows.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkWindows.cs @@ -93,7 +93,9 @@ protected override IEnumerable GetAllAvailableAndroidSdks () Path.Combine (Environment.GetFolderPath (Environment.SpecialFolder.LocalApplicationData), "Xamarin", "MonoAndroid", "android-sdk-windows"), Path.Combine (Environment.GetFolderPath (Environment.SpecialFolder.ProgramFilesX86), "Android", "android-sdk"), Path.Combine (Environment.GetFolderPath (Environment.SpecialFolder.ProgramFilesX86), "Android", "android-sdk-windows"), - Path.Combine (Environment.GetFolderPath (Environment.SpecialFolder.ProgramFiles), "Android", "android-sdk"), + !string.IsNullOrEmpty (Environment.GetEnvironmentVariable ("ProgramW6432")) + ? Path.Combine (Environment.GetEnvironmentVariable ("ProgramW6432"), "Android", "android-sdk") + : Path.Combine (Environment.GetFolderPath (Environment.SpecialFolder.ProgramFiles), "Android", "android-sdk"), Path.Combine (Environment.GetFolderPath (Environment.SpecialFolder.LocalApplicationData), "Android", "android-sdk"), Path.Combine (Environment.GetFolderPath (Environment.SpecialFolder.CommonApplicationData), "Android", "android-sdk"), @"C:\android-sdk-windows" From 818d1d6e04c67a491be07fd92bc6c8d885aa7740 Mon Sep 17 00:00:00 2001 From: Jonathan Pobst Date: Mon, 27 Jan 2020 21:23:49 -0600 Subject: [PATCH 071/308] Update to sdk-style csproj and netstandard2.0. (#78) Update `Xamarin.Android.Tools.AndroidSdk` to use sdk-style projects and target frameworks `netstandard2.0` and `net461`. Update `Xamarin.Android.Tools.AndroidSdk-Tests` to use sdk-style projects. Move `Xamarin.Android.Tools.AndroidSdk-Tests` into a `tests` directory to avoid sdk-style globbing. Use MSBuild properties for NuGet packages instead of .nuspec. Update `MicroBuild.Core` to `Microsoft.VisualStudioEng.MicroBuild.Core`. * Not sure what this does but [the docs][0] said the old one was deprecated and pointed to the new one which supports `netstandard2.0`. [0]: https://www.nuget.org/packages/MicroBuild.Core --- .../Xamarin.Android.Tools.sln | 2 +- .../azure-pipelines.yaml | 31 +++++--- .../Properties/AssemblyInfo.cs | 15 ---- .../Tests/Properties/AssemblyInfo.cs | 13 ---- ...arin.Android.Tools.AndroidSdk-Tests.csproj | 71 ------------------- .../Tests/packages.config | 13 ---- .../Xamarin.Android.Tools.AndroidSdk.csproj | 71 +++++-------------- .../Xamarin.Android.Tools.AndroidSdk.nuspec | 16 ----- .../AndroidAppManifestTests.cs | 0 .../AndroidSdkInfoTests.cs | 0 .../AndroidVersionTests.cs | 0 .../AndroidVersionsTests.cs | 0 .../JdkInfoTests.cs | 0 .../Resources/manifest-simplewidget.xml | 0 ...arin.Android.Tools.AndroidSdk-Tests.csproj | 34 +++++++++ 15 files changed, 74 insertions(+), 192 deletions(-) delete mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/AssemblyInfo.cs delete mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/Properties/AssemblyInfo.cs delete mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/Xamarin.Android.Tools.AndroidSdk-Tests.csproj delete mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/packages.config delete mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.nuspec rename external/xamarin-android-tools/{src/Xamarin.Android.Tools.AndroidSdk/Tests => tests/Xamarin.Android.Tools.AndroidSdk-Tests}/AndroidAppManifestTests.cs (100%) rename external/xamarin-android-tools/{src/Xamarin.Android.Tools.AndroidSdk/Tests => tests/Xamarin.Android.Tools.AndroidSdk-Tests}/AndroidSdkInfoTests.cs (100%) rename external/xamarin-android-tools/{src/Xamarin.Android.Tools.AndroidSdk/Tests => tests/Xamarin.Android.Tools.AndroidSdk-Tests}/AndroidVersionTests.cs (100%) rename external/xamarin-android-tools/{src/Xamarin.Android.Tools.AndroidSdk/Tests => tests/Xamarin.Android.Tools.AndroidSdk-Tests}/AndroidVersionsTests.cs (100%) rename external/xamarin-android-tools/{src/Xamarin.Android.Tools.AndroidSdk/Tests => tests/Xamarin.Android.Tools.AndroidSdk-Tests}/JdkInfoTests.cs (100%) rename external/xamarin-android-tools/{src/Xamarin.Android.Tools.AndroidSdk/Tests => tests/Xamarin.Android.Tools.AndroidSdk-Tests}/Resources/manifest-simplewidget.xml (100%) create mode 100644 external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/Xamarin.Android.Tools.AndroidSdk-Tests.csproj diff --git a/external/xamarin-android-tools/Xamarin.Android.Tools.sln b/external/xamarin-android-tools/Xamarin.Android.Tools.sln index 7fbff60706e..c7e13b76de5 100644 --- a/external/xamarin-android-tools/Xamarin.Android.Tools.sln +++ b/external/xamarin-android-tools/Xamarin.Android.Tools.sln @@ -2,7 +2,7 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 2012 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Xamarin.Android.Tools.AndroidSdk", "src\Xamarin.Android.Tools.AndroidSdk\Xamarin.Android.Tools.AndroidSdk.csproj", "{E34BCFA0-CAA4-412C-AA1C-75DB8D67D157}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Xamarin.Android.Tools.AndroidSdk-Tests", "src\Xamarin.Android.Tools.AndroidSdk\Tests\Xamarin.Android.Tools.AndroidSdk-Tests.csproj", "{1E5501E8-49C1-4659-838D-CC9720C5208F}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Xamarin.Android.Tools.AndroidSdk-Tests", "tests\Xamarin.Android.Tools.AndroidSdk-Tests\Xamarin.Android.Tools.AndroidSdk-Tests.csproj", "{1E5501E8-49C1-4659-838D-CC9720C5208F}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/external/xamarin-android-tools/azure-pipelines.yaml b/external/xamarin-android-tools/azure-pipelines.yaml index 01db5bb4585..1db8f6c455c 100644 --- a/external/xamarin-android-tools/azure-pipelines.yaml +++ b/external/xamarin-android-tools/azure-pipelines.yaml @@ -1,11 +1,16 @@ name: Xamarin.Android.Tools $(Rev:r) + jobs: - job: windows displayName: windows pool: Hosted VS2017 steps: + - task: NuGetToolInstaller@1 + displayName: 'Install NuGet' + inputs: + versionSpec: '5.4.x' - task: NuGetCommand@2 - displayName: 'NuGet restore' + displayName: 'NuGet Restore' inputs: restoreSolution: Xamarin.Android.Tools.sln - task: MSBuild@1 @@ -13,39 +18,43 @@ jobs: inputs: solution: Xamarin.Android.Tools.sln - task: VSTest@2 - displayName: 'VsTest - NUnit3' + displayName: 'Run Tests' inputs: testAssemblyVer2: 'bin\TestDebug\*-Tests.dll' - pathtoCustomTestAdapters: 'packages\NUnit3TestAdapter.3.13.0\build\net35' - powershell: | $hashOfLastVersionChange = & "git" "log" "--follow" "-1" "--pretty=%H" "nuget.version" $commitsSinceVersionChange = & "git" "rev-list" "--count" "$hashOfLastVersionChange..HEAD" $majorMinor = Get-Content "nuget.version" $version = "$majorMinor.$commitsSinceVersionChange" Write-Host "##vso[task.setvariable variable=xat.nuget.version]$version" - - task: NuGetCommand@2 - displayName: NuGet pack + - task: MSBuild@1 + displayName: 'Build NuGet' inputs: - command: custom - arguments: pack src\Xamarin.Android.Tools.AndroidSdk\Xamarin.Android.Tools.AndroidSdk.csproj -Version $(xat.nuget.version) -OutputDirectory $(Build.ArtifactStagingDirectory) + solution: 'src\Xamarin.Android.Tools.AndroidSdk\Xamarin.Android.Tools.AndroidSdk.csproj' + msbuildArguments: '/t:pack /p:Version=$(xat.nuget.version) /p:OutputPath=$(Build.ArtifactStagingDirectory)' - task: PublishBuildArtifacts@1 - displayName: upload artifacts + displayName: Upload Artifacts inputs: pathtoPublish: $(Build.ArtifactStagingDirectory) - job: mac displayName: mac pool: Hosted macOS steps: + - task: NuGetToolInstaller@1 + displayName: 'Install NuGet' + inputs: + versionSpec: '5.4.x' - task: NuGetCommand@2 - displayName: 'NuGet restore' + displayName: 'NuGet Restore' inputs: restoreSolution: Xamarin.Android.Tools.sln + restoreDirectory: 'packages' - task: MSBuild@1 displayName: 'Build solution Xamarin.Android.Tools.sln' inputs: solution: Xamarin.Android.Tools.sln - - script: mono packages/NUnit.ConsoleRunner.3.9.0/tools/nunit3-console.exe bin/TestDebug/Xamarin.Android.Tools.AndroidSdk-Tests.dll - displayName: Run NUnit Tests + - script: mono packages/nunit.consolerunner/3.9.0/tools/nunit3-console.exe bin/TestDebug/Xamarin.Android.Tools.AndroidSdk-Tests.dll + displayName: Run Tests - task: PublishTestResults@2 condition: always() inputs: diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/AssemblyInfo.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/AssemblyInfo.cs deleted file mode 100644 index 45dfcd0de40..00000000000 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; - -[assembly: AssemblyTitle ("Xamarin.Android.Tools")] -[assembly: AssemblyDescription ("")] -[assembly: AssemblyConfiguration ("")] -[assembly: AssemblyCompany ("Xamarin")] -[assembly: AssemblyProduct ("Xamarin.Android.Tools")] -[assembly: AssemblyCopyright ("Copyright © Xamarin 2011-2016")] -[assembly: AssemblyTrademark ("")] -[assembly: AssemblyCulture ("")] - -[assembly: AssemblyVersion ("1.0.0.0")] -[assembly: AssemblyFileVersion ("1.0.0.0")] - diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/Properties/AssemblyInfo.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/Properties/AssemblyInfo.cs deleted file mode 100644 index 3dfdbae2c03..00000000000 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System.Reflection; - -[assembly: AssemblyTitle("Xamarin.Android.Tools.Tests")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("Xamarin.Android.Tools.Tests")] -[assembly: AssemblyCopyright("Copyright © Xamarin 2017")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/Xamarin.Android.Tools.AndroidSdk-Tests.csproj b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/Xamarin.Android.Tools.AndroidSdk-Tests.csproj deleted file mode 100644 index 58d66d28e30..00000000000 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/Xamarin.Android.Tools.AndroidSdk-Tests.csproj +++ /dev/null @@ -1,71 +0,0 @@ - - - - - - - Debug - AnyCPU - {1E5501E8-49C1-4659-838D-CC9720C5208F} - Library - Properties - Xamarin.Android.Tools.Tests - Xamarin.Android.Tools.AndroidSdk-Tests - v4.6 - 512 - - - true - full - false - ..\..\..\bin\TestDebug\ - DEBUG;TRACE - prompt - 4 - - - pdbonly - true - ..\..\..\bin\TestRelease\ - TRACE - prompt - 4 - - - - ..\..\..\packages\NUnit.3.11.0\lib\net45\nunit.framework.dll - - - - - - - - - - - - - - - - - - - - - - {e34bcfa0-caa4-412c-aa1c-75db8d67d157} - Xamarin.Android.Tools.AndroidSdk - - - - - - - - manifest-simplewidget.xml - - - - diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/packages.config b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/packages.config deleted file mode 100644 index 089d3ef1926..00000000000 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/packages.config +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj index 73f45550816..0150bbc4834 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj @@ -1,70 +1,37 @@ - - + + - Debug - AnyCPU - {e34bcfa0-caa4-412c-aa1c-75db8d67d157} - Library - Xamarin.Android.Tools - Xamarin.Android.Tools.AndroidSdk - v4.6 - 8.0.30703 - 2.0 + netstandard2.0;net461 true ..\..\product.snk + Xamarin.Android.Tools + Xamarin + MIT + https://github.com/xamarin/xamarin-android-tools + Xamarin tools for interacting with the Android SDK. + Copyright © Xamarin 2011-2016 + Xamarin;Xamarin.Android - - true - full - false + + ..\..\bin\Debug - DEBUG; - prompt - 4 - false - - full - true + + ..\..\bin\Release - prompt - 4 - false + - - 0.3.0 - runtime; build; native; contentfiles; analyzers + all + runtime; build; native; contentfiles; analyzers + Microsoft400 - - - - - - - - - - - - - - - - - - - - - - - - + diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.nuspec b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.nuspec deleted file mode 100644 index 85a4f9a4ef5..00000000000 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.nuspec +++ /dev/null @@ -1,16 +0,0 @@ - - - - $id$ - $version$ - $title$ - $author$ - $author$ - https://github.com/xamarin/xamarin-android-tools/blob/master/LICENSE - https://github.com/xamarin/xamarin-android-tools - false - Xamarin tools for interacting with the Android SDK. - $copyright$ - Xamarin Xamarin.Android - - \ No newline at end of file diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/AndroidAppManifestTests.cs b/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/AndroidAppManifestTests.cs similarity index 100% rename from external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/AndroidAppManifestTests.cs rename to external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/AndroidAppManifestTests.cs diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/AndroidSdkInfoTests.cs b/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/AndroidSdkInfoTests.cs similarity index 100% rename from external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/AndroidSdkInfoTests.cs rename to external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/AndroidSdkInfoTests.cs diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/AndroidVersionTests.cs b/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/AndroidVersionTests.cs similarity index 100% rename from external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/AndroidVersionTests.cs rename to external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/AndroidVersionTests.cs diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/AndroidVersionsTests.cs b/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/AndroidVersionsTests.cs similarity index 100% rename from external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/AndroidVersionsTests.cs rename to external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/AndroidVersionsTests.cs diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/JdkInfoTests.cs b/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/JdkInfoTests.cs similarity index 100% rename from external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/JdkInfoTests.cs rename to external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/JdkInfoTests.cs diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/Resources/manifest-simplewidget.xml b/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/Resources/manifest-simplewidget.xml similarity index 100% rename from external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Tests/Resources/manifest-simplewidget.xml rename to external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/Resources/manifest-simplewidget.xml diff --git a/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/Xamarin.Android.Tools.AndroidSdk-Tests.csproj b/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/Xamarin.Android.Tools.AndroidSdk-Tests.csproj new file mode 100644 index 00000000000..b2de219707f --- /dev/null +++ b/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/Xamarin.Android.Tools.AndroidSdk-Tests.csproj @@ -0,0 +1,34 @@ + + + + net461 + false + false + + + + ..\..\bin\TestDebug + + + + ..\..\bin\TestRelease + + + + + + + + + + + + manifest-simplewidget.xml + + + + + + + + From e32af1027baa4701794d92f1e08e93e3448099b4 Mon Sep 17 00:00:00 2001 From: Jonathan Pryor Date: Wed, 26 Feb 2020 11:12:52 -0500 Subject: [PATCH 072/308] [Xamarin.Android.Tools.AndroidSdk] Remove net461 @DmitriyKirakosyan ran into a build failure when attempting to bump xamarin/android-sdk-installer to use commit 818d1d6e: external\androidtools\external\xamarin-android-tools\src\Xamarin.Android.Tools.AndroidSdk\Xamarin.Android.Tools.AndroidSdk.csproj(0,0): Error MSB4057: The target "BuiltProjectOutputGroupDependencies" does not exist in the project. [Further investigation][0] showed that the MSB4057 occurs because `Xamarin.Android.Tools.AndroidSdk.csproj` uses the `$(TargetFrameworks)` property (plural), and to avoid the MSB4057 the `$(TargetFramework)` property (singular) should instead be used. As `Xamarin.Android.Tools.AndroidSdk.csproj` also targets .NET Standard 2.0, and [.NET Standard 2.0 supports .NET 4.6][1], we can fix the MSB4057 by dropping the build of `net461`. [0]: https://stackoverflow.com/a/43481827 [1]: https://docs.microsoft.com/en-us/dotnet/standard/net-standard --- .../Xamarin.Android.Tools.AndroidSdk.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj index 0150bbc4834..02aec41ea22 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj @@ -1,7 +1,7 @@ - netstandard2.0;net461 + netstandard2.0 true ..\..\product.snk Xamarin.Android.Tools From 461a270241d793867ec15b187d7cebe03cbe5a41 Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Wed, 15 Apr 2020 18:56:17 +0000 Subject: [PATCH 073/308] JetBrains OpenJDK 11 detection (#82) Context: https://bintray.com/jetbrains/intellij-jdk/openjdk11-osx-x64 OpenJDK 11 from JetBrains does not have the `jre` directory which, so far, has been required by the `JdkInfo` class. Instead, there's just the `lib` directory which should be searched for the JVM libraries instead of `jre`. Modify `JdkInfo` so that it first checks the `jre` directory and, failing to find it, looks for the `lib` directory in the OpenJDK installation root. These changes allow Xamarin.Android.Tools tp detect JetBrains OpenJDK 11 properly. --- .../JdkInfo.cs | 22 +++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/JdkInfo.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/JdkInfo.cs index 9ec28ed29d9..4d7f4531bac 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/JdkInfo.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/JdkInfo.cs @@ -13,6 +13,10 @@ namespace Xamarin.Android.Tools { public class JdkInfo { + static readonly string[] JdkLibraryTopDirs = { + "jre", + "lib", + }; public string HomePath {get;} @@ -52,9 +56,23 @@ public JdkInfo (string homePath) JarPath = ProcessUtils.FindExecutablesInDirectory (binPath, "jar").FirstOrDefault (); JavaPath = ProcessUtils.FindExecutablesInDirectory (binPath, "java").FirstOrDefault (); JavacPath = ProcessUtils.FindExecutablesInDirectory (binPath, "javac").FirstOrDefault (); + + string topDir = null; + foreach (string dir in JdkLibraryTopDirs) { + topDir = Path.Combine (HomePath, dir); + if (!Directory.Exists (topDir)) { + topDir = null; + continue; + } + break; + } + + if (String.IsNullOrEmpty (topDir)) + topDir = Path.Combine (HomePath, JdkLibraryTopDirs [0]); + JdkJvmPath = OS.IsMac - ? FindLibrariesInDirectory (Path.Combine (HomePath, "jre"), "jli").FirstOrDefault () - : FindLibrariesInDirectory (Path.Combine (HomePath, "jre"), "jvm").FirstOrDefault (); + ? FindLibrariesInDirectory (topDir, "jli").FirstOrDefault () + : FindLibrariesInDirectory (topDir, "jvm").FirstOrDefault (); ValidateFile ("jar", JarPath); ValidateFile ("java", JavaPath); From 1bc75cfa71c8e55174341d943fe96f73e9495441 Mon Sep 17 00:00:00 2001 From: Jonathan Pobst Date: Thu, 16 Apr 2020 13:11:46 -0500 Subject: [PATCH 074/308] [AndroidSdkWindows] Guard against exception checking registry (#79) Fixes: https://developercommunity.visualstudio.com/content/problem/883179/ilegal-characters-in-path-after-fresh-install.html Somehow, a registry key contains invalid path characters. This causes an `ArgumentException` from `AndroidSdkWindows.CheckRegistryKeyForExecutable()`: System.ArgumentException: Illegal characters in path. at System.IO.Path.CheckInvalidPathChars(String path, Boolean checkAdditional) at System.IO.Path.Combine(String path1, String path2) at Xamarin.Android.Tools.AndroidSdkWindows.CheckRegistryKeyForExecutable(UIntPtr key, String subkey, String valueName, Wow64 wow64, String subdir, String exe) at Xamarin.Android.Tools.AndroidSdkWindows.d__37.MoveNext() at System.Linq.Enumerable.d__64`1.MoveNext() at System.Linq.Buffer`1..ctor(IEnumerable`1 source) at System.Linq.Enumerable.ToArray[TSource](IEnumerable`1 source) at Xamarin.Android.Tools.AndroidSdkBase.get_AllAndroidSdks() at Xamarin.Android.Tools.AndroidSdkBase.Initialize(String androidSdkPath, String androidNdkPath, String javaSdkPath) at Xamarin.Android.Tools.AndroidSdkWindows.Initialize(String androidSdkPath, String androidNdkPath, String javaSdkPath) at Xamarin.Android.Tools.AndroidSdkInfo..ctor(Action`2 logger, String androidSdkPath, String androidNdkPath, String javaSdkPath) Handle the `ArgumentException` by wrapping the body of `CheckRegistryKeyForExecutable()` within a `try/catch` block and catch all exceptions. If an exception is thrown, there is no executable of interest. --- .../Sdks/AndroidSdkWindows.cs | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkWindows.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkWindows.cs index ce68ee13ffd..792c6587459 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkWindows.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkWindows.cs @@ -303,19 +303,23 @@ public override void SetPreferredAndroidNdkPath (string path) #region Helper Methods private static bool CheckRegistryKeyForExecutable (UIntPtr key, string subkey, string valueName, RegistryEx.Wow64 wow64, string subdir, string exe) { - string key_name = string.Format (@"{0}\{1}\{2}", key == RegistryEx.CurrentUser ? "HKCU" : "HKLM", subkey, valueName); + try { + string key_name = string.Format (@"{0}\{1}\{2}", key == RegistryEx.CurrentUser ? "HKCU" : "HKLM", subkey, valueName); - var path = NullIfEmpty (RegistryEx.GetValueString (key, subkey, valueName, wow64)); + var path = NullIfEmpty (RegistryEx.GetValueString (key, subkey, valueName, wow64)); - if (path == null) { - return false; - } + if (path == null) { + return false; + } - if (!ProcessUtils.FindExecutablesInDirectory (Path.Combine (path, subdir), exe).Any ()) { + if (!ProcessUtils.FindExecutablesInDirectory (Path.Combine (path, subdir), exe).Any ()) { + return false; + } + + return true; + } catch (Exception) { return false; } - - return true; } #endregion From 846e3ec5273b075c8b700c4bf7de0af757cff833 Mon Sep 17 00:00:00 2001 From: Dima Date: Thu, 30 Apr 2020 08:49:00 +0700 Subject: [PATCH 075/308] [Xamarin.Android.Tools.AndroidSdk] Add support for cmdline-tools (#83) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Context: https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1109288 Context: https://dl.google.com/android/repository/repository2-1.xml Google has deprecated the `tools` Android SDK package, replacing it with the `cmdline-tools` package, which introduces a version directory component. Old and busted: $(AndroidSdkDirectory)/tools/bin/sdkmanager New hotness: $(AndroidSdkDirectory)/cmdline-tools/latest/bin/sdkmanager Of particular interest is that `latest` is a *literal value*. There is also a `cmdline-tools;1.0` package which creates a `cmdline-tools/1.0` directory. Add a new `AndroidSdkInfo.GetCommandLineToolsPaths()` method which returns the "command-line tools paths", ordered by version and (non-) obsolescence. For example, given the directory structure: * `$(AndroidSdkDirectory)/tools/bin/sdkmanager` * `$(AndroidSdkDirectory)/cmdline-tools/1.0/bin/sdkmanager` * `$(AndroidSdkDirectory)/cmdline-tools/latest/bin/sdkmanager` Then `AndroidSdkInfo.GetCommandLineToolsPaths()` will return, in this order: * `$(AndroidSdkDirectory)/cmdline-tools/latest` * `$(AndroidSdkDirectory)/cmdline-tools/1.0` * `$(AndroidSdkDirectory)/tools` The `latest` version is always preferred, if present, followed by any actually versioned cmdline-tools directories, followed by the `tools` directory, if it exists. Note that "prefixes" are returned. All utilities are within a nested `bin` directory, so if you want e.g. the latest `sdkmanager` util, you would want to do: var info = new AndroidSdkInfo (path); var latestSdkManager = Path.Combine ( info.GetCommandLineToolsPaths ().First (), "bin", "sdkmanager"); Finally, remove some unnecessary members from `AndroidSdkBase` which were never used -- and thus are "noise" -- and don't make sense in the new `cmdline-tools` world, as the cmdline-tools package doesn't contain them… --- .../AndroidSdkInfo.cs | 50 ++++++++- .../Sdks/AndroidSdkBase.cs | 12 -- .../AndroidSdkInfoTests.cs | 104 ++++++++++++++++-- 3 files changed, 141 insertions(+), 25 deletions(-) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidSdkInfo.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidSdkInfo.cs index 5738fb1ff84..4cf0ee4c7d9 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidSdkInfo.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidSdkInfo.cs @@ -52,11 +52,7 @@ public IEnumerable GetBuildToolsPaths () foreach (var d in preview) yield return d; - var sorted = from p in Directory.EnumerateDirectories (buildTools) - let version = TryParseVersion (Path.GetFileName (p)) - where version != null - orderby version descending - select p; + var sorted = SortedSubdirectoriesByVersion (buildTools); foreach (var d in sorted) yield return d; @@ -66,6 +62,15 @@ orderby version descending yield return ptPath; } + static IEnumerable SortedSubdirectoriesByVersion (string dir) + { + return from p in Directory.EnumerateDirectories (dir) + let version = TryParseVersion (Path.GetFileName (p)) + where version != null + orderby version descending + select p; + } + static Version TryParseVersion (string v) { Version version; @@ -185,5 +190,40 @@ public static void DetectAndSetPreferredJavaSdkPathToLatest (Action GetCommandLineToolsPaths (string preferredCommandLineToolsVersion) + { + if (!string.IsNullOrEmpty (preferredCommandLineToolsVersion)) { + var preferredDir = Path.Combine (AndroidSdkPath, "cmdline-tools", preferredCommandLineToolsVersion); + if (Directory.Exists (preferredDir)) + return new[] { preferredDir }.Concat (GetCommandLineToolsPaths ().Where (p => p != preferredDir)); + } + return GetCommandLineToolsPaths (); + } + + public IEnumerable GetCommandLineToolsPaths () + { + var cmdlineToolsDir = Path.Combine (AndroidSdkPath, "cmdline-tools"); + if (Directory.Exists (cmdlineToolsDir)) { + var latestDir = Path.Combine (cmdlineToolsDir, "latest"); + if (Directory.Exists (latestDir)) + yield return latestDir; + foreach (var d in SortedSubdirectoriesByVersion (cmdlineToolsDir)) { + var version = Path.GetFileName (d); + if (version == "latest") + continue; + yield return d; + } + } + var toolsDir = Path.Combine (AndroidSdkPath, "tools"); + if (Directory.Exists (toolsDir)) { + yield return toolsDir; + } + } } } diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkBase.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkBase.cs index 12892b9db0a..b401fb6d6e0 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkBase.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkBase.cs @@ -37,15 +37,10 @@ public AndroidSdkBase (Action logger) public string AndroidNdkPath { get; private set; } public string JavaSdkPath { get; private set; } public string JavaBinPath { get; private set; } - public string AndroidToolsPath { get; private set; } public string AndroidPlatformToolsPath { get; private set; } - public string AndroidToolsPathShort { get; private set; } public string AndroidPlatformToolsPathShort { get; private set; } public virtual string Adb { get; protected set; } = "adb"; - public virtual string Android { get; protected set; } = "android"; - public virtual string Emulator { get; protected set; } = "emulator"; - public virtual string Monitor { get; protected set; } = "monitor"; public virtual string ZipAlign { get; protected set; } = "zipalign"; public virtual string JarSigner { get; protected set; } = "jarsigner"; public virtual string KeyTool { get; protected set; } = "keytool"; @@ -76,13 +71,9 @@ public virtual void Initialize (string androidSdkPath = null, string androidNdkP } if (!string.IsNullOrEmpty (AndroidSdkPath)) { - AndroidToolsPath = Path.Combine (AndroidSdkPath, "tools"); - AndroidToolsPathShort = GetShortFormPath (AndroidToolsPath); AndroidPlatformToolsPath = Path.Combine (AndroidSdkPath, "platform-tools"); AndroidPlatformToolsPathShort = GetShortFormPath (AndroidPlatformToolsPath); } else { - AndroidToolsPath = null; - AndroidToolsPathShort = null; AndroidPlatformToolsPath = null; AndroidPlatformToolsPathShort = null; } @@ -98,9 +89,6 @@ public virtual void Initialize (string androidSdkPath = null, string androidNdkP // we need to look for extensions other than the default .exe|.bat // google have a habbit of changing them. Adb = GetExecutablePath (AndroidPlatformToolsPath, Adb); - Android = GetExecutablePath (AndroidToolsPath, Android); - Emulator = GetExecutablePath (AndroidToolsPath, Emulator); - Monitor = GetExecutablePath (AndroidToolsPath, Monitor); NdkStack = GetExecutablePath (AndroidNdkPath, NdkStack); } diff --git a/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/AndroidSdkInfoTests.cs b/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/AndroidSdkInfoTests.cs index 50c6d9a511d..5b31f3b0f77 100644 --- a/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/AndroidSdkInfoTests.cs +++ b/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/AndroidSdkInfoTests.cs @@ -164,13 +164,74 @@ public void JdkDirectory_JavaHome () } } + [Test] + public void Sdk_GetCommandLineToolsPaths () + { + CreateSdks(out string root, out string jdk, out string ndk, out string sdk); + + var cmdlineTools = Path.Combine (sdk, "cmdline-tools"); + var latestToolsVersion = "latest"; + var toolsVersion = "2.1"; + var higherToolsVersion = "11.2"; + + void recreateCmdlineToolsDirectory () { + Directory.Delete (cmdlineTools, recursive: true); + Directory.CreateDirectory (cmdlineTools); + } + + try { + var info = new AndroidSdkInfo (androidSdkPath: sdk); + + // Test cmdline-tools path + recreateCmdlineToolsDirectory(); + CreateFauxAndroidSdkToolsDirectory (sdk, createToolsDir: true, toolsVersion: toolsVersion, createOldToolsDir: false); + var toolsPaths = info.GetCommandLineToolsPaths (); + + Assert.AreEqual (toolsPaths.Count (), 1, "Incorrect number of elements"); + Assert.AreEqual (toolsPaths.First (), Path.Combine (sdk, "cmdline-tools", toolsVersion), "Incorrect command line tools path"); + + // Test that cmdline-tools is preferred over tools + recreateCmdlineToolsDirectory(); + CreateFauxAndroidSdkToolsDirectory (sdk, createToolsDir: true, toolsVersion: latestToolsVersion, createOldToolsDir: true); + toolsPaths = info.GetCommandLineToolsPaths (); + + Assert.AreEqual (toolsPaths.Count (), 2, "Incorrect number of elements"); + Assert.AreEqual (toolsPaths.First (), Path.Combine (sdk, "cmdline-tools", latestToolsVersion), "Incorrect command line tools path"); + Assert.AreEqual (toolsPaths.Last (), Path.Combine (sdk, "tools"), "Incorrect tools path"); + + // Test sorting + recreateCmdlineToolsDirectory (); + CreateFauxAndroidSdkToolsDirectory (sdk, createToolsDir: true, toolsVersion: latestToolsVersion, createOldToolsDir: false); + CreateFauxAndroidSdkToolsDirectory (sdk, createToolsDir: true, toolsVersion: toolsVersion, createOldToolsDir: false); + CreateFauxAndroidSdkToolsDirectory (sdk, createToolsDir: true, toolsVersion: higherToolsVersion, createOldToolsDir: true); + toolsPaths = info.GetCommandLineToolsPaths (); + + var toolsPathsList = toolsPaths.ToList (); + Assert.AreEqual (toolsPaths.Count (), 4, "Incorrect number of elements"); + bool isOrderCorrect = toolsPathsList [0].Equals (Path.Combine (sdk, "cmdline-tools", latestToolsVersion), StringComparison.Ordinal) + && toolsPathsList [1].Equals (Path.Combine (sdk, "cmdline-tools", higherToolsVersion), StringComparison.Ordinal) + && toolsPathsList [2].Equals (Path.Combine (sdk, "cmdline-tools", toolsVersion), StringComparison.Ordinal) + && toolsPathsList [3].Equals (Path.Combine (sdk, "tools"), StringComparison.Ordinal); + + Assert.IsTrue (isOrderCorrect, "Tools order is not descending"); + } finally { + Directory.Delete (root, recursive: true); + } + } + static bool IsWindows => OS.IsWindows; - static void CreateSdks (out string root, out string jdk, out string ndk, out string sdk) + static string CreateRoot () { - root = Path.GetTempFileName (); + var root = Path.GetTempFileName (); File.Delete (root); Directory.CreateDirectory (root); + return root; + } + + static void CreateSdks (out string root, out string jdk, out string ndk, out string sdk) + { + root = CreateRoot (); ndk = Path.Combine (root, "ndk"); sdk = Path.Combine (root, "sdk"); @@ -185,17 +246,45 @@ static void CreateSdks (out string root, out string jdk, out string ndk, out str CreateFauxJavaSdkDirectory (jdk, "1.8.0", out var _, out var _); } - static void CreateFauxAndroidSdkDirectory (string androidSdkDirectory, string buildToolsVersion, ApiInfo [] apiLevels = null) + static void CreateFauxAndroidSdkToolsDirectory (string androidSdkDirectory, bool createToolsDir, string toolsVersion, bool createOldToolsDir) + { + if (createToolsDir) { + string androidSdkToolsPath = Path.Combine (androidSdkDirectory, "cmdline-tools", toolsVersion ?? "1.0"); + string androidSdkToolsBinPath = Path.Combine (androidSdkToolsPath, "bin"); + + Directory.CreateDirectory (androidSdkToolsPath); + Directory.CreateDirectory (androidSdkToolsBinPath); + + File.WriteAllText (Path.Combine (androidSdkToolsBinPath, IsWindows ? "lint.bat" : "lint"), ""); + } + + if (createOldToolsDir) { + string androidSdkToolsPath = Path.Combine (androidSdkDirectory, "tools"); + string androidSdkToolsBinPath = Path.Combine (androidSdkToolsPath, "bin"); + + Directory.CreateDirectory (androidSdkToolsPath); + Directory.CreateDirectory (androidSdkToolsBinPath); + + File.WriteAllText (Path.Combine (androidSdkToolsBinPath, IsWindows ? "lint.bat" : "lint"), ""); + } + + } + + static void CreateFauxAndroidSdkDirectory ( + string androidSdkDirectory, + string buildToolsVersion, + bool createToolsDir = true, + string toolsVersion = null, + bool createOldToolsDir = false, + ApiInfo[] apiLevels = null) { - var androidSdkToolsPath = Path.Combine (androidSdkDirectory, "tools"); - var androidSdkBinPath = Path.Combine (androidSdkToolsPath, "bin"); + CreateFauxAndroidSdkToolsDirectory (androidSdkDirectory, createToolsDir, toolsVersion, createOldToolsDir); + var androidSdkPlatformToolsPath = Path.Combine (androidSdkDirectory, "platform-tools"); var androidSdkPlatformsPath = Path.Combine (androidSdkDirectory, "platforms"); var androidSdkBuildToolsPath = Path.Combine (androidSdkDirectory, "build-tools", buildToolsVersion); Directory.CreateDirectory (androidSdkDirectory); - Directory.CreateDirectory (androidSdkToolsPath); - Directory.CreateDirectory (androidSdkBinPath); Directory.CreateDirectory (androidSdkPlatformToolsPath); Directory.CreateDirectory (androidSdkPlatformsPath); Directory.CreateDirectory (androidSdkBuildToolsPath); @@ -203,7 +292,6 @@ static void CreateFauxAndroidSdkDirectory (string androidSdkDirectory, string bu File.WriteAllText (Path.Combine (androidSdkPlatformToolsPath, IsWindows ? "adb.exe" : "adb"), ""); File.WriteAllText (Path.Combine (androidSdkBuildToolsPath, IsWindows ? "zipalign.exe" : "zipalign"), ""); File.WriteAllText (Path.Combine (androidSdkBuildToolsPath, IsWindows ? "aapt.exe" : "aapt"), ""); - File.WriteAllText (Path.Combine (androidSdkToolsPath, IsWindows ? "lint.bat" : "lint"), ""); List defaults = new List (); for (int i = 10; i < 26; i++) { From 0f984edd29fd44d22ebee88869ed7e7d05d89290 Mon Sep 17 00:00:00 2001 From: Jonathan Pryor Date: Tue, 2 Jun 2020 05:09:04 -0400 Subject: [PATCH 076/308] [Xamarin.Android.Tools.AndroidSdk] Preview Build-Tools are Last (#85) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Context: https://github.com/xamarin/xamarin-android/pull/4735 > https://devdiv.visualstudio.com/DevDiv/_build/results?buildId=3771781&view=ms.vss-test-web.build-test-results-tab&runId=13535824&resultId=100072&paneView=attachments Context: https://github.com/xamarin/xamarin-android/pull/4567 Context: https://github.com/xamarin/androidtools/commit/a3965baeea566ba6a1f346c676d9e2d5ecba6167 The Windows Smoke Tests are failing on PR #4735, because the wrong Android SDK Build-tools version is being used: Task "ResolveAndroidTooling" (TaskId:9) Task Parameter:AndroidSdkBuildToolsVersion=29.0.2 (TaskId:9) … Trying build-tools path: C:\Users\dlab14\android-toolchain\sdk\build-tools\30.0.0-rc4 (TaskId:9) … ResolveAndroidTooling Outputs: (TaskId:9) AndroidSdkBuildToolsPath: C:\Users\dlab14\android-toolchain\sdk\build-tools\30.0.0-rc4 (TaskId:9) Here, Build-tools 30.0.0-rc4 is used. Why is that a problem? Because JDK 1.8 is still used! C:\Program Files\Android\jdk\microsoft_dist_openjdk_1.8.0.25\bin\java.exe -jar C:\Users\dlab14\android-toolchain\sdk\build-tools\30.0.0-rc4\lib\apksigner.jar sign --ks "C:\Users\dlab14\AppData\Local\Xamarin\Mono for Android\debug.keystore" --ks-pass pass:android --ks-key-alias androiddebugkey --key-pass pass:android --min-sdk-version 21 --max-sdk-version 29 … java.lang.UnsupportedClassVersionError: com/android/apksigner/ApkSignerTool has been compiled by a more recent version of the Java Runtime (class file version 53.0), this version of the Java Runtime only recognizes class file versions up to 52.0 JDK11 is required in order to use Build-tools r30+, but we don't currently support JDK11 use; see PR #4567. What's happening is that *because of* PR #4567, some of the build machines are being provisioned with Build-tools 30.0.0-rc4, i.e. e.g. `$HOME/android-toolchain/sdk/build-tools/30.0.0-rc4` *exists*. *Because* that directory exists -- and *isn't* parse-able by `System.Version.TryParse()` -- it's considered to be a "preview" version, and thus *has priority over non-preview versions*, and thus is returned *first*. *Because* it's returned *first*, `` prefers it, causing the `` task to subsequently fail, because it's unusable. The "workaround" is for `$(AndroidSdkBuildToolsVersion)` to be set to the desired version to use. In retrospect, however, this is all *backwards*: `AndroidSdkInfo.GetBuildToolsPaths()` shouldn't prefer preview versions, it should prefer *non-* preview versions. If you *really* want a preview version, *then* you should override `$(AndroidSdkBuildToolsPath)`. Previews should be opt-in, not opt-out. With that in mind, why were preview versions preferred in the first place? That was done in xamarin/androidtools@a3965bae to fix [Bug #30555][0], stating: > handle[] by searching for these "preview" directories first, so that > the latest tooling will be used IF the user decides to install it. > The assumption is that a stable release will NOT inlcude a suffix. While "a preview directory will only exist if someone explicitly installs it, and thus should be preferred" sounds reasonable, in retrospect it's a recipe for pain when using a shared CI environment. Just because it's there does *not* mean it should be used. [0]: https://bugzilla.xamarin.com/show_bug.cgi?id=30555 --- external/xamarin-android-tools/Makefile | 4 +-- external/xamarin-android-tools/NuGet.config | 17 +++++++++++++ .../AndroidSdkInfo.cs | 10 ++++---- .../AndroidSdkInfoTests.cs | 25 +++++++++++++++++++ 4 files changed, 49 insertions(+), 7 deletions(-) create mode 100644 external/xamarin-android-tools/NuGet.config diff --git a/external/xamarin-android-tools/Makefile b/external/xamarin-android-tools/Makefile index ef6d107d30e..6cf6e73e525 100644 --- a/external/xamarin-android-tools/Makefile +++ b/external/xamarin-android-tools/Makefile @@ -1,5 +1,5 @@ CONFIGURATION := Debug -NUNIT_CONSOLE := packages/NUnit.ConsoleRunner.3.9.0/tools/nunit3-console.exe +NUNIT_CONSOLE := packages/nunit.consolerunner/3.9.0/tools/nunit3-console.exe OS := $(shell uname) RUNTIME := mono --debug=casts V ?= 0 @@ -23,7 +23,7 @@ define RUN_NUNIT_TEST $(RUNTIME) \ $(NUNIT_CONSOLE) $(NUNIT_EXTRA) $(1) \ $(if $(RUN),-run:$(RUN)) \ - --result="TestResult-$(basename $(notdir $(1))).xml;format=nunit2" \ + --result="TestResult-$(basename $(notdir $(1))).xml" \ -output=bin/Test$(CONFIGURATION)/TestOutput-$(basename $(notdir $(1))).txt \ || true ; \ if [ -f "bin/Test$(CONFIGURATION)/TestOutput-$(basename $(notdir $(1))).txt" ] ; then \ diff --git a/external/xamarin-android-tools/NuGet.config b/external/xamarin-android-tools/NuGet.config new file mode 100644 index 00000000000..a877b8c9f07 --- /dev/null +++ b/external/xamarin-android-tools/NuGet.config @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidSdkInfo.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidSdkInfo.cs index 4cf0ee4c7d9..c12d7a41b84 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidSdkInfo.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidSdkInfo.cs @@ -45,17 +45,17 @@ public IEnumerable GetBuildToolsPaths () { var buildTools = Path.Combine (AndroidSdkPath, "build-tools"); if (Directory.Exists (buildTools)) { + var sorted = SortedSubdirectoriesByVersion (buildTools); + + foreach (var d in sorted) + yield return d; + var preview = Directory.EnumerateDirectories (buildTools) .Where(x => TryParseVersion (Path.GetFileName (x)) == null) .Select(x => x); foreach (var d in preview) yield return d; - - var sorted = SortedSubdirectoriesByVersion (buildTools); - - foreach (var d in sorted) - yield return d; } var ptPath = Path.Combine (AndroidSdkPath, "platform-tools"); if (Directory.Exists (ptPath)) diff --git a/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/AndroidSdkInfoTests.cs b/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/AndroidSdkInfoTests.cs index 5b31f3b0f77..262a2925aeb 100644 --- a/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/AndroidSdkInfoTests.cs +++ b/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/AndroidSdkInfoTests.cs @@ -448,5 +448,30 @@ string UnixConfigPath { return Path.Combine (UnixConfigDirOverridePath, "monodroid-config.xml"); } } + + [Test] + public void GetBuildToolsPaths_StableVersionsFirst () + { + CreateSdks (out string root, out string jdk, out string ndk, out string sdk); + CreateFauxAndroidSdkDirectory (sdk, "27.0.0-rc4"); + + var logs = new StringWriter (); + Action logger = (level, message) => { + logs.WriteLine ($"[{level}] {message}"); + }; + + try { + var info = new AndroidSdkInfo (logger, androidSdkPath: sdk, androidNdkPath: ndk, javaSdkPath: jdk); + + var buildToolsPaths = info.GetBuildToolsPaths ().ToList (); + Assert.AreEqual (3, buildToolsPaths.Count); + Assert.AreEqual (Path.Combine (sdk, "build-tools", "26.0.0"), buildToolsPaths [0]); + Assert.AreEqual (Path.Combine (sdk, "build-tools", "27.0.0-rc4"), buildToolsPaths [1]); + Assert.AreEqual (Path.Combine (sdk, "platform-tools"), buildToolsPaths [2]); + } + finally { + Directory.Delete (root, recursive: true); + } + } } } From b7b03d483efbbaf3b7ea2f212a243206012d6611 Mon Sep 17 00:00:00 2001 From: Jonathan Pryor Date: Tue, 2 Jun 2020 12:02:08 -0400 Subject: [PATCH 077/308] [Xamarin.Android.Tools.AndroidSdk] Nullable Reference Type support (#84) Annotate `Xamarin.Android.Tools.AndroidSdk.dll` to support C#8 [Nullable Reference Types][0] Additionally, add Visual Studio Code support and fix the Azure Pipelines build. [0]: https://docs.microsoft.com/en-us/dotnet/csharp/nullable-references --- external/xamarin-android-tools/.editorconfig | 199 ++++++++++++++++++ .../.vscode/extensions.json | 8 + .../xamarin-android-tools/.vscode/launch.json | 22 ++ .../.vscode/settings.json | 6 + .../xamarin-android-tools/.vscode/tasks.json | 43 ++++ .../Directory.Build.props | 25 +++ external/xamarin-android-tools/Makefile | 2 +- external/xamarin-android-tools/NuGet.Config | 11 + external/xamarin-android-tools/NuGet.config | 4 + .../Xamarin.Android.Tools.code-workspace | 9 + .../azure-pipelines.yaml | 58 +++-- .../AndroidAppManifest.cs | 26 +-- .../AndroidSdkInfo.cs | 28 +-- .../AndroidVersion.cs | 6 +- .../AndroidVersions.cs | 26 +-- .../FileUtil.cs | 2 +- .../JdkInfo.cs | 53 +++-- .../NullableAttributes.cs | 133 ++++++++++++ .../Xamarin.Android.Tools.AndroidSdk/OS.cs | 14 +- .../ProcessUtils.cs | 14 +- .../Sdks/AndroidSdkBase.cs | 46 ++-- .../Sdks/AndroidSdkUnix.cs | 34 +-- .../Sdks/AndroidSdkWindows.cs | 49 ++--- .../Xamarin.Android.Tools.AndroidSdk.csproj | 15 +- ...arin.Android.Tools.AndroidSdk-Tests.csproj | 10 +- 25 files changed, 668 insertions(+), 175 deletions(-) create mode 100644 external/xamarin-android-tools/.editorconfig create mode 100644 external/xamarin-android-tools/.vscode/extensions.json create mode 100644 external/xamarin-android-tools/.vscode/launch.json create mode 100644 external/xamarin-android-tools/.vscode/settings.json create mode 100644 external/xamarin-android-tools/.vscode/tasks.json create mode 100644 external/xamarin-android-tools/Directory.Build.props create mode 100644 external/xamarin-android-tools/NuGet.Config create mode 100644 external/xamarin-android-tools/Xamarin.Android.Tools.code-workspace create mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/NullableAttributes.cs diff --git a/external/xamarin-android-tools/.editorconfig b/external/xamarin-android-tools/.editorconfig new file mode 100644 index 00000000000..73178936acc --- /dev/null +++ b/external/xamarin-android-tools/.editorconfig @@ -0,0 +1,199 @@ +############################### +# Core EditorConfig Options # +############################### + +root = true + +# All files +[*] +charset = utf-8 +trim_trailing_whitespace = true + +# MSBuild +[*.{csproj,proj,projitems,shproj,fsproj,targets,props}] +indent_style = space +indent_size = 2 + +# XML config files +[*.{xml,axml,xaml,config,nuspec,resx}] +indent_style = space +indent_size = 2 + +# JSON files +[*.json] +indent_style = space +indent_size = 2 + +# F# files +[*.{fs, fsx, fsi}] +indent_style = space +indent_size = 4 + +# Code files +[*.{cs,csx,vb,vbx}] +insert_final_newline = true +indent_style = tab +tab_width = 8 +indent_size = 8 +max_line_length = 180 + +############################### +# .NET Coding Conventions # +############################### + +[*.{cs,vb}] +# Organize usings +dotnet_sort_system_directives_first = true +dotnet_separate_import_directive_groups = false + +# Avoid "this." and "Me." if not necessary +dotnet_style_qualification_for_field = false:suggestion +dotnet_style_qualification_for_property = false:suggestion +dotnet_style_qualification_for_method = false:suggestion +dotnet_style_qualification_for_event = false:suggestion + +# Use language keywords instead of framework type names for type references +dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion +dotnet_style_predefined_type_for_member_access = true:suggestion + +# Suggest more modern language features when available +dotnet_style_object_initializer = true:suggestion +dotnet_style_collection_initializer = true:suggestion +dotnet_style_coalesce_expression = true:suggestion +dotnet_style_null_propagation = true:suggestion +dotnet_style_explicit_tuple_names = true:suggestion + +# Parentheses preferences +dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent +dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent +dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent +dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent + +# Avoid redundant accessibility modifiers when they're default +dotnet_style_require_accessibility_modifiers = omit_if_default:suggestion +dotnet_style_readonly_field = true:suggestion + +# Expression-level preferences +dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion +dotnet_style_prefer_inferred_tuple_names = true:suggestion +dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion +dotnet_style_prefer_auto_properties = true:silent +dotnet_style_prefer_conditional_expression_over_assignment = true:silent +dotnet_style_prefer_conditional_expression_over_return = true:silent + +############################### +# Naming Conventions # +############################### + +# Style Definitions +dotnet_naming_style.pascal_case_style.capitalization = pascal_case + +dotnet_naming_style.underline_separator.word_separator = _ +dotnet_naming_style.underline_separator.capitalization = all_lower + +# Symbol Definitions +dotnet_naming_symbols.parameters.applicable_kinds = parameter +dotnet_naming_symbols.parameters.applicable_accessibilities = * + +dotnet_naming_symbols.fields.applicable_kinds = field + +dotnet_naming_symbols.constant_fields.applicable_kinds = field +dotnet_naming_symbols.constant_fields.applicable_accessibilities = * +dotnet_naming_symbols.constant_fields.required_modifiers = const + +# Use CamelCase for parameters +dotnet_naming_rule.method_parameters_should_be_camel_case.severity = suggestion +dotnet_naming_rule.method_parameters_should_be_camel_case.symbols = parameters +dotnet_naming_rule.method_parameters_should_be_camel_case.style = camel_case + +# Use PascalCase for constant fields +dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields +dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style + +# Use underline separator for instance fields +dotnet_naming_rule.fields_should_be_underline_separator.severity = suggestion +dotnet_naming_rule.fields_should_be_underline_separator.symbols = fields +dotnet_naming_rule.fields_should_be_underline_separator.style = underline_separator + + +############################### +# C# Code Style Rules # +############################### + +[*.cs] +# var preferences +csharp_style_var_for_built_in_types = true:silent +csharp_style_var_when_type_is_apparent = true:silent +csharp_style_var_elsewhere = true:silent + +# Expression-bodied members +csharp_style_expression_bodied_methods = false:silent +csharp_style_expression_bodied_constructors = false:silent +csharp_style_expression_bodied_operators = false:silent +csharp_style_expression_bodied_properties = true:silent +csharp_style_expression_bodied_indexers = true:silent +csharp_style_expression_bodied_accessors = true:silent + +# Pattern-matching preferences +csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion +csharp_style_pattern_matching_over_as_with_null_check = true:suggestion + +# Null-checking preferences +csharp_style_throw_expression = true:suggestion +csharp_style_conditional_delegate_call = true:suggestion + +# Modifier preferences +csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:suggestion + +# Expression-level preferences +csharp_prefer_braces = true:silent +csharp_style_deconstructed_variable_declaration = true:suggestion +csharp_prefer_simple_default_expression = true:suggestion +csharp_style_pattern_local_over_anonymous_function = true:suggestion +csharp_style_inlined_variable_declaration = true:suggestion + +############################### +# C# Formatting Rules # +############################### + +# Newline settings +csharp_new_line_before_open_brace = methods,types +csharp_new_line_before_else = false +csharp_new_line_before_catch = false +csharp_new_line_before_finally = false +csharp_new_line_before_members_in_object_initializers = true +csharp_new_line_before_members_in_anonymous_types = true + +# Indentation preferences +csharp_indent_switch_labels = false +csharp_indent_case_contents = true +csharp_indent_switch_labels = true + +# Space preferences +csharp_space_after_cast = true +csharp_space_after_keywords_in_control_flow_statements = true +csharp_space_between_method_call_parameter_list_parentheses = false +csharp_space_between_method_declaration_parameter_list_parentheses = false +csharp_space_between_parentheses = false +csharp_space_before_colon_in_inheritance_clause = true +csharp_space_after_colon_in_inheritance_clause = true +csharp_space_around_binary_operators = before_and_after +csharp_space_between_method_declaration_empty_parameter_list_parentheses = false +csharp_space_between_method_call_name_and_opening_parenthesis = true +csharp_space_between_method_call_empty_parameter_list_parentheses = false +csharp_space_between_method_declaration_name_and_open_parenthesis = true +csharp_space_after_keywords_in_control_flow_statements = true +csharp_space_before_open_square_brackets = true + +# Wrapping preferences +csharp_preserve_single_line_statements = true +csharp_preserve_single_line_blocks = true + +################################## +# Visual Basic Code Style Rules # +################################## + +[*.vb] +# Modifier preferences +visual_basic_preferred_modifier_order = Partial,Default,Private,Protected,Public,Friend,NotOverridable,Overridable,MustOverride,Overloads,Overrides,MustInherit,NotInheritable,Static,Shared,Shadows,ReadOnly,WriteOnly,Dim,Const,WithEvents,Widening,Narrowing,Custom,Async:suggestion diff --git a/external/xamarin-android-tools/.vscode/extensions.json b/external/xamarin-android-tools/.vscode/extensions.json new file mode 100644 index 00000000000..0377b399d7b --- /dev/null +++ b/external/xamarin-android-tools/.vscode/extensions.json @@ -0,0 +1,8 @@ +{ + "recommendations": [ + "ms-vscode.csharp", + "ms-vscode.mono-debug", + "wghats.vscode-nxunit-test-adapter", + "visualstudioexptteam.vscodeintellicode", + ] +} \ No newline at end of file diff --git a/external/xamarin-android-tools/.vscode/launch.json b/external/xamarin-android-tools/.vscode/launch.json new file mode 100644 index 00000000000..a0ddfb7fc04 --- /dev/null +++ b/external/xamarin-android-tools/.vscode/launch.json @@ -0,0 +1,22 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Launch", + "type": "mono", + "request": "launch", + "program": "${workspaceRoot}/packages/NUnit.ConsoleRunner.3.9.0/tools/nunit3-console.exe ${workspaceRoot}/bin/TestDebug/generator-Tests.dll", + "cwd": "${workspaceRoot}bin/TestDebug/" + }, + { + "name": "Attach", + "type": "mono", + "request": "attach", + "address": "localhost", + "port": 55555 + } + ] +} \ No newline at end of file diff --git a/external/xamarin-android-tools/.vscode/settings.json b/external/xamarin-android-tools/.vscode/settings.json new file mode 100644 index 00000000000..bff3f6acf7a --- /dev/null +++ b/external/xamarin-android-tools/.vscode/settings.json @@ -0,0 +1,6 @@ +{ + "nxunitExplorer.nunit": "packages/NUnit.ConsoleRunner.3.9.0/tools/nunit3-console.exe", + "nxunitExplorer.modules": [ + "bin/TestDebug/Xamarin.Android.Tools.AndroidSdk-Tests.dll" + ] +} \ No newline at end of file diff --git a/external/xamarin-android-tools/.vscode/tasks.json b/external/xamarin-android-tools/.vscode/tasks.json new file mode 100644 index 00000000000..87828d66f95 --- /dev/null +++ b/external/xamarin-android-tools/.vscode/tasks.json @@ -0,0 +1,43 @@ +{ + // See https://go.microsoft.com/fwlink/?LinkId=733558 + // for the documentation about the tasks.json format + "version": "2.0.0", + "tasks": [ + { + "label": "Build Xamarin.Android.Tools", + "type": "shell", + "command": "msbuild Xamarin.Android.Tools.sln /restore", + "group": { + "kind": "build", + "isDefault": true + }, + "problemMatcher": [ + "$msCompile" + ] + }, + { + "label": "Clean Xamarin.Android.Tools", + "type": "shell", + "command": "msbuild Xamarin.Android.Tools.sln /restore /t:Clean", + "group": { + "kind": "build", + "isDefault": true + }, + "problemMatcher": [ + "$msCompile" + ] + }, + { + "label": "Run Unit Tests", + "type": "shell", + "command": "make run-all-tests", + "group": { + "kind": "test", + "isDefault": true + }, + "problemMatcher": [ + "$msCompile" + ] + } + ] +} diff --git a/external/xamarin-android-tools/Directory.Build.props b/external/xamarin-android-tools/Directory.Build.props new file mode 100644 index 00000000000..74fe6cfa3a3 --- /dev/null +++ b/external/xamarin-android-tools/Directory.Build.props @@ -0,0 +1,25 @@ + + + + + Debug + + + obj\ + + + True + + + $(BaseIntermediateOutputPath)\$(Configuration)-$(TargetFramework.ToLowerInvariant()) + $(MSBuildThisFileDirectory)bin\Build$(Configuration)\$(TargetFramework.ToLowerInvariant())\ + $(MSBuildThisFileDirectory)bin\$(Configuration)\$(TargetFramework.ToLowerInvariant())\ + $(MSBuildThisFileDirectory)bin\Test$(Configuration)-$(TargetFramework.ToLowerInvariant())\ + + + $(BaseIntermediateOutputPath)\$(Configuration) + $(MSBuildThisFileDirectory)bin\Build$(Configuration)\ + $(MSBuildThisFileDirectory)bin\$(Configuration)\ + $(MSBuildThisFileDirectory)bin\Test$(Configuration)\ + + diff --git a/external/xamarin-android-tools/Makefile b/external/xamarin-android-tools/Makefile index 6cf6e73e525..1c686c441f9 100644 --- a/external/xamarin-android-tools/Makefile +++ b/external/xamarin-android-tools/Makefile @@ -7,7 +7,7 @@ V ?= 0 include build-tools/scripts/msbuild.mk all: - $(MSBUILD) $(MSBUILD_FLAGS) Xamarin.Android.Tools.sln + $(MSBUILD) /restore $(MSBUILD_FLAGS) Xamarin.Android.Tools.sln clean: -$(MSBUILD) $(MSBUILD_FLAGS) /t:Clean Xamarin.Android.Tools.sln diff --git a/external/xamarin-android-tools/NuGet.Config b/external/xamarin-android-tools/NuGet.Config new file mode 100644 index 00000000000..9eee256d93c --- /dev/null +++ b/external/xamarin-android-tools/NuGet.Config @@ -0,0 +1,11 @@ + + + + + + + \ No newline at end of file diff --git a/external/xamarin-android-tools/NuGet.config b/external/xamarin-android-tools/NuGet.config index a877b8c9f07..713d9fcef2a 100644 --- a/external/xamarin-android-tools/NuGet.config +++ b/external/xamarin-android-tools/NuGet.config @@ -1,4 +1,8 @@ + diff --git a/external/xamarin-android-tools/Xamarin.Android.Tools.code-workspace b/external/xamarin-android-tools/Xamarin.Android.Tools.code-workspace new file mode 100644 index 00000000000..be71cd0b901 --- /dev/null +++ b/external/xamarin-android-tools/Xamarin.Android.Tools.code-workspace @@ -0,0 +1,9 @@ +{ + "folders": [ + { + "path": "." + } + ], + "settings": { + } +} diff --git a/external/xamarin-android-tools/azure-pipelines.yaml b/external/xamarin-android-tools/azure-pipelines.yaml index 1db8f6c455c..59a3e7bceb4 100644 --- a/external/xamarin-android-tools/azure-pipelines.yaml +++ b/external/xamarin-android-tools/azure-pipelines.yaml @@ -1,14 +1,32 @@ name: Xamarin.Android.Tools $(Rev:r) +trigger: + - master + - d16-* + +pr: + - master + - d16-* + +# Global variables +variables: + DotNetCoreVersion: 3.1.100 + HostedMac: Hosted Mac Internal + HostedWinVS2019: Hosted Windows 2019 with VS2019 + jobs: - job: windows displayName: windows - pool: Hosted VS2017 + pool: $(HostedWinVS2019) steps: - - task: NuGetToolInstaller@1 + - task: UseDotNet@2 + displayName: Use .NET Core $(DotNetCoreVersion) + inputs: + version: $(DotNetCoreVersion) + - task: NuGetToolInstaller@0 displayName: 'Install NuGet' inputs: - versionSpec: '5.4.x' + versionSpec: 5.x - task: NuGetCommand@2 displayName: 'NuGet Restore' inputs: @@ -38,25 +56,27 @@ jobs: pathtoPublish: $(Build.ArtifactStagingDirectory) - job: mac displayName: mac - pool: Hosted macOS + pool: $(HostedMac) steps: - - task: NuGetToolInstaller@1 - displayName: 'Install NuGet' - inputs: - versionSpec: '5.4.x' - - task: NuGetCommand@2 - displayName: 'NuGet Restore' + - task: UseDotNet@2 + displayName: Use .NET Core $(DotNetCoreVersion) inputs: - restoreSolution: Xamarin.Android.Tools.sln - restoreDirectory: 'packages' - - task: MSBuild@1 - displayName: 'Build solution Xamarin.Android.Tools.sln' - inputs: - solution: Xamarin.Android.Tools.sln - - script: mono packages/nunit.consolerunner/3.9.0/tools/nunit3-console.exe bin/TestDebug/Xamarin.Android.Tools.AndroidSdk-Tests.dll - displayName: Run Tests + version: $(DotNetCoreVersion) + - script: | + dotnet tool install --global boots + boots https://download.mono-project.com/archive/6.4.0/macos-10-universal/MonoFramework-MDK-6.4.0.198.macos10.xamarin.universal.pkg + displayName: Install Mono 6.4 + - script: make prepare CONFIGURATION=$(Build.Configuration) + displayName: make prepare + + - script: make all CONFIGURATION=$(Build.Configuration) + displayName: make all + + - script: make run-all-tests CONFIGURATION=$(Build.Configuration) + displayName: make run-all-tests + - task: PublishTestResults@2 condition: always() inputs: testResultsFormat: NUnit - testResultsFiles: TestResult.xml + testResultsFiles: TestResult*.xml diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidAppManifest.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidAppManifest.cs index 2db516c84d7..5a9e5a75f4f 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidAppManifest.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidAppManifest.cs @@ -130,42 +130,42 @@ public void WriteToFile (string fileName) } } - static string NullIfEmpty (string value) + static string? NullIfEmpty (string? value) { return string.IsNullOrEmpty (value) ? null : value; } - public string PackageName { + public string? PackageName { get { return (string) manifest.Attribute ("package"); } set { manifest.SetAttributeValue ("package", NullIfEmpty (value)); } } - public string ApplicationLabel { + public string? ApplicationLabel { get { return (string) application.Attribute (aNS + "label"); } set { application.SetAttributeValue (aNS + "label", NullIfEmpty (value)); } } - public string ApplicationIcon { + public string? ApplicationIcon { get { return (string) application.Attribute (aNS + "icon"); } set { application.SetAttributeValue (aNS + "icon", NullIfEmpty (value)); } } - public string ApplicationTheme { + public string? ApplicationTheme { get { return (string) application.Attribute (aNS + "theme"); } set { application.SetAttributeValue (aNS + "theme", NullIfEmpty (value)); } } - public string VersionName { + public string? VersionName { get { return (string) manifest.Attribute (aNS + "versionName"); } set { manifest.SetAttributeValue (aNS + "versionName", NullIfEmpty (value)); } } - public string VersionCode { + public string? VersionCode { get { return (string) manifest.Attribute (aNS + "versionCode"); } set { manifest.SetAttributeValue (aNS + "versionCode", NullIfEmpty (value)); } } - public string InstallLocation { + public string? InstallLocation { get { return (string) manifest.Attribute (aNS + "installLocation"); } set { manifest.SetAttributeValue (aNS + "installLocation", NullIfEmpty (value)); } } @@ -190,7 +190,7 @@ public int? TargetSdkVersion { int? apiLevel = versions.GetApiLevelFromId (version); if (apiLevel.HasValue) return apiLevel.Value; - return versions.MaxStableVersion.ApiLevel; + return versions.MaxStableVersion?.ApiLevel; } return vn; } @@ -273,16 +273,16 @@ void RemoveAndroidPermissions (IEnumerable permissions) } [Obsolete ("Use GetLaunchableFastdevActivityName or GetLaunchableUserActivityName")] - public string GetLaunchableActivityName () + public string? GetLaunchableActivityName () { return GetLaunchableFastDevActivityName (); } /// Gets an activity that can be used to initialize the override directory for fastdev. [Obsolete ("This should not be needed anymore; Activity execution is not part of installation.")] - public string GetLaunchableFastDevActivityName () + public string? GetLaunchableFastDevActivityName () { - string first = null; + string? first = null; foreach (var a in GetLaunchableActivities ()) { var name = (string) a.Attribute (aName); //prefer the fastdev launcher, it's quicker @@ -300,7 +300,7 @@ public string GetLaunchableFastDevActivityName () // We add a fake launchable activity for FastDev, but we don't want // to launch that one when the user does Run or Debug - public string GetLaunchableUserActivityName () + public string? GetLaunchableUserActivityName () { return GetLaunchableActivities () .Select (a => (string) a.Attribute (aName)) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidSdkInfo.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidSdkInfo.cs index c12d7a41b84..1297e515f05 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidSdkInfo.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidSdkInfo.cs @@ -10,7 +10,7 @@ public class AndroidSdkInfo { AndroidSdkBase sdk; - public AndroidSdkInfo (Action logger = null, string androidSdkPath = null, string androidNdkPath = null, string javaSdkPath = null) + public AndroidSdkInfo (Action? logger = null, string? androidSdkPath = null, string? androidNdkPath = null, string? javaSdkPath = null) { logger = logger ?? DefaultConsoleLogger; @@ -71,10 +71,9 @@ orderby version descending select p; } - static Version TryParseVersion (string v) + static Version? TryParseVersion (string v) { - Version version; - if (Version.TryParse (v, out version)) + if (Version.TryParse (v, out var version)) return version; return null; } @@ -97,10 +96,13 @@ public string GetPlatformDirectoryFromId (string id) return Path.Combine (AndroidSdkPath, "platforms", "android-" + id); } - public string TryGetPlatformDirectoryFromApiLevel (string idOrApiLevel, AndroidVersions versions) + public string? TryGetPlatformDirectoryFromApiLevel (string idOrApiLevel, AndroidVersions versions) { var id = versions.GetIdFromApiLevel (idOrApiLevel); - var dir = GetPlatformDirectoryFromId (id); + if (id == null) + return null; + + string? dir = GetPlatformDirectoryFromId (id); if (Directory.Exists (dir)) return dir; @@ -118,12 +120,12 @@ public bool IsPlatformInstalled (int apiLevel) return apiLevel != 0 && Directory.Exists (GetPlatformDirectory (apiLevel)); } - public string AndroidNdkPath { + public string? AndroidNdkPath { get { return sdk.AndroidNdkPath; } } public string AndroidSdkPath { - get { return sdk.AndroidSdkPath; } + get { return sdk.AndroidSdkPath!; } } public string [] AllAndroidSdkPaths { @@ -133,14 +135,14 @@ public string [] AllAndroidSdkPaths { } public string JavaSdkPath { - get { return sdk.JavaSdkPath; } + get { return sdk.JavaSdkPath!; } } public string AndroidNdkHostPlatform { get { return sdk.NdkHostPlatform; } } - public static void SetPreferredAndroidNdkPath (string path, Action logger = null) + public static void SetPreferredAndroidNdkPath (string path, Action? logger = null) { logger = logger ?? DefaultConsoleLogger; @@ -160,7 +162,7 @@ internal static void DefaultConsoleLogger (TraceLevel level, string message) } } - public static void SetPreferredAndroidSdkPath (string path, Action logger = null) + public static void SetPreferredAndroidSdkPath (string path, Action? logger = null) { logger = logger ?? DefaultConsoleLogger; @@ -168,7 +170,7 @@ public static void SetPreferredAndroidSdkPath (string path, Action logger = null) + public static void SetPreferredJavaSdkPath (string path, Action? logger = null) { logger = logger ?? DefaultConsoleLogger; @@ -176,7 +178,7 @@ public static void SetPreferredJavaSdkPath (string path, Action logger = null) + public static void DetectAndSetPreferredJavaSdkPathToLatest (Action? logger = null) { if (OS.IsWindows) throw new NotImplementedException ("Windows is not supported at this time."); diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidVersion.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidVersion.cs index 9805fc62c70..5cb3a3bc987 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidVersion.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidVersion.cs @@ -13,7 +13,7 @@ public class AndroidVersion public string Id { get; private set; } // Name of an Android release, e.g. "Oreo" - public string CodeName { get; private set; } + public string? CodeName { get; private set; } // Android version number, e.g. 8.0 public string OSVersion { get; private set; } @@ -28,9 +28,9 @@ public class AndroidVersion public bool Stable { get; private set; } // Alternate Ids for a given API level. Allows for historical mapping, e.g. API-11 has alternate ID 'H'. - internal string[] AlternateIds { get; set; } + internal string[]? AlternateIds { get; set; } - public AndroidVersion (int apiLevel, string osVersion, string codeName = null, string id = null, bool stable = true) + public AndroidVersion (int apiLevel, string osVersion, string? codeName = null, string? id = null, bool stable = true) { if (osVersion == null) throw new ArgumentNullException (nameof (osVersion)); diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidVersions.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidVersions.cs index a56dcf343d1..98962884726 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidVersions.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidVersions.cs @@ -13,8 +13,8 @@ public class AndroidVersions List installedVersions = new List (); public IReadOnlyList FrameworkDirectories { get; } - public AndroidVersion MaxStableVersion { get; private set; } - public AndroidVersion MinStableVersion { get; private set; } + public AndroidVersion? MaxStableVersion { get; private set; } + public AndroidVersion? MinStableVersion { get; private set; } public IReadOnlyList InstalledBindingVersions { get; private set; } @@ -33,7 +33,7 @@ public AndroidVersions (IEnumerable frameworkDirectories) var dn = Path.GetFileName (dp); // In "normal" use, `dp` will contain e.g. `...\MonoAndroid\v1.0`. // We want the `MonoAndroid` dir, not the versioned dir. - var p = dn.StartsWith ("v", StringComparison.Ordinal) ? Path.GetDirectoryName (dp) : dp; + var p = dn.StartsWith ("v", StringComparison.Ordinal) ? (Path.GetDirectoryName (dp) ?? "") : dp; dirs.Add (Path.GetFullPath (p)); } @@ -46,6 +46,8 @@ public AndroidVersions (IEnumerable frameworkDirectories) .Select (file => AndroidVersion.Load (file)); LoadVersions (versions); + + InstalledBindingVersions = new ReadOnlyCollection (installedVersions); } public AndroidVersions (IEnumerable versions) @@ -56,6 +58,8 @@ public AndroidVersions (IEnumerable versions) FrameworkDirectories = new ReadOnlyCollection (new string [0]); LoadVersions (versions); + + InstalledBindingVersions = new ReadOnlyCollection (installedVersions); } void LoadVersions (IEnumerable versions) @@ -71,8 +75,6 @@ void LoadVersions (IEnumerable versions) MinStableVersion = version; } } - - InstalledBindingVersions = new ReadOnlyCollection(installedVersions); } public int? GetApiLevelFromFrameworkVersion (string frameworkVersion) @@ -100,7 +102,7 @@ static bool MatchesId (AndroidVersion version, string id) (version.ApiLevel.ToString () == id); } - public string GetIdFromApiLevel (int apiLevel) + public string? GetIdFromApiLevel (int apiLevel) { return installedVersions.FirstOrDefault (v => v.ApiLevel == apiLevel)?.Id ?? KnownVersions.FirstOrDefault (v => v.ApiLevel == apiLevel)?.Id; @@ -108,7 +110,7 @@ public string GetIdFromApiLevel (int apiLevel) // Sometimes, e.g. when new API levels are introduced, the "API level" is a letter, not a number, // e.g. 'API-H' for API-11, 'API-O' for API-26, etc. - public string GetIdFromApiLevel (string apiLevel) + public string? GetIdFromApiLevel (string apiLevel) { if (int.TryParse (apiLevel, out var platform)) return GetIdFromApiLevel (platform); @@ -116,19 +118,19 @@ public string GetIdFromApiLevel (string apiLevel) KnownVersions.FirstOrDefault (v => MatchesId (v, apiLevel))?.Id; } - public string GetIdFromFrameworkVersion (string frameworkVersion) + public string? GetIdFromFrameworkVersion (string frameworkVersion) { return installedVersions.FirstOrDefault (v => MatchesFrameworkVersion (v, frameworkVersion))?.Id ?? KnownVersions.FirstOrDefault (v => MatchesFrameworkVersion (v, frameworkVersion))?.Id; } - public string GetFrameworkVersionFromApiLevel (int apiLevel) + public string? GetFrameworkVersionFromApiLevel (int apiLevel) { return installedVersions.FirstOrDefault (v => v.ApiLevel == apiLevel)?.FrameworkVersion ?? KnownVersions.FirstOrDefault (v => v.ApiLevel == apiLevel)?.FrameworkVersion; } - public string GetFrameworkVersionFromId (string id) + public string? GetFrameworkVersionFromId (string id) { return installedVersions.FirstOrDefault (v => MatchesId (v, id))?.FrameworkVersion ?? KnownVersions.FirstOrDefault (v => MatchesId (v, id))?.FrameworkVersion; @@ -179,10 +181,10 @@ class EqualityComparer : IEqualityComparer Func equals; Func getHashCode; - public EqualityComparer (Func equals, Func getHashCode = null) + public EqualityComparer (Func equals, Func? getHashCode = null) { this.equals = equals; - this.getHashCode = getHashCode ?? (v => v.GetHashCode ()); + this.getHashCode = getHashCode ?? (v => v?.GetHashCode () ?? 0); } public bool Equals (T x, T y) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/FileUtil.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/FileUtil.cs index 6fe8487e583..1b8457542bf 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/FileUtil.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/FileUtil.cs @@ -16,7 +16,7 @@ public static void SystemRename (string sourceFile, string destFile) { //FIXME: use the atomic System.IO.File.Replace on NTFS if (OS.IsWindows) { - string wtmp = null; + string? wtmp = null; if (File.Exists (destFile)) { do { wtmp = Path.Combine (Path.GetTempPath (), Guid.NewGuid ().ToString ()); diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/JdkInfo.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/JdkInfo.cs index 4d7f4531bac..e82886d27fb 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/JdkInfo.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/JdkInfo.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using System.Text; @@ -20,7 +21,7 @@ public class JdkInfo { public string HomePath {get;} - public string Locator {get;} + public string? Locator {get;} public string JarPath {get;} public string JavaPath {get;} @@ -28,10 +29,10 @@ public class JdkInfo { public string JdkJvmPath {get;} public ReadOnlyCollection IncludePath {get;} - public Version Version => javaVersion.Value; - public string Vendor { + public Version? Version => javaVersion.Value; + public string? Vendor { get { - if (GetJavaSettingsPropertyValue ("java.vendor", out string vendor)) + if (GetJavaSettingsPropertyValue ("java.vendor", out string? vendor)) return vendor; return null; } @@ -41,7 +42,7 @@ public string Vendor { public IEnumerable JavaSettingsPropertyKeys => javaProperties.Value.Keys; Lazy>> javaProperties; - Lazy javaVersion; + Lazy javaVersion; public JdkInfo (string homePath) { @@ -57,7 +58,7 @@ public JdkInfo (string homePath) JavaPath = ProcessUtils.FindExecutablesInDirectory (binPath, "java").FirstOrDefault (); JavacPath = ProcessUtils.FindExecutablesInDirectory (binPath, "javac").FirstOrDefault (); - string topDir = null; + string? topDir = null; foreach (string dir in JdkLibraryTopDirs) { topDir = Path.Combine (HomePath, dir); if (!Directory.Exists (topDir)) { @@ -71,8 +72,8 @@ public JdkInfo (string homePath) topDir = Path.Combine (HomePath, JdkLibraryTopDirs [0]); JdkJvmPath = OS.IsMac - ? FindLibrariesInDirectory (topDir, "jli").FirstOrDefault () - : FindLibrariesInDirectory (topDir, "jvm").FirstOrDefault (); + ? FindLibrariesInDirectory (topDir!, "jli").FirstOrDefault () + : FindLibrariesInDirectory (topDir!, "jvm").FirstOrDefault (); ValidateFile ("jar", JarPath); ValidateFile ("java", JavaPath); @@ -93,7 +94,7 @@ public JdkInfo (string homePath) IncludePath = new ReadOnlyCollection (includes); javaProperties = new Lazy>> (GetJavaProperties, LazyThreadSafetyMode.ExecutionAndPublication); - javaVersion = new Lazy (GetJavaVersion, LazyThreadSafetyMode.ExecutionAndPublication); + javaVersion = new Lazy (GetJavaVersion, LazyThreadSafetyMode.ExecutionAndPublication); } public JdkInfo (string homePath, string locator) @@ -107,7 +108,7 @@ public override string ToString() return $"JdkInfo(Version={Version}, Vendor=\"{Vendor}\", HomePath=\"{HomePath}\", Locator=\"{Locator}\")"; } - public bool GetJavaSettingsPropertyValues (string key, out IEnumerable value) + public bool GetJavaSettingsPropertyValues (string key, [NotNullWhen (true)] out IEnumerable? value) { value = null; var props = javaProperties.Value; @@ -118,7 +119,7 @@ public bool GetJavaSettingsPropertyValues (string key, out IEnumerable v return false; } - public bool GetJavaSettingsPropertyValue (string key, out string value) + public bool GetJavaSettingsPropertyValue (string key, [NotNullWhen (true)] out string? value) { value = null; var props = javaProperties.Value; @@ -147,9 +148,9 @@ void ValidateFile (string name, string path) static Regex NonDigitMatcher = new Regex (@"[^\d]", RegexOptions.Compiled | RegexOptions.CultureInvariant); - Version GetJavaVersion () + Version? GetJavaVersion () { - string version = null; + string? version = null; if (ReleaseProperties.TryGetValue ("JAVA_VERSION", out version) && !string.IsNullOrEmpty (version)) { version = GetParsableVersion (version); if (ReleaseProperties.TryGetValue ("BUILD_NUMBER", out var build) && !string.IsNullOrEmpty (build)) @@ -193,7 +194,7 @@ ReadOnlyDictionary GetReleaseProperties () return new ReadOnlyDictionary(props); using (var release = File.OpenText (releasePath)) { - string line; + string? line; while ((line = release.ReadLine ()) != null) { line = line.Trim (); const string PropertyDelim = "="; @@ -243,7 +244,7 @@ static Dictionary> GetJavaProperties (string java) }; var props = new Dictionary> (); - string curKey = null; + string? curKey = null; if (!AnySystemJavasInstalled () && (java == "/usr/bin/java" || java == "java")) return props; @@ -266,8 +267,8 @@ static Dictionary> GetJavaProperties (string java) return; curKey = e.Data.Substring (NewValuePrefix.Length, delim - NewValuePrefix.Length); var value = e.Data.Substring (delim + NameValueDelim.Length); - List values; - if (!props.TryGetValue (curKey, out values)) + List? values; + if (!props.TryGetValue (curKey!, out values)) props.Add (curKey, values = new List ()); values.Add (value); } @@ -276,7 +277,7 @@ static Dictionary> GetJavaProperties (string java) return props; } - public static IEnumerable GetKnownSystemJdkInfos (Action logger = null) + public static IEnumerable GetKnownSystemJdkInfos (Action? logger = null) { logger = logger ?? AndroidSdkInfo.DefaultConsoleLogger; @@ -295,6 +296,7 @@ static IEnumerable GetConfiguredJdks (Action logger return GetConfiguredJdkPaths (logger) .Select (p => TryGetJdkInfo (p, logger, "monodroid-config.xml")) .Where (jdk => jdk != null) + .Select (jdk => jdk!) .OrderByDescending (jdk => jdk, JdkInfoVersionComparer.Default); } @@ -312,6 +314,7 @@ internal static IEnumerable GetMacOSMicrosoftJdks (Action TryGetJdkInfo (p, logger, "$HOME/Library/Developer/Xamarin/jdk")) .Where (jdk => jdk != null) + .Select (jdk => jdk!) .OrderByDescending (jdk => jdk, JdkInfoVersionComparer.Default); } @@ -329,9 +332,9 @@ static IEnumerable GetMacOSMicrosoftJdkPaths () return Directory.EnumerateDirectories (jdks); } - static JdkInfo TryGetJdkInfo (string path, Action logger, string locator) + static JdkInfo? TryGetJdkInfo (string path, Action logger, string locator) { - JdkInfo jdk = null; + JdkInfo? jdk = null; try { jdk = new JdkInfo (path, locator); } @@ -366,6 +369,7 @@ static IEnumerable GetLibexecJdks (Action logger) .Distinct () .Select (p => TryGetJdkInfo (p, logger, "`/usr/libexec/java_home -X`")) .Where (jdk => jdk != null) + .Select (jdk => jdk!) .OrderByDescending (jdk => jdk, JdkInfoVersionComparer.Default); } @@ -404,7 +408,8 @@ static IEnumerable GetJavaAlternativesJdks (Action return GetJavaAlternativesJdkPaths () .Distinct () .Select (p => TryGetJdkInfo (p, logger, "`/usr/sbin/update-java-alternatives -l`")) - .Where (jdk => jdk != null); + .Where (jdk => jdk != null) + .Select (jdk => jdk!); } static IEnumerable GetJavaAlternativesJdkPaths () @@ -438,6 +443,7 @@ static IEnumerable GetLibJvmJdks (Action logger) .Distinct () .Select (p => TryGetJdkInfo (p, logger, "`ls /usr/lib/jvm/*`")) .Where (jdk => jdk != null) + .Select (jdk => jdk!) .OrderByDescending (jdk => jdk, JdkInfoVersionComparer.Default); } @@ -459,7 +465,8 @@ static IEnumerable GetPathEnvironmentJdks (Action l { return GetPathEnvironmentJdkPaths () .Select (p => TryGetJdkInfo (p, logger, "$PATH")) - .Where (jdk => jdk != null); + .Where (jdk => jdk != null) + .Select (jdk => jdk!); } static IEnumerable GetPathEnvironmentJdkPaths () @@ -471,7 +478,7 @@ static IEnumerable GetPathEnvironmentJdkPaths () // `java -XshowSettings:properties -version 2>&1 | grep java.home` ends with `/jre` on macOS. // We need the parent dir so we can properly lookup the `include` directories if (java_home.EndsWith ("jre", StringComparison.OrdinalIgnoreCase)) { - java_home = Path.GetDirectoryName (java_home); + java_home = Path.GetDirectoryName (java_home) ?? ""; } yield return java_home; } diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/NullableAttributes.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/NullableAttributes.cs new file mode 100644 index 00000000000..ba4e92f0106 --- /dev/null +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/NullableAttributes.cs @@ -0,0 +1,133 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace System.Diagnostics.CodeAnalysis +{ + /// Specifies that null is allowed as an input even if the corresponding type disallows it. + [AttributeUsage (AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property, Inherited = false)] +#if INTERNAL_NULLABLE_ATTRIBUTES + internal +#else + public +#endif + sealed class AllowNullAttribute : Attribute + { } + + /// Specifies that null is disallowed as an input even if the corresponding type allows it. + [AttributeUsage (AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property, Inherited = false)] +#if INTERNAL_NULLABLE_ATTRIBUTES + internal +#else + public +#endif + sealed class DisallowNullAttribute : Attribute + { } + + /// Specifies that an output may be null even if the corresponding type disallows it. + [AttributeUsage (AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, Inherited = false)] +#if INTERNAL_NULLABLE_ATTRIBUTES + internal +#else + public +#endif + sealed class MaybeNullAttribute : Attribute + { } + + /// Specifies that an output will not be null even if the corresponding type allows it. + [AttributeUsage (AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, Inherited = false)] +#if INTERNAL_NULLABLE_ATTRIBUTES + internal +#else + public +#endif + sealed class NotNullAttribute : Attribute + { } + + /// Specifies that when a method returns , the parameter may be null even if the corresponding type disallows it. + [AttributeUsage (AttributeTargets.Parameter, Inherited = false)] +#if INTERNAL_NULLABLE_ATTRIBUTES + internal +#else + public +#endif + sealed class MaybeNullWhenAttribute : Attribute + { + /// Initializes the attribute with the specified return value condition. + /// + /// The return value condition. If the method returns this value, the associated parameter may be null. + /// + public MaybeNullWhenAttribute (bool returnValue) => ReturnValue = returnValue; + + /// Gets the return value condition. + public bool ReturnValue { get; } + } + + /// Specifies that when a method returns , the parameter will not be null even if the corresponding type allows it. + [AttributeUsage (AttributeTargets.Parameter, Inherited = false)] +#if INTERNAL_NULLABLE_ATTRIBUTES + internal +#else + public +#endif + sealed class NotNullWhenAttribute : Attribute + { + /// Initializes the attribute with the specified return value condition. + /// + /// The return value condition. If the method returns this value, the associated parameter will not be null. + /// + public NotNullWhenAttribute (bool returnValue) => ReturnValue = returnValue; + + /// Gets the return value condition. + public bool ReturnValue { get; } + } + + /// Specifies that the output will be non-null if the named parameter is non-null. + [AttributeUsage (AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, AllowMultiple = true, Inherited = false)] +#if INTERNAL_NULLABLE_ATTRIBUTES + internal +#else + public +#endif + sealed class NotNullIfNotNullAttribute : Attribute + { + /// Initializes the attribute with the associated parameter name. + /// + /// The associated parameter name. The output will be non-null if the argument to the parameter specified is non-null. + /// + public NotNullIfNotNullAttribute (string parameterName) => ParameterName = parameterName; + + /// Gets the associated parameter name. + public string ParameterName { get; } + } + + /// Applied to a method that will never return under any circumstance. + [AttributeUsage (AttributeTargets.Method, Inherited = false)] +#if INTERNAL_NULLABLE_ATTRIBUTES + internal +#else + public +#endif + sealed class DoesNotReturnAttribute : Attribute + { } + + /// Specifies that the method will not return if the associated Boolean parameter is passed the specified value. + [AttributeUsage (AttributeTargets.Parameter, Inherited = false)] +#if INTERNAL_NULLABLE_ATTRIBUTES + internal +#else + public +#endif + sealed class DoesNotReturnIfAttribute : Attribute + { + /// Initializes the attribute with the specified parameter value. + /// + /// The condition parameter value. Code after the method will be considered unreachable by diagnostics if the argument to + /// the associated parameter matches this value. + /// + public DoesNotReturnIfAttribute (bool parameterValue) => ParameterValue = parameterValue; + + /// Gets the condition parameter value. + public bool ParameterValue { get; } + } +} diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/OS.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/OS.cs index 81e776618aa..ffb395f1286 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/OS.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/OS.cs @@ -10,9 +10,9 @@ public class OS public readonly static bool IsWindows; public readonly static bool IsMac; - internal readonly static string ProgramFilesX86; + internal readonly static string? ProgramFilesX86; - internal readonly static string NativeLibraryFormat; + internal readonly static string NativeLibraryFormat = "{0}"; static OS () { @@ -39,7 +39,7 @@ static bool IsRunningOnMac () buf = Marshal.AllocHGlobal (8192); // This is a hacktastic way of getting sysname from uname () if (uname (buf) == 0) { - string os = System.Runtime.InteropServices.Marshal.PtrToStringAnsi (buf); + string? os = System.Runtime.InteropServices.Marshal.PtrToStringAnsi (buf); if (os == "Darwin") return true; } @@ -136,13 +136,13 @@ static extern int RegSetValueExW (UIntPtr hKey, string lpValueName, int lpReserv uint dwType, IntPtr data, uint cbData); [DllImport (ADVAPI, CharSet = CharSet.Unicode, SetLastError = true)] - static extern int RegCreateKeyEx (UIntPtr hKey, string subKey, uint reserved, string @class, uint options, + static extern int RegCreateKeyEx (UIntPtr hKey, string subKey, uint reserved, string? @class, uint options, uint samDesired, IntPtr lpSecurityAttributes, out UIntPtr phkResult, out Disposition lpdwDisposition); [DllImport ("advapi32.dll", SetLastError = true)] static extern int RegCloseKey (UIntPtr hKey); - public static string GetValueString (UIntPtr key, string subkey, string valueName, Wow64 wow64) + public static string? GetValueString (UIntPtr key, string subkey, string valueName, Wow64 wow64) { UIntPtr regKeyHandle; uint sam = (uint)Rights.QueryValue + (uint)wow64; @@ -168,8 +168,8 @@ public static void SetValueString (UIntPtr key, string subkey, string valueName, uint sam = (uint)(Rights.CreateSubKey | Rights.SetValue) + (uint)wow64; uint options = (uint) Options.NonVolatile; Disposition disposition; - if (RegCreateKeyEx (key, subkey, 0, null, options, sam, IntPtr.Zero, out regKeyHandle, out disposition) != 0) { - throw new Exception ("Could not open or craete key"); + if (RegCreateKeyEx (key, subkey, 0, "", options, sam, IntPtr.Zero, out regKeyHandle, out disposition) != 0) { + throw new Exception ("Could not open or create key"); } try { diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/ProcessUtils.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/ProcessUtils.cs index 352eb775d4e..19965e2c96e 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/ProcessUtils.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/ProcessUtils.cs @@ -19,7 +19,7 @@ static ProcessUtils () ExecutableFileExtensions = pathExts; } - public static async Task StartProcess (ProcessStartInfo psi, TextWriter stdout, TextWriter stderr, CancellationToken cancellationToken, Action onStarted = null) + public static async Task StartProcess (ProcessStartInfo psi, TextWriter? stdout, TextWriter? stderr, CancellationToken cancellationToken, Action? onStarted = null) { cancellationToken.ThrowIfCancellationRequested (); psi.UseShellExecute = false; @@ -47,10 +47,10 @@ public static async Task StartProcess (ProcessStartInfo psi, TextWriter std // end up writing to the same buffer, or they are the same object. using (cancellationToken.Register (() => KillProcess (process))) { if (psi.RedirectStandardOutput) - output = ReadStreamAsync (process.StandardOutput, TextWriter.Synchronized (stdout)); + output = ReadStreamAsync (process.StandardOutput, TextWriter.Synchronized (stdout!)); if (psi.RedirectStandardError) - error = ReadStreamAsync (process.StandardError, TextWriter.Synchronized (stderr)); + error = ReadStreamAsync (process.StandardError, TextWriter.Synchronized (stderr!)); await Task.WhenAll (new [] { output, error, exit }).ConfigureAwait (false); } @@ -89,7 +89,7 @@ static async Task ReadStreamAsync (StreamReader stream, TextWriter destination) /// /// Executes an Android Sdk tool and returns a result. The result is based on a function of the command output. /// - public static Task ExecuteToolAsync (string exe, Func result, CancellationToken token, Action onStarted = null) + public static Task ExecuteToolAsync (string exe, Func result, CancellationToken token, Action? onStarted = null) { var tcs = new TaskCompletionSource (); @@ -115,12 +115,12 @@ public static Task ExecuteToolAsync (string exe, Func FindExecutablesInPath (string executable) { - var path = Environment.GetEnvironmentVariable ("PATH"); + var path = Environment.GetEnvironmentVariable ("PATH") ?? ""; var pathDirs = path.Split (new char[] { Path.PathSeparator }, StringSplitOptions.RemoveEmptyEntries); foreach (var dir in pathDirs) { diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkBase.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkBase.cs index b401fb6d6e0..1b10df18d04 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkBase.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkBase.cs @@ -1,5 +1,6 @@ using System; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.IO; using System.Collections.Generic; @@ -8,8 +9,8 @@ namespace Xamarin.Android.Tools { abstract class AndroidSdkBase { - string[] allAndroidSdks = null; - string[] allAndroidNdks = null; + string[]? allAndroidSdks; + string[]? allAndroidNdks; public string[] AllAndroidSdks { get { @@ -33,12 +34,12 @@ public AndroidSdkBase (Action logger) Logger = logger; } - public string AndroidSdkPath { get; private set; } - public string AndroidNdkPath { get; private set; } - public string JavaSdkPath { get; private set; } - public string JavaBinPath { get; private set; } - public string AndroidPlatformToolsPath { get; private set; } - public string AndroidPlatformToolsPathShort { get; private set; } + public string? AndroidSdkPath { get; private set; } + public string? AndroidNdkPath { get; private set; } + public string? JavaSdkPath { get; private set; } + public string? JavaBinPath { get; private set; } + public string? AndroidPlatformToolsPath { get; private set; } + public string? AndroidPlatformToolsPathShort { get; private set; } public virtual string Adb { get; protected set; } = "adb"; public virtual string ZipAlign { get; protected set; } = "zipalign"; @@ -50,11 +51,11 @@ public AndroidSdkBase (Action logger) public abstract string NdkHostPlatform64Bit { get; } public virtual string Javac { get; protected set; } = "javac"; - public abstract string PreferedAndroidSdkPath { get; } - public abstract string PreferedAndroidNdkPath { get; } - public abstract string PreferedJavaSdkPath { get; } + public abstract string? PreferedAndroidSdkPath { get; } + public abstract string? PreferedAndroidNdkPath { get; } + public abstract string? PreferedJavaSdkPath { get; } - public virtual void Initialize (string androidSdkPath = null, string androidNdkPath = null, string javaSdkPath = null) + public virtual void Initialize (string? androidSdkPath = null, string? androidNdkPath = null, string? javaSdkPath = null) { androidSdkPath = androidSdkPath ?? PreferedAndroidSdkPath; androidNdkPath = androidNdkPath ?? PreferedAndroidNdkPath; @@ -94,12 +95,12 @@ public virtual void Initialize (string androidSdkPath = null, string androidNdkP protected abstract IEnumerable GetAllAvailableAndroidSdks (); protected abstract IEnumerable GetAllAvailableAndroidNdks (); - protected abstract string GetJavaSdkPath (); + protected abstract string? GetJavaSdkPath (); protected abstract string GetShortFormPath (string path); - public abstract void SetPreferredAndroidSdkPath (string path); - public abstract void SetPreferredJavaSdkPath (string path); - public abstract void SetPreferredAndroidNdkPath (string path); + public abstract void SetPreferredAndroidSdkPath (string? path); + public abstract void SetPreferredJavaSdkPath (string? path); + public abstract void SetPreferredAndroidNdkPath (string? path); public bool IsNdk64Bit { get; private set; } @@ -110,7 +111,7 @@ public string NdkHostPlatform { /// /// Checks that a value is the location of an Android SDK. /// - public bool ValidateAndroidSdkLocation (string loc) + public bool ValidateAndroidSdkLocation ([NotNullWhen (true)] string? loc) { bool result = !string.IsNullOrEmpty (loc) && ProcessUtils.FindExecutablesInDirectory (Path.Combine (loc, "platform-tools"), Adb).Any (); Logger (TraceLevel.Verbose, $"{nameof (ValidateAndroidSdkLocation)}: `{loc}`, result={result}"); @@ -120,7 +121,7 @@ public bool ValidateAndroidSdkLocation (string loc) /// /// Checks that a value is the location of a Java SDK. /// - public virtual bool ValidateJavaSdkLocation (string loc) + public virtual bool ValidateJavaSdkLocation ([NotNullWhen (true)] string? loc) { bool result = !string.IsNullOrEmpty (loc) && ProcessUtils.FindExecutablesInDirectory (Path.Combine (loc, "bin"), JarSigner).Any (); Logger (TraceLevel.Verbose, $"{nameof (ValidateJavaSdkLocation)}: `{loc}`, result={result}"); @@ -130,14 +131,15 @@ public virtual bool ValidateJavaSdkLocation (string loc) /// /// Checks that a value is the location of an Android SDK. /// - public bool ValidateAndroidNdkLocation (string loc) + public bool ValidateAndroidNdkLocation ([NotNullWhen (true)] string? loc) { - bool result = !string.IsNullOrEmpty (loc) && ProcessUtils.FindExecutablesInDirectory (loc, NdkStack).Any (); + bool result = !string.IsNullOrEmpty (loc) && + ProcessUtils.FindExecutablesInDirectory (loc!, NdkStack).Any (); Logger (TraceLevel.Verbose, $"{nameof (ValidateAndroidNdkLocation)}: `{loc}`, result={result}"); return result; } - protected static string NullIfEmpty (string s) + protected static string? NullIfEmpty (string? s) { if (s == null || s.Length != 0) return s; @@ -145,7 +147,7 @@ protected static string NullIfEmpty (string s) return null; } - static string GetExecutablePath (string dir, string exe) + static string GetExecutablePath (string? dir, string exe) { if (string.IsNullOrEmpty (dir)) return exe; diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkUnix.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkUnix.cs index 0d18f7bed62..12bd5360b21 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkUnix.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkUnix.cs @@ -13,8 +13,8 @@ namespace Xamarin.Android.Tools class AndroidSdkUnix : AndroidSdkBase { // See comments above UnixConfigPath for explanation on why these are needed - static readonly string sudo_user; - static readonly string user; + static readonly string? sudo_user; + static readonly string? user; static readonly bool need_chown; static AndroidSdkUnix () @@ -41,7 +41,7 @@ public override string NdkHostPlatform64Bit { get { return OS.IsMac ? "darwin-x86_64" : "linux-x86_64"; } } - public override string PreferedAndroidSdkPath { + public override string? PreferedAndroidSdkPath { get { var config_file = GetUnixConfigFile (Logger); var androidEl = config_file.Root.Element ("android-sdk"); @@ -56,7 +56,7 @@ public override string PreferedAndroidSdkPath { } } - public override string PreferedAndroidNdkPath { + public override string? PreferedAndroidNdkPath { get { var config_file = GetUnixConfigFile (Logger); var androidEl = config_file.Root.Element ("android-ndk"); @@ -71,7 +71,7 @@ public override string PreferedAndroidNdkPath { } } - public override string PreferedJavaSdkPath { + public override string? PreferedJavaSdkPath { get { var config_file = GetUnixConfigFile (Logger); var javaEl = config_file.Root.Element ("java-sdk"); @@ -90,7 +90,7 @@ protected override IEnumerable GetAllAvailableAndroidSdks () { var preferedSdkPath = PreferedAndroidSdkPath; if (!string.IsNullOrEmpty (preferedSdkPath)) - yield return preferedSdkPath; + yield return preferedSdkPath!; // Look in PATH foreach (var adb in ProcessUtils.FindExecutablesInPath (Adb)) { @@ -108,7 +108,7 @@ protected override IEnumerable GetAllAvailableAndroidSdks () yield return macSdkPath; } - protected override string GetJavaSdkPath () + protected override string? GetJavaSdkPath () { return JdkInfo.GetKnownSystemJdkInfos (Logger).FirstOrDefault ()?.HomePath; } @@ -117,7 +117,7 @@ protected override IEnumerable GetAllAvailableAndroidNdks () { var preferedNdkPath = PreferedAndroidNdkPath; if (!string.IsNullOrEmpty (preferedNdkPath)) - yield return preferedNdkPath; + yield return preferedNdkPath!; // Look in PATH foreach (var ndkStack in ProcessUtils.FindExecutablesInPath (NdkStack)) { @@ -133,7 +133,7 @@ protected override string GetShortFormPath (string path) return path; } - public override void SetPreferredAndroidSdkPath (string path) + public override void SetPreferredAndroidSdkPath (string? path) { path = NullIfEmpty (path); @@ -149,7 +149,7 @@ public override void SetPreferredAndroidSdkPath (string path) SaveConfig (doc); } - public override void SetPreferredJavaSdkPath (string path) + public override void SetPreferredJavaSdkPath (string? path) { path = NullIfEmpty (path); @@ -165,7 +165,7 @@ public override void SetPreferredJavaSdkPath (string path) SaveConfig (doc); } - public override void SetPreferredAndroidNdkPath (string path) + public override void SetPreferredAndroidNdkPath (string? path) { path = NullIfEmpty (path); @@ -184,11 +184,11 @@ public override void SetPreferredAndroidNdkPath (string path) void SaveConfig (XDocument doc) { string cfg = UnixConfigPath; - List created = null; + List ? created = null; if (!File.Exists (cfg)) { - string dir = Path.GetDirectoryName (cfg); - if (!Directory.Exists (dir)) { + string? dir = Path.GetDirectoryName (cfg); + if (dir != null && !Directory.Exists (dir)) { Directory.CreateDirectory (dir); AddToList (dir); } @@ -245,7 +245,7 @@ private static string UnixConfigPath { internal static XDocument GetUnixConfigFile (Action logger) { var file = UnixConfigPath; - XDocument doc = null; + XDocument? doc = null; if (File.Exists (file)) { try { doc = XDocument.Load (file); @@ -270,7 +270,7 @@ internal static XDocument GetUnixConfigFile (Action logger) return doc; } - void FixOwnership (List paths) + void FixOwnership (List? paths) { if (!need_chown || paths == null || paths.Count == 0) return; @@ -278,7 +278,7 @@ void FixOwnership (List paths) var stdout = new StringWriter (); var stderr = new StringWriter (); var args = new List { - QuoteString (sudo_user) + QuoteString (sudo_user!) }; foreach (string p in paths) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkWindows.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkWindows.cs index 792c6587459..c36370359ed 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkWindows.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkWindows.cs @@ -32,7 +32,7 @@ public AndroidSdkWindows (Action logger) public override string NdkHostPlatform64Bit { get { return "windows-x86_64"; } } public override string Javac { get; protected set; } = "javac.exe"; - public override string PreferedAndroidSdkPath { + public override string? PreferedAndroidSdkPath { get { var wow = RegistryEx.Wow64.Key32; var regKey = GetMDRegistryKey (); @@ -41,7 +41,7 @@ public override string PreferedAndroidSdkPath { return null; } } - public override string PreferedAndroidNdkPath { + public override string? PreferedAndroidNdkPath { get { var wow = RegistryEx.Wow64.Key32; var regKey = GetMDRegistryKey (); @@ -50,7 +50,7 @@ public override string PreferedAndroidNdkPath { return null; } } - public override string PreferedJavaSdkPath { + public override string? PreferedJavaSdkPath { get { var wow = RegistryEx.Wow64.Key32; var regKey = GetMDRegistryKey (); @@ -77,16 +77,16 @@ protected override IEnumerable GetAllAvailableAndroidSdks () // Check for the key the user gave us in the VS/addin options foreach (var root in roots) if (CheckRegistryKeyForExecutable (root, regKey, MDREG_ANDROID_SDK, wow, "platform-tools", Adb)) - yield return RegistryEx.GetValueString (root, regKey, MDREG_ANDROID_SDK, wow); + yield return RegistryEx.GetValueString (root, regKey, MDREG_ANDROID_SDK, wow) ?? ""; // Check for the key written by the Xamarin installer if (CheckRegistryKeyForExecutable (RegistryEx.CurrentUser, XAMARIN_ANDROID_INSTALLER_PATH, XAMARIN_ANDROID_INSTALLER_KEY, wow, "platform-tools", Adb)) - yield return RegistryEx.GetValueString (RegistryEx.CurrentUser, XAMARIN_ANDROID_INSTALLER_PATH, XAMARIN_ANDROID_INSTALLER_KEY, wow); + yield return RegistryEx.GetValueString (RegistryEx.CurrentUser, XAMARIN_ANDROID_INSTALLER_PATH, XAMARIN_ANDROID_INSTALLER_KEY, wow) ?? ""; // Check for the key written by the Android SDK installer foreach (var root in roots) if (CheckRegistryKeyForExecutable (root, ANDROID_INSTALLER_PATH, ANDROID_INSTALLER_KEY, wow, "platform-tools", Adb)) - yield return RegistryEx.GetValueString (root, ANDROID_INSTALLER_PATH, ANDROID_INSTALLER_KEY, wow); + yield return RegistryEx.GetValueString (root, ANDROID_INSTALLER_PATH, ANDROID_INSTALLER_KEY, wow) ?? ""; // Check some hardcoded paths for good measure var paths = new string [] { @@ -94,7 +94,7 @@ protected override IEnumerable GetAllAvailableAndroidSdks () Path.Combine (Environment.GetFolderPath (Environment.SpecialFolder.ProgramFilesX86), "Android", "android-sdk"), Path.Combine (Environment.GetFolderPath (Environment.SpecialFolder.ProgramFilesX86), "Android", "android-sdk-windows"), !string.IsNullOrEmpty (Environment.GetEnvironmentVariable ("ProgramW6432")) - ? Path.Combine (Environment.GetEnvironmentVariable ("ProgramW6432"), "Android", "android-sdk") + ? Path.Combine (Environment.GetEnvironmentVariable ("ProgramW6432") ?? "", "Android", "android-sdk") : Path.Combine (Environment.GetFolderPath (Environment.SpecialFolder.ProgramFiles), "Android", "android-sdk"), Path.Combine (Environment.GetFolderPath (Environment.SpecialFolder.LocalApplicationData), "Android", "android-sdk"), Path.Combine (Environment.GetFolderPath (Environment.SpecialFolder.CommonApplicationData), "Android", "android-sdk"), @@ -106,7 +106,7 @@ protected override IEnumerable GetAllAvailableAndroidSdks () yield return basePath; } - protected override string GetJavaSdkPath () + protected override string? GetJavaSdkPath () { var jdk = GetJdkInfos (Logger).FirstOrDefault (); return jdk?.HomePath; @@ -114,9 +114,9 @@ protected override string GetJavaSdkPath () internal static IEnumerable GetJdkInfos (Action logger) { - JdkInfo TryGetJdkInfo (string path, string locator) + JdkInfo? TryGetJdkInfo (string path, string locator) { - JdkInfo jdk = null; + JdkInfo? jdk = null; try { jdk = new JdkInfo (path, locator); } @@ -131,6 +131,7 @@ IEnumerable ToJdkInfos (IEnumerable paths, string locator) { return paths.Select (p => TryGetJdkInfo (p, locator)) .Where (jdk => jdk != null) + .Select(jdk => jdk!) .OrderByDescending (jdk => jdk, JdkInfoVersionComparer.Default); } @@ -161,7 +162,7 @@ private static IEnumerable GetPreferredJdkPaths () foreach (var root in roots) { if (CheckRegistryKeyForExecutable (root, regKey, MDREG_JAVA_SDK, wow, "bin", _JarSigner)) - yield return RegistryEx.GetValueString (root, regKey, MDREG_JAVA_SDK, wow); + yield return RegistryEx.GetValueString (root, regKey, MDREG_JAVA_SDK, wow) ?? ""; } } @@ -174,7 +175,7 @@ private static IEnumerable GetOpenJdkPaths () foreach (var wow in wows) { if (CheckRegistryKeyForExecutable (root, subKey, valueName, wow, "bin", _JarSigner)) - yield return RegistryEx.GetValueString (root, subKey, valueName, wow); + yield return RegistryEx.GetValueString (root, subKey, valueName, wow) ?? ""; } } @@ -196,7 +197,7 @@ private static IEnumerable GetKnownOpenJdkPaths () if (Directory.Exists (rootPath)) { foreach (var directoryName in Directory.EnumerateDirectories (rootPath, $"{JdkFolderNamePattern}*").ToList ()) { var versionString = directoryName.Replace ($"{rootPath}\\{JdkFolderNamePattern}", string.Empty); - if (Version.TryParse (versionString, out Version ver)) { + if (Version.TryParse (versionString, out Version? ver)) { paths.Add (new Tuple(directoryName, ver)); } } @@ -220,13 +221,13 @@ private static IEnumerable GetOracleJdkPaths () // No matter what the CurrentVersion is, look for 1.6 or 1.7 or 1.8 if (CheckRegistryKeyForExecutable (RegistryEx.LocalMachine, subkey + "\\" + "1.8", "JavaHome", wow64, "bin", _JarSigner)) - yield return RegistryEx.GetValueString (RegistryEx.LocalMachine, subkey + "\\" + "1.8", "JavaHome", wow64); + yield return RegistryEx.GetValueString (RegistryEx.LocalMachine, subkey + "\\" + "1.8", "JavaHome", wow64) ?? ""; if (CheckRegistryKeyForExecutable (RegistryEx.LocalMachine, subkey + "\\" + "1.7", "JavaHome", wow64, "bin", _JarSigner)) - yield return RegistryEx.GetValueString (RegistryEx.LocalMachine, subkey + "\\" + "1.7", "JavaHome", wow64); + yield return RegistryEx.GetValueString (RegistryEx.LocalMachine, subkey + "\\" + "1.7", "JavaHome", wow64) ?? ""; if (CheckRegistryKeyForExecutable (RegistryEx.LocalMachine, subkey + "\\" + "1.6", "JavaHome", wow64, "bin", _JarSigner)) - yield return RegistryEx.GetValueString (RegistryEx.LocalMachine, subkey + "\\" + "1.6", "JavaHome", wow64); + yield return RegistryEx.GetValueString (RegistryEx.LocalMachine, subkey + "\\" + "1.6", "JavaHome", wow64) ?? ""; } } } @@ -244,7 +245,7 @@ protected override IEnumerable GetAllAvailableAndroidNdks () var sdks = GetAllAvailableAndroidSdks().ToList(); if (!string.IsNullOrEmpty(AndroidSdkPath)) - sdks.Add(AndroidSdkPath); + sdks.Add (AndroidSdkPath!); foreach(var sdk in sdks.Distinct()) if (Directory.Exists(ndk = Path.Combine(sdk, "ndk-bundle"))) @@ -254,7 +255,7 @@ protected override IEnumerable GetAllAvailableAndroidNdks () // Check for the key the user gave us in the VS/addin options foreach (var root in roots) if (CheckRegistryKeyForExecutable (root, regKey, MDREG_ANDROID_NDK, wow, ".", NdkStack)) - yield return RegistryEx.GetValueString (root, regKey, MDREG_ANDROID_NDK, wow); + yield return RegistryEx.GetValueString (root, regKey, MDREG_ANDROID_NDK, wow) ?? ""; /* // Check for the key written by the Xamarin installer @@ -267,7 +268,7 @@ protected override IEnumerable GetAllAvailableAndroidNdks () var vs_default = Path.Combine (Environment.GetFolderPath (Environment.SpecialFolder.CommonApplicationData), "Microsoft", "AndroidNDK"); var vs_default32bit = Path.Combine (Environment.GetFolderPath (Environment.SpecialFolder.CommonApplicationData), "Microsoft", "AndroidNDK32"); var vs_2017_default = Path.Combine (Environment.GetFolderPath (Environment.SpecialFolder.CommonApplicationData), "Microsoft", "AndroidNDK64"); - var android_default = Path.Combine (OS.ProgramFilesX86, "Android"); + var android_default = Path.Combine (OS.ProgramFilesX86 ?? "", "Android"); var cdrive_default = @"C:\"; foreach (var basePath in new string [] {xamarin_private, android_default, vs_default, vs_default32bit, vs_2017_default, cdrive_default}) @@ -282,19 +283,19 @@ protected override string GetShortFormPath (string path) return KernelEx.GetShortPathName (path); } - public override void SetPreferredAndroidSdkPath (string path) + public override void SetPreferredAndroidSdkPath (string? path) { var regKey = GetMDRegistryKey (); RegistryEx.SetValueString (RegistryEx.CurrentUser, regKey, MDREG_ANDROID_SDK, path ?? "", RegistryEx.Wow64.Key32); } - public override void SetPreferredJavaSdkPath (string path) + public override void SetPreferredJavaSdkPath (string? path) { var regKey = GetMDRegistryKey (); RegistryEx.SetValueString (RegistryEx.CurrentUser, regKey, MDREG_JAVA_SDK, path ?? "", RegistryEx.Wow64.Key32); } - public override void SetPreferredAndroidNdkPath (string path) + public override void SetPreferredAndroidNdkPath (string? path) { var regKey = GetMDRegistryKey (); RegistryEx.SetValueString (RegistryEx.CurrentUser, regKey, MDREG_ANDROID_NDK, path ?? "", RegistryEx.Wow64.Key32); @@ -323,7 +324,7 @@ private static bool CheckRegistryKeyForExecutable (UIntPtr key, string subkey, s } #endregion - public override void Initialize (string androidSdkPath = null, string androidNdkPath = null, string javaSdkPath = null) + public override void Initialize (string? androidSdkPath = null, string? androidNdkPath = null, string? javaSdkPath = null) { base.Initialize (androidSdkPath, androidNdkPath, javaSdkPath); @@ -335,7 +336,7 @@ public override void Initialize (string androidSdkPath = null, string androidNdk var javaBinPath = this.JavaBinPath; if (!string.IsNullOrEmpty (javaBinPath)) { - var environmentPath = Environment.GetEnvironmentVariable ("PATH"); + var environmentPath = Environment.GetEnvironmentVariable ("PATH") ?? ""; if (!environmentPath.Contains (javaBinPath)) { var processPath = string.Concat (javaBinPath, Path.PathSeparator, environmentPath); Environment.SetEnvironmentVariable ("PATH", processPath); diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj index 02aec41ea22..81a7a742d1c 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj @@ -1,7 +1,10 @@ - netstandard2.0 + netstandard2.0;netcoreapp3.1 + 8.0 + enable + INTERNAL_NULLABLE_ATTRIBUTES true ..\..\product.snk Xamarin.Android.Tools @@ -13,12 +16,12 @@ Xamarin;Xamarin.Android - - ..\..\bin\Debug - + + + - - ..\..\bin\Release + + $(ToolOutputFullPath) diff --git a/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/Xamarin.Android.Tools.AndroidSdk-Tests.csproj b/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/Xamarin.Android.Tools.AndroidSdk-Tests.csproj index b2de219707f..2de72a8e1d2 100644 --- a/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/Xamarin.Android.Tools.AndroidSdk-Tests.csproj +++ b/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/Xamarin.Android.Tools.AndroidSdk-Tests.csproj @@ -1,17 +1,13 @@ - net461 + net461;netcoreapp3.1 false false - - ..\..\bin\TestDebug - - - - ..\..\bin\TestRelease + + $(TestOutputFullPath) From 7aad57ef200dc3affb985c4a45f0ef96cd4d2be6 Mon Sep 17 00:00:00 2001 From: Jonathan Pryor Date: Tue, 2 Jun 2020 12:12:17 -0400 Subject: [PATCH 078/308] Delete NuGet.Config Filenames that differ only in case are (1) painful, and (2) break things when using case-insensitive filesystems. Remove `NuGet.Config`. Aside: How did I *create* that file in the first place?! --- external/xamarin-android-tools/NuGet.Config | 11 ----------- 1 file changed, 11 deletions(-) delete mode 100644 external/xamarin-android-tools/NuGet.Config diff --git a/external/xamarin-android-tools/NuGet.Config b/external/xamarin-android-tools/NuGet.Config deleted file mode 100644 index 9eee256d93c..00000000000 --- a/external/xamarin-android-tools/NuGet.Config +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - \ No newline at end of file From fd4e508f2dc1bd101b0b1609da7c68d5aa034d29 Mon Sep 17 00:00:00 2001 From: Jonathan Pryor Date: Wed, 3 Jun 2020 20:53:12 -0400 Subject: [PATCH 079/308] [Xamarin.Android.Tools.AndroidSdk] Prefer JAVA_HOME (#86) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Context: https://github.com/xamarin/xamarin-android/pull/4567 `JdkInfo.GetKnownSystemJdkInfos()` returns a list of "known JDK locations", and the order of the returned paths was, basically, "locations that Xamarin/Microsoft controls come first." `AndroidSdkWindows.GetJdkInfos()` reads the Windows registry and is "controlled by" Visual Studio (unless someone is editing the Registry by hand…). If not on Windows, `GetConfiguredJdks()` reads `monodroid-config.xml` (managed by Visual Studio for Mac); failing that we probe some "well known Microsoft-controlled" directory locations… …and only failing *that* do we try the `$JAVA_HOME` environment variable, the ["de-facto" way][0] to tell software where Java is installed, and if `$JAVA_HOME` isn't set we further fallback to checking directories in `$PATH` and other mechanisms. The problem with this approach is that it isn't overridable, which is a usefully important feature if you want to *test new JDK versions*, as is the case in xamarin/xamarin-android#4567. The "obvious" way to "try out" a new JDK would be to export the `JAVA_HOME` environment variable to the location of the JDK to use, but *that won't work* because `JdkInfo.GetKnownSystemJdkInfos()` *explicitly prefers* locations that aren't easily controllable in a CI environment. Given that *existing convention* is for JDK installs to set the `JAVA_HOME` environment variable -- and thus `JAVA_HOME` may very well refer to a JDK which Xamarin.Android doesn't support -- we are leery of making `JAVA_HOME` the "primary" override. Instead, add support for a new `JI_JAVA_HOME` environment variable which, if set, is the *preferred* JDK to use within Xamarin.Android (unless otherwise overridden by e.g. `$(JavaSdkDirectory)`). This will allow CI to export the `JAVA_HOME` environment variable, allowing it to be preferred over others. Additionally, remove some `JAVA_HOME` "duplication" between `JdkInfo` and `AndroidSdkWindows`, so that things are easier to reason about. [0]: https://docs.oracle.com/cd/E19182-01/821-0917/inst_jdk_javahome_t/index.html --- .../Xamarin.Android.Tools.AndroidSdk/JdkInfo.cs | 11 ++++++----- .../Sdks/AndroidSdkWindows.cs | 15 ++------------- .../AndroidSdkInfoTests.cs | 15 +++++++++------ .../JdkInfoTests.cs | 17 +++++++++++++++++ 4 files changed, 34 insertions(+), 24 deletions(-) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/JdkInfo.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/JdkInfo.cs index e82886d27fb..6c4c8b89709 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/JdkInfo.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/JdkInfo.cs @@ -281,10 +281,11 @@ public static IEnumerable GetKnownSystemJdkInfos (Action GetWindowsJdks (Action logger) return AndroidSdkWindows.GetJdkInfos (logger); } - static IEnumerable GetJavaHomeEnvironmentJdks (Action logger) + static IEnumerable GetEnvironmentVariableJdks (string envVar, Action logger) { - var java_home = Environment.GetEnvironmentVariable ("JAVA_HOME"); + var java_home = Environment.GetEnvironmentVariable (envVar); if (string.IsNullOrEmpty (java_home)) yield break; - var jdk = TryGetJdkInfo (java_home, logger, "$JAVA_HOME"); + var jdk = TryGetJdkInfo (java_home, logger, $"${envVar}"); if (jdk != null) yield return jdk; } diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkWindows.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkWindows.cs index c36370359ed..8740798a66d 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkWindows.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkWindows.cs @@ -108,7 +108,7 @@ protected override IEnumerable GetAllAvailableAndroidSdks () protected override string? GetJavaSdkPath () { - var jdk = GetJdkInfos (Logger).FirstOrDefault (); + var jdk = JdkInfo.GetKnownSystemJdkInfos (Logger).FirstOrDefault (); return jdk?.HomePath; } @@ -139,18 +139,7 @@ IEnumerable ToJdkInfos (IEnumerable paths, string locator) .Concat (ToJdkInfos (GetOpenJdkPaths (), "OpenJDK")) .Concat (ToJdkInfos (GetKnownOpenJdkPaths (), "Well-known OpenJDK paths")) .Concat (ToJdkInfos (GetOracleJdkPaths (), "Oracle JDK")) - .Concat (ToJdkInfos (GetEnvironmentJdkPaths (), "Environment Variables")); - } - - private static IEnumerable GetEnvironmentJdkPaths () - { - var environment = new [] { "JAVA_HOME" }; - foreach (var key in environment) { - var value = Environment.GetEnvironmentVariable (key); - if (!string.IsNullOrEmpty (value)) { - yield return value; - } - } + ; } private static IEnumerable GetPreferredJdkPaths () diff --git a/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/AndroidSdkInfoTests.cs b/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/AndroidSdkInfoTests.cs index 262a2925aeb..a02ffd63b71 100644 --- a/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/AndroidSdkInfoTests.cs +++ b/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/AndroidSdkInfoTests.cs @@ -136,9 +136,13 @@ public void Constructor_SetValuesFromPath () } [Test] - [Ignore ("This test will only work locally if you rename/remove your Open JDK directory.")] - public void JdkDirectory_JavaHome () + public void JdkDirectory_JavaHome ([Values ("JI_JAVA_HOME", "JAVA_HOME")] string envVar) { + if (envVar.Equals ("JAVA_HOME", StringComparison.OrdinalIgnoreCase)) { + Assert.Ignore ("This test will only work locally if you rename/remove your Open JDK directory."); + return; + } + CreateSdks (out string root, out string jdk, out string ndk, out string sdk); JdkInfoTests.CreateFauxJdk (jdk, releaseVersion: "1.8.999", releaseBuildNumber: "9", javaVersion: "1.8.999-9"); @@ -150,16 +154,15 @@ public void JdkDirectory_JavaHome () string java_home = null; try { // We only set via JAVA_HOME - java_home = Environment.GetEnvironmentVariable ("JAVA_HOME", EnvironmentVariableTarget.Process); - Environment.SetEnvironmentVariable ("JAVA_HOME", jdk); + java_home = Environment.GetEnvironmentVariable (envVar, EnvironmentVariableTarget.Process); + Environment.SetEnvironmentVariable (envVar, jdk, EnvironmentVariableTarget.Process); var info = new AndroidSdkInfo (logger, androidSdkPath: sdk, androidNdkPath: ndk, javaSdkPath: ""); Assert.AreEqual (ndk, info.AndroidNdkPath, "AndroidNdkPath not preserved!"); Assert.AreEqual (sdk, info.AndroidSdkPath, "AndroidSdkPath not preserved!"); Assert.AreEqual (jdk, info.JavaSdkPath, "JavaSdkPath not preserved!"); } finally { - if (java_home != null) - Environment.SetEnvironmentVariable ("JAVA_HOME", java_home, EnvironmentVariableTarget.Process); + Environment.SetEnvironmentVariable (envVar, java_home, EnvironmentVariableTarget.Process); Directory.Delete (root, recursive: true); } } diff --git a/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/JdkInfoTests.cs b/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/JdkInfoTests.cs index 122deed28d8..594c6d0efa7 100644 --- a/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/JdkInfoTests.cs +++ b/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/JdkInfoTests.cs @@ -11,6 +11,23 @@ namespace Xamarin.Android.Tools.Tests [TestFixture] public class JdkInfoTests { + [Test] + public void GetKnownSystemJdkInfos_PrefersJiJavaHome () + { + var previous = Environment.GetEnvironmentVariable ("JI_JAVA_HOME", EnvironmentVariableTarget.Process); + try { + Environment.SetEnvironmentVariable ("JI_JAVA_HOME", FauxJdkDir, EnvironmentVariableTarget.Process); + + var defaultJdkDir = JdkInfo.GetKnownSystemJdkInfos () + .FirstOrDefault (); + Assert.IsNotNull (defaultJdkDir); + Assert.AreEqual (FauxJdkDir, defaultJdkDir.HomePath); + } + finally { + Environment.SetEnvironmentVariable ("JI_JAVA_HOME", previous, EnvironmentVariableTarget.Process); + } + } + [Test] public void Constructor_NullPath () { From 08f6a521a9fa2b6b41b3e8e12ae1a92ecf245439 Mon Sep 17 00:00:00 2001 From: Jonathan Pryor Date: Thu, 4 Jun 2020 11:50:14 -0400 Subject: [PATCH 080/308] [Xamarin.Android.Tools.AndroidSdk] Improve utility of JDK warnings (#87) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Context: https://github.com/xamarin/xamarin-android/pull/4567 Context: https://devdiv.visualstudio.com/DevDiv/_build/results?buildId=3781637&view=logs&j=aacb7678-d5ef-5d73-dd2c-3def32e966f3&t=36563657-e889-567e-4d1d-090ca934735a We're trying to get our CI to use JDK11 (xamarin/xamarin-android#4567), for which we added support for a `JI_JAVA_HOME` environment variable "override"; see fd4e508f. However, even with `JI_JAVA_HOME` set, `nuget restore` still fails: …\Xamarin.Android.Tooling.targets(65,5): warning XA5300: Not a valid JDK directory: `C:\Users\dlab14\android-toolchain\jdk`; via locator: $JI_JAVA_HOME The question is *why* it's an invalid JDK directory. (It certainly looks like it should be valid!) While we *do* print out the contents of `e.ToString()`, this is only written as "verbose" output, which isn't captured by `nuget restore`. As such, we have no idea why the JDK directory is not considered valid. Update the warning message that Java.Interop emits when checking JDK locations so that it includes `Exception.Message`, e.g. The directory `/invalid`, via locator `$JI_JAVA_HOME`, is not a valid JDK directory: Not a directory This will hopefully provide enough detail to let us know why it's failing. --- .../src/Xamarin.Android.Tools.AndroidSdk/JdkInfo.cs | 2 +- .../Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkWindows.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/JdkInfo.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/JdkInfo.cs index 6c4c8b89709..4ea56bba39b 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/JdkInfo.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/JdkInfo.cs @@ -340,7 +340,7 @@ static IEnumerable GetMacOSMicrosoftJdkPaths () jdk = new JdkInfo (path, locator); } catch (Exception e) { - logger (TraceLevel.Warning, $"Not a valid JDK directory: `{path}`; via locator: {locator}"); + logger (TraceLevel.Warning, $"The directory `{path}`, via locator `{locator}`, is not a valid JDK directory: {e.Message}"); logger (TraceLevel.Verbose, e.ToString ()); } return jdk; diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkWindows.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkWindows.cs index 8740798a66d..bf026406405 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkWindows.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkWindows.cs @@ -121,7 +121,7 @@ internal static IEnumerable GetJdkInfos (Action log jdk = new JdkInfo (path, locator); } catch (Exception e) { - logger (TraceLevel.Warning, $"Not a valid JDK directory: `{path}`; via category: {locator}"); + logger (TraceLevel.Warning, $"The directory `{path}`, via locator `{locator}`, is not a valid JDK directory: {e.Message}"); logger (TraceLevel.Verbose, e.ToString ()); } return jdk; From 2c41c911d8703f0041f4bf41fd91ef497f37f634 Mon Sep 17 00:00:00 2001 From: Jonathan Pryor Date: Thu, 4 Jun 2020 13:27:53 -0400 Subject: [PATCH 081/308] [Xamarin.Android.Tools.AndroidSdk] JdkInfo + JDK11 + Windows (#88) Context: https://github.com/xamarin/xamarin-android/pull/4567 Commit 461a2702 added support for JetBrains OpenJDK 11 detection on macOS and Linux. Lacking was *Windows* support for JetBrains OpenJDK 11, because of course it has to be different. In particular, OpenJDK 11 *moves the `jvm` library* for Windows. We checked for it in `{HomePath}\jre\**\jvm.dll` or `{HomePath}\lib\**\jvm.dll`, but neither of those exist. Instead, OpenJDK 11 has `jvm.dll` in `{HomePath}\bin\server\jvm.dll`, which is also the only `.dll` file in the `.tar.gz` which exports the symbol `JNI_CreateJavaVM`. Update `JdkInfo` so that it looks for `{HomePath}\bin\server\jvm.dll` to populate `JdkInfo.JdkJvmPath`. --- .../JdkInfo.cs | 45 ++++++++++--------- 1 file changed, 23 insertions(+), 22 deletions(-) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/JdkInfo.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/JdkInfo.cs index 4ea56bba39b..8ff1c4a1173 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/JdkInfo.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/JdkInfo.cs @@ -14,10 +14,6 @@ namespace Xamarin.Android.Tools { public class JdkInfo { - static readonly string[] JdkLibraryTopDirs = { - "jre", - "lib", - }; public string HomePath {get;} @@ -58,27 +54,14 @@ public JdkInfo (string homePath) JavaPath = ProcessUtils.FindExecutablesInDirectory (binPath, "java").FirstOrDefault (); JavacPath = ProcessUtils.FindExecutablesInDirectory (binPath, "javac").FirstOrDefault (); - string? topDir = null; - foreach (string dir in JdkLibraryTopDirs) { - topDir = Path.Combine (HomePath, dir); - if (!Directory.Exists (topDir)) { - topDir = null; - continue; - } - break; - } - - if (String.IsNullOrEmpty (topDir)) - topDir = Path.Combine (HomePath, JdkLibraryTopDirs [0]); - - JdkJvmPath = OS.IsMac - ? FindLibrariesInDirectory (topDir!, "jli").FirstOrDefault () - : FindLibrariesInDirectory (topDir!, "jvm").FirstOrDefault (); + string? jdkJvmPath = GetJdkJvmPath (); ValidateFile ("jar", JarPath); ValidateFile ("java", JavaPath); ValidateFile ("javac", JavacPath); - ValidateFile ("jvm", JdkJvmPath); + ValidateFile ("jvm", jdkJvmPath); + + JdkJvmPath = jdkJvmPath!; var includes = new List (); var jdkInclude = Path.Combine (HomePath, "include"); @@ -132,6 +115,24 @@ public bool GetJavaSettingsPropertyValue (string key, [NotNullWhen (true)] out s return false; } + string? GetJdkJvmPath () + { + string jreDir = Path.Combine (HomePath, "jre"); + string libDir = Path.Combine (HomePath, "lib"); + + if (OS.IsMac) { + return FindLibrariesInDirectory (jreDir, "jli").FirstOrDefault () ?? + FindLibrariesInDirectory (libDir, "jli").FirstOrDefault (); + } + if (OS.IsWindows) { + string binServerDir = Path.Combine (HomePath, "bin", "server"); + return FindLibrariesInDirectory (jreDir, "jvm").FirstOrDefault () ?? + FindLibrariesInDirectory (binServerDir, "jvm").FirstOrDefault (); + } + return FindLibrariesInDirectory (jreDir, "jvm").FirstOrDefault () ?? + FindLibrariesInDirectory (libDir, "jvm").FirstOrDefault (); + } + static IEnumerable FindLibrariesInDirectory (string dir, string libraryName) { if (!Directory.Exists (dir)) @@ -140,7 +141,7 @@ static IEnumerable FindLibrariesInDirectory (string dir, string libraryN return Directory.EnumerateFiles (dir, library, SearchOption.AllDirectories); } - void ValidateFile (string name, string path) + void ValidateFile (string name, string? path) { if (path == null || !File.Exists (path)) throw new ArgumentException ($"Could not find required file `{name}` within `{HomePath}`; is this a valid JDK?", "homePath"); From eca421265e19d4c2ebef3786dca1a38b3ab9079f Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Tue, 30 Jun 2020 14:09:41 -0500 Subject: [PATCH 082/308] Return a default for unknown API levels (#90) Context: https://github.com/xamarin/xamarin-android/pull/4873 Context: https://github.com/xamarin/xamarin-android/pull/4873#issuecomment-651331337 When xamarin-android is built to support .NET 5, the .NET 5 install directory contains a single `AndroidApiInfo.xml` file: 30 30 R v11.0 True `AndroidVersions`, meanwhile, is setup to read a *set* of `AndroidApiInfo.xml` files (07ca557c) to "dynamically" compute mappings between possible `$(TargetFrameworkVersion)` values, API-levels, and IDs for those API levels. When there is only one such file, if you call: int? apiLevel = androidVersions.GetApiLevelFromId (29); then `apiLevel` will always be `null`, because (at present) `AndroidVersions.KnownVersions` doesn't know about API-29 or API-30. We *could* update `AndroidVersions.KnownVersions` to contain entries for API-29 & API-30, but doing so means that we reintroduce the scenario that `AndroidVersions` was added to help defend against: a need/requirement to update `AndroidVersions.KnownVersions` *every time* a new API level was released, lest ~everything break. We *don't* want to require `AndroidVersions.KnownVersions` updates. To allow a .NET 5-like environment to work *without* updating `KnownVersions`, update the various `GetApiLevelFromId()` methods to return the incoming API level as a fallback. If `"29"` comes in, then `29` can be returned and assumed to be a valid API level. --- .../Xamarin.Android.Tools.AndroidSdk/AndroidVersions.cs | 9 ++++++--- .../AndroidVersionsTests.cs | 6 +++--- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidVersions.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidVersions.cs index 98962884726..e240abae277 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidVersions.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidVersions.cs @@ -92,7 +92,8 @@ static bool MatchesFrameworkVersion (AndroidVersion version, string frameworkVer public int? GetApiLevelFromId (string id) { return installedVersions.FirstOrDefault (v => MatchesId (v, id))?.ApiLevel ?? - KnownVersions.FirstOrDefault (v => MatchesId (v, id))?.ApiLevel; + KnownVersions.FirstOrDefault (v => MatchesId (v, id))?.ApiLevel ?? + (int.TryParse (id, out int apiLevel) ? apiLevel : default (int?)); } static bool MatchesId (AndroidVersion version, string id) @@ -105,7 +106,8 @@ static bool MatchesId (AndroidVersion version, string id) public string? GetIdFromApiLevel (int apiLevel) { return installedVersions.FirstOrDefault (v => v.ApiLevel == apiLevel)?.Id ?? - KnownVersions.FirstOrDefault (v => v.ApiLevel == apiLevel)?.Id; + KnownVersions.FirstOrDefault (v => v.ApiLevel == apiLevel)?.Id ?? + apiLevel.ToString (); } // Sometimes, e.g. when new API levels are introduced, the "API level" is a letter, not a number, @@ -115,7 +117,8 @@ static bool MatchesId (AndroidVersion version, string id) if (int.TryParse (apiLevel, out var platform)) return GetIdFromApiLevel (platform); return installedVersions.FirstOrDefault (v => MatchesId (v, apiLevel))?.Id ?? - KnownVersions.FirstOrDefault (v => MatchesId (v, apiLevel))?.Id; + KnownVersions.FirstOrDefault (v => MatchesId (v, apiLevel))?.Id ?? + apiLevel; } public string? GetIdFromFrameworkVersion (string frameworkVersion) diff --git a/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/AndroidVersionsTests.cs b/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/AndroidVersionsTests.cs index a3cd293cb79..1ebe8f2c365 100644 --- a/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/AndroidVersionsTests.cs +++ b/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/AndroidVersionsTests.cs @@ -202,9 +202,9 @@ public void GetIdFromApiLevel () Assert.AreEqual ("II", versions.GetIdFromApiLevel ("14")); Assert.AreEqual ("II", versions.GetIdFromApiLevel ("II")); - Assert.AreEqual (null, versions.GetIdFromApiLevel (-1)); - Assert.AreEqual (null, versions.GetIdFromApiLevel ("-1")); - Assert.AreEqual (null, versions.GetIdFromApiLevel ("D")); + Assert.AreEqual ("-1", versions.GetIdFromApiLevel (-1)); + Assert.AreEqual ("-1", versions.GetIdFromApiLevel ("-1")); + Assert.AreEqual ("D", versions.GetIdFromApiLevel ("D")); // via KnownVersions Assert.AreEqual ("11", versions.GetIdFromApiLevel (11)); From f86bc5776013815f51dda31eaaf035db98753c00 Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Wed, 1 Jul 2020 13:41:37 -0500 Subject: [PATCH 083/308] [build] fail macOS build if tests fail (#94) Context: https://docs.microsoft.com/azure/devops/pipelines/tasks/test/publish-test-results#yaml-snippet Context: https://dev.azure.com/xamarin/public/_build/results?buildId=21614&view=ms.vss-test-web.build-test-results-tab Currently, if a test fails on macOS the build will still be green. We need to set `failTaskOnFailedTests` on the `PublishTestResults` step. I also filled out `testRunTitle`, to fix the display name shown on test results. --- external/xamarin-android-tools/azure-pipelines.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/external/xamarin-android-tools/azure-pipelines.yaml b/external/xamarin-android-tools/azure-pipelines.yaml index 59a3e7bceb4..6b2a1470dbf 100644 --- a/external/xamarin-android-tools/azure-pipelines.yaml +++ b/external/xamarin-android-tools/azure-pipelines.yaml @@ -39,6 +39,7 @@ jobs: displayName: 'Run Tests' inputs: testAssemblyVer2: 'bin\TestDebug\*-Tests.dll' + testRunTitle: windows-tests - powershell: | $hashOfLastVersionChange = & "git" "log" "--follow" "-1" "--pretty=%H" "nuget.version" $commitsSinceVersionChange = & "git" "rev-list" "--count" "$hashOfLastVersionChange..HEAD" @@ -80,3 +81,5 @@ jobs: inputs: testResultsFormat: NUnit testResultsFiles: TestResult*.xml + testRunTitle: mac-tests + failTaskOnFailedTests: true From 05519991bb054e6cdadaa20f2c10d866946b10e1 Mon Sep 17 00:00:00 2001 From: Dean Ellis Date: Wed, 1 Jul 2020 20:25:26 +0100 Subject: [PATCH 084/308] [Xamarin.Android.Tools.AndroidSdk] Default SDK component versions (#93) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Context: https://issuetracker.google.com/issues/150189789 Context: https://github.com/xamarin/androidtools/commit/3f51412d5f213c0267fc597623f3430437d6452e We have discovered an issue when installing Visual Studio for Mac on macOS: certain versions of the installer will install the *latest* Android SDK Build-tools package, currently r30.0.0. This is a problem because recent versions of Xamarin.Android will use `apksigner` to sign `Mono.Android.Platform.ApiLevel_*.apk` files, so that they can be installed on API-30 targets -- API-30 targets no longer like `jarsigner`-signed `.apk` files -- but the `apksigner` included in the Build-tools r30 package requires JDK 9. Visual Studio for Mac is not ready to migrate to JDK >= 9. Consequently, on such installation environments, the Xamarin.Android shared runtime cannot be created, as `apksigner` won't run: Exception in thread "main" java.lang.UnsupportedClassVersionError: com/android/apksigner/ApkSignerTool has been compiled by a more recent version of the Java Runtime (class file version 53.0), this version of the Java Runtime only recognizes class file versions up to 52.0 What we need is a way for the macOS installer to share the same [default Android SDK component versions as Xamarin.Android itself][0]. This *could* plausibly be done by adding a git submodule reference from the xamarin/xamarin-android repo to the macOS installer, but this is undesirable. Instead, both the the xamarin-android repo and the installer repo's have existing git submodules graphs which include the xamarin/xamarin-android-tools repo. Add a new `Xamarin.Android.Tools.Versions.props` file, which contains the default Android SDK component versions, as an MSBuild project file: 29.0.2 … With this file in place, we can update the macOS installer to use `Xamarin.Android.Tools.Versions.props` as the "source of truth" for which versions to install. In particular, we'll set the default Build-tools version to 29.0.2, *not* 30.0.0, thus avoiding the problem with `apksigner` invocations. In the future, when `Xamarin.Android.Tools.Versions.props` is changed *all* repos referencing xamarin/xamarin-android-tools will need to be updated, so that everything can be on the same "page". [0]: https://github.com/xamarin/xamarin-android/blob/5f78740d74e19b235aad62c8ea8e314c9c78ddce/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.props.in#L18-L21 --- .../Xamarin.Android.Tools.AndroidSdk.csproj | 5 +++++ .../Xamarin.Android.Tools.Versions.props | 20 +++++++++++++++++++ 2 files changed, 25 insertions(+) create mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.Versions.props diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj index 81a7a742d1c..bae6585883d 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj @@ -37,4 +37,9 @@ + + + PreserveNewest + + diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.Versions.props b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.Versions.props new file mode 100644 index 00000000000..9c016e16c04 --- /dev/null +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.Versions.props @@ -0,0 +1,20 @@ + + + + + 29.0.2 + 1.0 + 29.0.5 + 26.1.1 + + android-29 + 16.1 + + \ No newline at end of file From f3ebd96928b06a67abcf950d62bbaa53fc136275 Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Wed, 8 Jul 2020 11:31:16 -0500 Subject: [PATCH 085/308] [Xamarin.Android.Tools.AndroidSdk] Add API-29, API-30 to KnownVersions (#89) --- .../src/Xamarin.Android.Tools.AndroidSdk/AndroidVersions.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidVersions.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidVersions.cs index e240abae277..59ac73f4199 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidVersions.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidVersions.cs @@ -176,6 +176,12 @@ static bool MatchesId (AndroidVersion version, string id) new AndroidVersion (28, "9.0", "Pie") { AlternateIds = new[]{ "P" }, }, + new AndroidVersion (29, "10.0", "Android10") { + AlternateIds = new[]{ "Q" }, + }, + new AndroidVersion (30, "11.0") { + AlternateIds = new[]{ "R" }, + }, }; } From 644054c6c111612c56233d2876542bd211943aa4 Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Tue, 14 Jul 2020 15:33:27 -0500 Subject: [PATCH 086/308] Fix sort ordering for ndk-bundle, add macOS support (#91) Fixes: https://github.com/xamarin/xamarin-android-tools/issues/92 Context: https://github.com/xamarin/xamarin-android-tools/pull/90#issuecomment-651375196 The PR builds for #90 encountered an "unrelated test failure" in `AndroidSdkInfoTests.Ndk_PathInSdk()` on Windows, because Windows is non-deterministic: the test asserts that given an Android SDK directory `androidSdk` which contains the file `{androidSdk}\ndk-bundle\ndk-stack.cmd`, then this: var info = new AndroidSdkInfo (logger: null, androidSdkPath: androidSdk); will have `info.AndroidNdkPath`==`{androidSdk}\ndk-bundle`. Instead, this test would occasionally fail on CI: Ndk_PathInSdk AndroidNdkPath not found inside sdk! Expected string length 71 but was 53. Strings differ at index 3. Expected: "C:\Users\VssAdministrator\AppData\Local\Temp\tmpAE78.tmp\sdk\..." But was: "C:\Program Files (x86)\Android\android-sdk\ndk-bundle" Here, the "preferred"/system-wide NDK is being chosen over the `{androidSdk}\ndk-bundle` directory that the unit test created. The wrong directory was chosen for two reasons: 1. `AndroidSdkBase.Initialize()` would use `PreferedAndroidNdkPath` when `androidNdkPath` was null, *first*, before we checked `{androidSdk}\ndk-bundle`. 2. If `PreferedAndroidNdkPath` happened to be null, then `AndroidSdkBase.Initialize()` would try to use `AllAndroidNdks.FirstOrDefault()` as a default value, also before checking `{androidSdk}\ndk-bundle`. The problem here is that the `AllAndrdoidNdks` property uses [`Enumerable.Distinct()`][0], which returns an *unordered* list of directories. That the test ever worked at all is a minor miracle. Additionally, the support for `{androidSdk}\ndk-bundle` was Windows- specific; it didn't run on macOS. Update `AndroidSdkInfo` so that `{androidSdk}/ndk-bundle` is supported on macOS, and that `{androidSdk}/ndk-bundle` is *preferred* when the `androidNdkPath` parameter is `null`, *before* checking any other plausible default locations. This allows the `AndroidSdkInfoTests.Ndk_PathInSdk()` test to run everywhere, and work reliably. [0]: https://docs.microsoft.com/en-us/dotnet/api/system.linq.enumerable.distinct?view=netcore-3.1 --- .../Sdks/AndroidSdkBase.cs | 87 +++++++++++++++---- .../Sdks/AndroidSdkUnix.cs | 28 ++---- .../Sdks/AndroidSdkWindows.cs | 26 ++---- .../AndroidSdkInfoTests.cs | 6 +- 4 files changed, 82 insertions(+), 65 deletions(-) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkBase.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkBase.cs index 1b10df18d04..820c81cb111 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkBase.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkBase.cs @@ -10,22 +10,21 @@ namespace Xamarin.Android.Tools abstract class AndroidSdkBase { string[]? allAndroidSdks; - string[]? allAndroidNdks; public string[] AllAndroidSdks { get { - if (allAndroidSdks == null) - allAndroidSdks = GetAllAvailableAndroidSdks ().Distinct ().ToArray (); + if (allAndroidSdks == null) { + var dirs = new List (); + dirs.Add (AndroidSdkPath); + dirs.AddRange (GetAllAvailableAndroidSdks ()); + allAndroidSdks = dirs.Where (d => ValidateAndroidSdkLocation (d)) + .Select (d => d!) + .Distinct () + .ToArray (); + } return allAndroidSdks; } } - public string[] AllAndroidNdks { - get { - if (allAndroidNdks == null) - allAndroidNdks = GetAllAvailableAndroidNdks ().Distinct ().ToArray (); - return allAndroidNdks; - } - } public readonly Action Logger; @@ -57,13 +56,10 @@ public AndroidSdkBase (Action logger) public virtual void Initialize (string? androidSdkPath = null, string? androidNdkPath = null, string? javaSdkPath = null) { - androidSdkPath = androidSdkPath ?? PreferedAndroidSdkPath; - androidNdkPath = androidNdkPath ?? PreferedAndroidNdkPath; - javaSdkPath = javaSdkPath ?? PreferedJavaSdkPath; + AndroidSdkPath = GetValidPath (ValidateAndroidSdkLocation, androidSdkPath, () => PreferedAndroidSdkPath, () => GetAllAvailableAndroidSdks ()); + JavaSdkPath = GetValidPath (ValidateJavaSdkLocation, javaSdkPath, () => PreferedJavaSdkPath, () => GetJavaSdkPaths ()); - AndroidSdkPath = ValidateAndroidSdkLocation (androidSdkPath) ? androidSdkPath : AllAndroidSdks.FirstOrDefault (); - AndroidNdkPath = ValidateAndroidNdkLocation (androidNdkPath) ? androidNdkPath : AllAndroidNdks.FirstOrDefault (); - JavaSdkPath = ValidateJavaSdkLocation (javaSdkPath) ? javaSdkPath : GetJavaSdkPath (); + AndroidNdkPath = GetValidNdkPath (androidNdkPath); if (!string.IsNullOrEmpty (JavaSdkPath)) { JavaBinPath = Path.Combine (JavaSdkPath, "bin"); @@ -93,11 +89,60 @@ public virtual void Initialize (string? androidSdkPath = null, string? androidNd NdkStack = GetExecutablePath (AndroidNdkPath, NdkStack); } + static string? GetValidPath (Func pathValidator, string? ctorParam, Func getPreferredPath, Func> getAllPaths) + { + if (pathValidator (ctorParam)) + return ctorParam; + ctorParam = getPreferredPath (); + if (pathValidator (ctorParam)) + return ctorParam; + foreach (var path in getAllPaths ()) { + if (pathValidator (path)) + return path; + } + return null; + } + + string? GetValidNdkPath (string? ctorParam) + { + if (ValidateAndroidNdkLocation (ctorParam)) + return ctorParam; + if (AndroidSdkPath != null) { + string bundle = Path.Combine (AndroidSdkPath, "ndk-bundle"); + if (Directory.Exists (bundle) && ValidateAndroidNdkLocation (bundle)) + return bundle; + } + ctorParam = PreferedAndroidNdkPath; + if (ValidateAndroidNdkLocation (ctorParam)) + return ctorParam; + foreach (var path in GetAllAvailableAndroidNdks ()) { + if (ValidateAndroidNdkLocation (path)) + return path; + } + return null; + } + protected abstract IEnumerable GetAllAvailableAndroidSdks (); - protected abstract IEnumerable GetAllAvailableAndroidNdks (); - protected abstract string? GetJavaSdkPath (); protected abstract string GetShortFormPath (string path); + protected virtual IEnumerable GetAllAvailableAndroidNdks () + { + // Look in PATH + foreach (var ndkStack in ProcessUtils.FindExecutablesInPath (NdkStack)) { + var ndkDir = Path.GetDirectoryName (ndkStack); + if (ndkDir == null) + continue; + yield return ndkDir; + } + + // Check for the "ndk-bundle" directory inside other SDK directories + foreach (var sdk in GetAllAvailableAndroidSdks ()) { + if (sdk == AndroidSdkPath) + continue; + yield return Path.Combine (sdk, "ndk-bundle"); + } + } + public abstract void SetPreferredAndroidSdkPath (string? path); public abstract void SetPreferredJavaSdkPath (string? path); public abstract void SetPreferredAndroidNdkPath (string? path); @@ -108,6 +153,12 @@ public string NdkHostPlatform { get { return IsNdk64Bit ? NdkHostPlatform64Bit : NdkHostPlatform32Bit; } } + IEnumerable GetJavaSdkPaths () + { + return JdkInfo.GetKnownSystemJdkInfos (Logger) + .Select (jdk => jdk.HomePath); + } + /// /// Checks that a value is the location of an Android SDK. /// diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkUnix.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkUnix.cs index 12bd5360b21..34836f3d008 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkUnix.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkUnix.cs @@ -98,33 +98,15 @@ protected override IEnumerable GetAllAvailableAndroidSdks () // Strip off "platform-tools" var dir = Path.GetDirectoryName (path); - if (ValidateAndroidSdkLocation (dir)) - yield return dir; + if (dir == null) + continue; + + yield return dir; } // Check some hardcoded paths for good measure var macSdkPath = Path.Combine (Environment.GetFolderPath (Environment.SpecialFolder.Personal), "Library", "Android", "sdk"); - if (ValidateAndroidSdkLocation (macSdkPath)) - yield return macSdkPath; - } - - protected override string? GetJavaSdkPath () - { - return JdkInfo.GetKnownSystemJdkInfos (Logger).FirstOrDefault ()?.HomePath; - } - - protected override IEnumerable GetAllAvailableAndroidNdks () - { - var preferedNdkPath = PreferedAndroidNdkPath; - if (!string.IsNullOrEmpty (preferedNdkPath)) - yield return preferedNdkPath!; - - // Look in PATH - foreach (var ndkStack in ProcessUtils.FindExecutablesInPath (NdkStack)) { - var ndkDir = Path.GetDirectoryName (ndkStack); - if (ValidateAndroidNdkLocation (ndkDir)) - yield return ndkDir; - } + yield return macSdkPath; } protected override string GetShortFormPath (string path) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkWindows.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkWindows.cs index bf026406405..59b20824103 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkWindows.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkWindows.cs @@ -102,14 +102,7 @@ protected override IEnumerable GetAllAvailableAndroidSdks () }; foreach (var basePath in paths) if (Directory.Exists (basePath)) - if (ValidateAndroidSdkLocation (basePath)) - yield return basePath; - } - - protected override string? GetJavaSdkPath () - { - var jdk = JdkInfo.GetKnownSystemJdkInfos (Logger).FirstOrDefault (); - return jdk?.HomePath; + yield return basePath; } internal static IEnumerable GetJdkInfos (Action logger) @@ -223,24 +216,13 @@ private static IEnumerable GetOracleJdkPaths () protected override IEnumerable GetAllAvailableAndroidNdks () { + var roots = new[] { RegistryEx.CurrentUser, RegistryEx.LocalMachine }; var wow = RegistryEx.Wow64.Key32; var regKey = GetMDRegistryKey (); Logger (TraceLevel.Info, "Looking for Android NDK..."); - // Check for the "ndk-bundle" directory inside the SDK directories - string ndk; - - var sdks = GetAllAvailableAndroidSdks().ToList(); - if (!string.IsNullOrEmpty(AndroidSdkPath)) - sdks.Add (AndroidSdkPath!); - - foreach(var sdk in sdks.Distinct()) - if (Directory.Exists(ndk = Path.Combine(sdk, "ndk-bundle"))) - if (ValidateAndroidNdkLocation(ndk)) - yield return ndk; - // Check for the key the user gave us in the VS/addin options foreach (var root in roots) if (CheckRegistryKeyForExecutable (root, regKey, MDREG_ANDROID_NDK, wow, ".", NdkStack)) @@ -265,6 +247,10 @@ protected override IEnumerable GetAllAvailableAndroidNdks () foreach (var dir in Directory.GetDirectories (basePath, "android-ndk-r*")) if (ValidateAndroidNdkLocation (dir)) yield return dir; + + foreach (var dir in base.GetAllAvailableAndroidNdks ()) { + yield return dir; + } } protected override string GetShortFormPath (string path) diff --git a/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/AndroidSdkInfoTests.cs b/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/AndroidSdkInfoTests.cs index a02ffd63b71..28aad1488fa 100644 --- a/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/AndroidSdkInfoTests.cs +++ b/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/AndroidSdkInfoTests.cs @@ -67,9 +67,6 @@ public void Constructor_Paths () [Test] public void Ndk_PathInSdk() { - if (!OS.IsWindows) - Assert.Ignore("This only works in Windows"); - CreateSdks(out string root, out string jdk, out string ndk, out string sdk); var logs = new StringWriter(); @@ -79,10 +76,11 @@ public void Ndk_PathInSdk() try { + var extension = OS.IsWindows ? ".cmd" : ""; var ndkPath = Path.Combine(sdk, "ndk-bundle"); Directory.CreateDirectory(ndkPath); Directory.CreateDirectory(Path.Combine(ndkPath, "toolchains")); - File.WriteAllText(Path.Combine(ndkPath, "ndk-stack.cmd"), ""); + File.WriteAllText(Path.Combine(ndkPath, $"ndk-stack{extension}"), ""); var info = new AndroidSdkInfo(logger, androidSdkPath: sdk, androidNdkPath: null, javaSdkPath: jdk); From b393aef11489d5b6459c1880ee5b2daaefdf08b5 Mon Sep 17 00:00:00 2001 From: Jonathan Pobst Date: Fri, 25 Sep 2020 12:05:17 -0500 Subject: [PATCH 087/308] [Xamarin.Android.Tools.AndroidSdk] Fix a few nullability warnings (#97) Fixes a few Nullable Reference Type (NRT) warnings in `JdkInfoVersionComparer`. There are also some NRT warnings in `EqualityComparer`, but this class is `private` and not used anywhere; remove it instead. --- .../AndroidVersions.cs | 22 ------------------- .../JdkInfo.cs | 4 ++-- 2 files changed, 2 insertions(+), 24 deletions(-) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidVersions.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidVersions.cs index 59ac73f4199..b258127d28d 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidVersions.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidVersions.cs @@ -184,26 +184,4 @@ static bool MatchesId (AndroidVersion version, string id) }, }; } - - class EqualityComparer : IEqualityComparer - { - Func equals; - Func getHashCode; - - public EqualityComparer (Func equals, Func? getHashCode = null) - { - this.equals = equals; - this.getHashCode = getHashCode ?? (v => v?.GetHashCode () ?? 0); - } - - public bool Equals (T x, T y) - { - return equals (x, y); - } - - public int GetHashCode (T obj) - { - return getHashCode (obj); - } - } } diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/JdkInfo.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/JdkInfo.cs index 8ff1c4a1173..9a72a799459 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/JdkInfo.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/JdkInfo.cs @@ -492,9 +492,9 @@ class JdkInfoVersionComparer : IComparer { public static readonly IComparer Default = new JdkInfoVersionComparer (); - public int Compare (JdkInfo x, JdkInfo y) + public int Compare ([AllowNull]JdkInfo x, [AllowNull]JdkInfo y) { - if (x.Version != null && y.Version != null) + if (x?.Version != null && y?.Version != null) return x.Version.CompareTo (y.Version); return 0; } From d392eead2b146fb300be10e203cd2d2573525288 Mon Sep 17 00:00:00 2001 From: moljac Date: Sat, 3 Oct 2020 17:31:06 +0200 Subject: [PATCH 088/308] [Xamarin.Android.Tools.AndroidSdk] Error & Warning Localization (#96) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Context: https://dev.azure.com/devdiv/DevDiv/_workitems/edit/1009374/ Context: https://github.com/xamarin/xamarin-android/commit/0342fe5698b86e21e36c924732ff135b9a87e4af Localize error and warning messages produced by `Xamarin.Android.Tools.AndroidSdk.dll`. We will be following the [.NET Resource Localization pattern][0] and generating satellite assemblies using [`.resx` files][1], in particular `src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.resx`. `Resources.resx` is an XML file, and will contain `/root/data` elements in which `//data/@name` will be a semantically meaningful shorthand for the actual error message, followed by a `_`-separated list of "parameter names" for the message, if the message has any format string placeholders. The `//data/value` will be the error or warning message: An exception occurred while reading configuration file '{0}'. Exception: {1} {0} - The path of the file being read. {1} - The exception message of the associated exception. An optional `//data/comment` element may be provided to describe the meaning within the `//data/value` element to translators. When using Visual Studio or Visual Studio for Mac, changes to `Resrouces.resx` will cause `Resources.Designer.cs` to be updated: namespace Xamarin.Android.Tools.AndroidSdk.Properties { internal partial class Resources { internal static string InvalidMonodroidConfigFile_path_message { get => … } } } The `Resources` members should be used to obtain all strings for use in `logger()` calls: logger (TraceLevel.Error, string.Format (Resources.InvalidMonodroidConfigFile_path_message, file, ex.Message)); When an MSBuild error or warning code is used with more than one output string, then a semantically meaningful suffix should be used to distinguish between the two. Note that this infrastructure does not interoperate with C#6 string interpolation. Any error or warning messages currently using C#6 string interpolation will need to use .NET 1.0-style format strings. Our translation team doesn't work directly with `.resx` files. Instead, the translation team works with [XLIFF files][2]. `Resources.resx` is converted into a set of `src/Xamarin.Android.Build.Tasks/Properties/xlf/Resources.*.xlf` files via `XliffTasks.targets` from the [dotnet/xliff-tasks][3] repo. The `Resources.*.xlf` files should be automatically updated whenever `Resources.resx` is updated. [0]: https://docs.microsoft.com/dotnet/framework/resources/index [1]: https://docs.microsoft.com/dotnet/framework/resources/creating-resource-files-for-desktop-apps#resources-in-resx-files [2]: http://docs.oasis-open.org/xliff/v1.2/os/xliff-core.html [3]: https://github.com/dotnet/xliff-tasks --- external/xamarin-android-tools/.gitattributes | 3 + .../azure-pipelines.yaml | 2 + .../JdkInfo.cs | 4 +- .../ProcessUtils.cs | 5 +- .../Properties/Resources.Designer.cs | 81 +++++++++++ .../Properties/Resources.resx | 133 ++++++++++++++++++ .../Properties/xlf/Resources.cs.xlf | 22 +++ .../Properties/xlf/Resources.de.xlf | 22 +++ .../Properties/xlf/Resources.es.xlf | 22 +++ .../Properties/xlf/Resources.fr.xlf | 22 +++ .../Properties/xlf/Resources.it.xlf | 22 +++ .../Properties/xlf/Resources.ja.xlf | 22 +++ .../Properties/xlf/Resources.ko.xlf | 22 +++ .../Properties/xlf/Resources.pl.xlf | 22 +++ .../Properties/xlf/Resources.pt-BR.xlf | 22 +++ .../Properties/xlf/Resources.ru.xlf | 22 +++ .../Properties/xlf/Resources.tr.xlf | 22 +++ .../Properties/xlf/Resources.zh-Hans.xlf | 22 +++ .../Properties/xlf/Resources.zh-Hant.xlf | 22 +++ .../Sdks/AndroidSdkUnix.cs | 4 +- .../Sdks/AndroidSdkWindows.cs | 4 +- .../Xamarin.Android.Tools.AndroidSdk.csproj | 21 +++ 22 files changed, 539 insertions(+), 4 deletions(-) create mode 100644 external/xamarin-android-tools/.gitattributes create mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.Designer.cs create mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.resx create mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.cs.xlf create mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.de.xlf create mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.es.xlf create mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.fr.xlf create mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.it.xlf create mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.ja.xlf create mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.ko.xlf create mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.pl.xlf create mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.pt-BR.xlf create mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.ru.xlf create mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.tr.xlf create mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.zh-Hans.xlf create mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.zh-Hant.xlf diff --git a/external/xamarin-android-tools/.gitattributes b/external/xamarin-android-tools/.gitattributes new file mode 100644 index 00000000000..22983d6f305 --- /dev/null +++ b/external/xamarin-android-tools/.gitattributes @@ -0,0 +1,3 @@ +*.Designer.cs eol=crlf +*.resx text +*.xlf text \ No newline at end of file diff --git a/external/xamarin-android-tools/azure-pipelines.yaml b/external/xamarin-android-tools/azure-pipelines.yaml index 6b2a1470dbf..7a55b270385 100644 --- a/external/xamarin-android-tools/azure-pipelines.yaml +++ b/external/xamarin-android-tools/azure-pipelines.yaml @@ -31,6 +31,8 @@ jobs: displayName: 'NuGet Restore' inputs: restoreSolution: Xamarin.Android.Tools.sln + feedsToUse: config + nugetConfigPath: NuGet.config - task: MSBuild@1 displayName: 'Build solution Xamarin.Android.Tools.sln' inputs: diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/JdkInfo.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/JdkInfo.cs index 9a72a799459..52437ad3bcb 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/JdkInfo.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/JdkInfo.cs @@ -11,6 +11,8 @@ using System.Xml; using System.Xml.Linq; +using Xamarin.Android.Tools.AndroidSdk.Properties; + namespace Xamarin.Android.Tools { public class JdkInfo { @@ -341,7 +343,7 @@ static IEnumerable GetMacOSMicrosoftJdkPaths () jdk = new JdkInfo (path, locator); } catch (Exception e) { - logger (TraceLevel.Warning, $"The directory `{path}`, via locator `{locator}`, is not a valid JDK directory: {e.Message}"); + logger (TraceLevel.Warning, string.Format (Resources.InvalidJdkDirectory_path_locator_message, path, locator, e.Message)); logger (TraceLevel.Verbose, e.ToString ()); } return jdk; diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/ProcessUtils.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/ProcessUtils.cs index 19965e2c96e..d002730a739 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/ProcessUtils.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/ProcessUtils.cs @@ -123,8 +123,11 @@ public static Task ExecuteToolAsync (string exe, Func +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Xamarin.Android.Tools.AndroidSdk.Properties { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Xamarin.Android.Tools.AndroidSdk.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to An exception occurred while validating the Java SDK installation in '{0}' that was found while searching the paths from '{1}'. Ensure that the Android section of the Visual Studio options has a valid Java SDK directory configured. To use a custom SDK path for a command line build, set the 'JavaSdkDirectory' MSBuild property to the custom path. Exception: {2}. + /// + internal static string InvalidJdkDirectory_path_locator_message { + get { + return ResourceManager.GetString("InvalidJdkDirectory_path_locator_message", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to An exception occurred while reading configuration file '{0}'. Exception: {1}. + /// + internal static string InvalidMonodroidConfigFile_path_message { + get { + return ResourceManager.GetString("InvalidMonodroidConfigFile_path_message", resourceCulture); + } + } + } +} diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.resx b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.resx new file mode 100644 index 00000000000..4e90d1ac9da --- /dev/null +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.resx @@ -0,0 +1,133 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + An exception occurred while validating the Java SDK installation in '{0}' that was found while searching the paths from '{1}'. Ensure that the Android section of the Visual Studio options has a valid Java SDK directory configured. To use a custom SDK path for a command line build, set the 'JavaSdkDirectory' MSBuild property to the custom path. Exception: {2} + +{0} - The path of the invalid installation +{1} - a "contextual" name for where {0} came from: `Preferred Registry` (Windows Registry), `OpenJDK`, `$JAVA_HOME` (environment variable), etc. +{2} - The exception message of the associated exception. + + + An exception occurred while reading configuration file '{0}'. Exception: {1} + +{0} - The path of the file being read. +{1} - The exception message of the associated exception. + + \ No newline at end of file diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.cs.xlf b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.cs.xlf new file mode 100644 index 00000000000..4ee39f8d13c --- /dev/null +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.cs.xlf @@ -0,0 +1,22 @@ + + + + + + An exception occurred while validating the Java SDK installation in '{0}' that was found while searching the paths from '{1}'. Ensure that the Android section of the Visual Studio options has a valid Java SDK directory configured. To use a custom SDK path for a command line build, set the 'JavaSdkDirectory' MSBuild property to the custom path. Exception: {2} + An exception occurred while validating the Java SDK installation in '{0}' that was found while searching the paths from '{1}'. Ensure that the Android section of the Visual Studio options has a valid Java SDK directory configured. To use a custom SDK path for a command line build, set the 'JavaSdkDirectory' MSBuild property to the custom path. Exception: {2} + +{0} - The path of the invalid installation +{1} - a "contextual" name for where {0} came from: `Preferred Registry` (Windows Registry), `OpenJDK`, `$JAVA_HOME` (environment variable), etc. +{2} - The exception message of the associated exception. + + + An exception occurred while reading configuration file '{0}'. Exception: {1} + An exception occurred while reading configuration file '{0}'. Exception: {1} + +{0} - The path of the file being read. +{1} - The exception message of the associated exception. + + + + \ No newline at end of file diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.de.xlf b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.de.xlf new file mode 100644 index 00000000000..0ef03afff9d --- /dev/null +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.de.xlf @@ -0,0 +1,22 @@ + + + + + + An exception occurred while validating the Java SDK installation in '{0}' that was found while searching the paths from '{1}'. Ensure that the Android section of the Visual Studio options has a valid Java SDK directory configured. To use a custom SDK path for a command line build, set the 'JavaSdkDirectory' MSBuild property to the custom path. Exception: {2} + An exception occurred while validating the Java SDK installation in '{0}' that was found while searching the paths from '{1}'. Ensure that the Android section of the Visual Studio options has a valid Java SDK directory configured. To use a custom SDK path for a command line build, set the 'JavaSdkDirectory' MSBuild property to the custom path. Exception: {2} + +{0} - The path of the invalid installation +{1} - a "contextual" name for where {0} came from: `Preferred Registry` (Windows Registry), `OpenJDK`, `$JAVA_HOME` (environment variable), etc. +{2} - The exception message of the associated exception. + + + An exception occurred while reading configuration file '{0}'. Exception: {1} + An exception occurred while reading configuration file '{0}'. Exception: {1} + +{0} - The path of the file being read. +{1} - The exception message of the associated exception. + + + + \ No newline at end of file diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.es.xlf b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.es.xlf new file mode 100644 index 00000000000..745a7b1eb63 --- /dev/null +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.es.xlf @@ -0,0 +1,22 @@ + + + + + + An exception occurred while validating the Java SDK installation in '{0}' that was found while searching the paths from '{1}'. Ensure that the Android section of the Visual Studio options has a valid Java SDK directory configured. To use a custom SDK path for a command line build, set the 'JavaSdkDirectory' MSBuild property to the custom path. Exception: {2} + An exception occurred while validating the Java SDK installation in '{0}' that was found while searching the paths from '{1}'. Ensure that the Android section of the Visual Studio options has a valid Java SDK directory configured. To use a custom SDK path for a command line build, set the 'JavaSdkDirectory' MSBuild property to the custom path. Exception: {2} + +{0} - The path of the invalid installation +{1} - a "contextual" name for where {0} came from: `Preferred Registry` (Windows Registry), `OpenJDK`, `$JAVA_HOME` (environment variable), etc. +{2} - The exception message of the associated exception. + + + An exception occurred while reading configuration file '{0}'. Exception: {1} + An exception occurred while reading configuration file '{0}'. Exception: {1} + +{0} - The path of the file being read. +{1} - The exception message of the associated exception. + + + + \ No newline at end of file diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.fr.xlf b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.fr.xlf new file mode 100644 index 00000000000..88ef6bd4636 --- /dev/null +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.fr.xlf @@ -0,0 +1,22 @@ + + + + + + An exception occurred while validating the Java SDK installation in '{0}' that was found while searching the paths from '{1}'. Ensure that the Android section of the Visual Studio options has a valid Java SDK directory configured. To use a custom SDK path for a command line build, set the 'JavaSdkDirectory' MSBuild property to the custom path. Exception: {2} + An exception occurred while validating the Java SDK installation in '{0}' that was found while searching the paths from '{1}'. Ensure that the Android section of the Visual Studio options has a valid Java SDK directory configured. To use a custom SDK path for a command line build, set the 'JavaSdkDirectory' MSBuild property to the custom path. Exception: {2} + +{0} - The path of the invalid installation +{1} - a "contextual" name for where {0} came from: `Preferred Registry` (Windows Registry), `OpenJDK`, `$JAVA_HOME` (environment variable), etc. +{2} - The exception message of the associated exception. + + + An exception occurred while reading configuration file '{0}'. Exception: {1} + An exception occurred while reading configuration file '{0}'. Exception: {1} + +{0} - The path of the file being read. +{1} - The exception message of the associated exception. + + + + \ No newline at end of file diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.it.xlf b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.it.xlf new file mode 100644 index 00000000000..e450f94fdad --- /dev/null +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.it.xlf @@ -0,0 +1,22 @@ + + + + + + An exception occurred while validating the Java SDK installation in '{0}' that was found while searching the paths from '{1}'. Ensure that the Android section of the Visual Studio options has a valid Java SDK directory configured. To use a custom SDK path for a command line build, set the 'JavaSdkDirectory' MSBuild property to the custom path. Exception: {2} + An exception occurred while validating the Java SDK installation in '{0}' that was found while searching the paths from '{1}'. Ensure that the Android section of the Visual Studio options has a valid Java SDK directory configured. To use a custom SDK path for a command line build, set the 'JavaSdkDirectory' MSBuild property to the custom path. Exception: {2} + +{0} - The path of the invalid installation +{1} - a "contextual" name for where {0} came from: `Preferred Registry` (Windows Registry), `OpenJDK`, `$JAVA_HOME` (environment variable), etc. +{2} - The exception message of the associated exception. + + + An exception occurred while reading configuration file '{0}'. Exception: {1} + An exception occurred while reading configuration file '{0}'. Exception: {1} + +{0} - The path of the file being read. +{1} - The exception message of the associated exception. + + + + \ No newline at end of file diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.ja.xlf b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.ja.xlf new file mode 100644 index 00000000000..964e5f8dbb8 --- /dev/null +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.ja.xlf @@ -0,0 +1,22 @@ + + + + + + An exception occurred while validating the Java SDK installation in '{0}' that was found while searching the paths from '{1}'. Ensure that the Android section of the Visual Studio options has a valid Java SDK directory configured. To use a custom SDK path for a command line build, set the 'JavaSdkDirectory' MSBuild property to the custom path. Exception: {2} + An exception occurred while validating the Java SDK installation in '{0}' that was found while searching the paths from '{1}'. Ensure that the Android section of the Visual Studio options has a valid Java SDK directory configured. To use a custom SDK path for a command line build, set the 'JavaSdkDirectory' MSBuild property to the custom path. Exception: {2} + +{0} - The path of the invalid installation +{1} - a "contextual" name for where {0} came from: `Preferred Registry` (Windows Registry), `OpenJDK`, `$JAVA_HOME` (environment variable), etc. +{2} - The exception message of the associated exception. + + + An exception occurred while reading configuration file '{0}'. Exception: {1} + An exception occurred while reading configuration file '{0}'. Exception: {1} + +{0} - The path of the file being read. +{1} - The exception message of the associated exception. + + + + \ No newline at end of file diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.ko.xlf b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.ko.xlf new file mode 100644 index 00000000000..f9c441ffad5 --- /dev/null +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.ko.xlf @@ -0,0 +1,22 @@ + + + + + + An exception occurred while validating the Java SDK installation in '{0}' that was found while searching the paths from '{1}'. Ensure that the Android section of the Visual Studio options has a valid Java SDK directory configured. To use a custom SDK path for a command line build, set the 'JavaSdkDirectory' MSBuild property to the custom path. Exception: {2} + An exception occurred while validating the Java SDK installation in '{0}' that was found while searching the paths from '{1}'. Ensure that the Android section of the Visual Studio options has a valid Java SDK directory configured. To use a custom SDK path for a command line build, set the 'JavaSdkDirectory' MSBuild property to the custom path. Exception: {2} + +{0} - The path of the invalid installation +{1} - a "contextual" name for where {0} came from: `Preferred Registry` (Windows Registry), `OpenJDK`, `$JAVA_HOME` (environment variable), etc. +{2} - The exception message of the associated exception. + + + An exception occurred while reading configuration file '{0}'. Exception: {1} + An exception occurred while reading configuration file '{0}'. Exception: {1} + +{0} - The path of the file being read. +{1} - The exception message of the associated exception. + + + + \ No newline at end of file diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.pl.xlf b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.pl.xlf new file mode 100644 index 00000000000..124fad07c6a --- /dev/null +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.pl.xlf @@ -0,0 +1,22 @@ + + + + + + An exception occurred while validating the Java SDK installation in '{0}' that was found while searching the paths from '{1}'. Ensure that the Android section of the Visual Studio options has a valid Java SDK directory configured. To use a custom SDK path for a command line build, set the 'JavaSdkDirectory' MSBuild property to the custom path. Exception: {2} + An exception occurred while validating the Java SDK installation in '{0}' that was found while searching the paths from '{1}'. Ensure that the Android section of the Visual Studio options has a valid Java SDK directory configured. To use a custom SDK path for a command line build, set the 'JavaSdkDirectory' MSBuild property to the custom path. Exception: {2} + +{0} - The path of the invalid installation +{1} - a "contextual" name for where {0} came from: `Preferred Registry` (Windows Registry), `OpenJDK`, `$JAVA_HOME` (environment variable), etc. +{2} - The exception message of the associated exception. + + + An exception occurred while reading configuration file '{0}'. Exception: {1} + An exception occurred while reading configuration file '{0}'. Exception: {1} + +{0} - The path of the file being read. +{1} - The exception message of the associated exception. + + + + \ No newline at end of file diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.pt-BR.xlf b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.pt-BR.xlf new file mode 100644 index 00000000000..948c0ba0726 --- /dev/null +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.pt-BR.xlf @@ -0,0 +1,22 @@ + + + + + + An exception occurred while validating the Java SDK installation in '{0}' that was found while searching the paths from '{1}'. Ensure that the Android section of the Visual Studio options has a valid Java SDK directory configured. To use a custom SDK path for a command line build, set the 'JavaSdkDirectory' MSBuild property to the custom path. Exception: {2} + An exception occurred while validating the Java SDK installation in '{0}' that was found while searching the paths from '{1}'. Ensure that the Android section of the Visual Studio options has a valid Java SDK directory configured. To use a custom SDK path for a command line build, set the 'JavaSdkDirectory' MSBuild property to the custom path. Exception: {2} + +{0} - The path of the invalid installation +{1} - a "contextual" name for where {0} came from: `Preferred Registry` (Windows Registry), `OpenJDK`, `$JAVA_HOME` (environment variable), etc. +{2} - The exception message of the associated exception. + + + An exception occurred while reading configuration file '{0}'. Exception: {1} + An exception occurred while reading configuration file '{0}'. Exception: {1} + +{0} - The path of the file being read. +{1} - The exception message of the associated exception. + + + + \ No newline at end of file diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.ru.xlf b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.ru.xlf new file mode 100644 index 00000000000..0b14aed9b73 --- /dev/null +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.ru.xlf @@ -0,0 +1,22 @@ + + + + + + An exception occurred while validating the Java SDK installation in '{0}' that was found while searching the paths from '{1}'. Ensure that the Android section of the Visual Studio options has a valid Java SDK directory configured. To use a custom SDK path for a command line build, set the 'JavaSdkDirectory' MSBuild property to the custom path. Exception: {2} + An exception occurred while validating the Java SDK installation in '{0}' that was found while searching the paths from '{1}'. Ensure that the Android section of the Visual Studio options has a valid Java SDK directory configured. To use a custom SDK path for a command line build, set the 'JavaSdkDirectory' MSBuild property to the custom path. Exception: {2} + +{0} - The path of the invalid installation +{1} - a "contextual" name for where {0} came from: `Preferred Registry` (Windows Registry), `OpenJDK`, `$JAVA_HOME` (environment variable), etc. +{2} - The exception message of the associated exception. + + + An exception occurred while reading configuration file '{0}'. Exception: {1} + An exception occurred while reading configuration file '{0}'. Exception: {1} + +{0} - The path of the file being read. +{1} - The exception message of the associated exception. + + + + \ No newline at end of file diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.tr.xlf b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.tr.xlf new file mode 100644 index 00000000000..3406e2d338f --- /dev/null +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.tr.xlf @@ -0,0 +1,22 @@ + + + + + + An exception occurred while validating the Java SDK installation in '{0}' that was found while searching the paths from '{1}'. Ensure that the Android section of the Visual Studio options has a valid Java SDK directory configured. To use a custom SDK path for a command line build, set the 'JavaSdkDirectory' MSBuild property to the custom path. Exception: {2} + An exception occurred while validating the Java SDK installation in '{0}' that was found while searching the paths from '{1}'. Ensure that the Android section of the Visual Studio options has a valid Java SDK directory configured. To use a custom SDK path for a command line build, set the 'JavaSdkDirectory' MSBuild property to the custom path. Exception: {2} + +{0} - The path of the invalid installation +{1} - a "contextual" name for where {0} came from: `Preferred Registry` (Windows Registry), `OpenJDK`, `$JAVA_HOME` (environment variable), etc. +{2} - The exception message of the associated exception. + + + An exception occurred while reading configuration file '{0}'. Exception: {1} + An exception occurred while reading configuration file '{0}'. Exception: {1} + +{0} - The path of the file being read. +{1} - The exception message of the associated exception. + + + + \ No newline at end of file diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.zh-Hans.xlf b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.zh-Hans.xlf new file mode 100644 index 00000000000..1414a0f03b7 --- /dev/null +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.zh-Hans.xlf @@ -0,0 +1,22 @@ + + + + + + An exception occurred while validating the Java SDK installation in '{0}' that was found while searching the paths from '{1}'. Ensure that the Android section of the Visual Studio options has a valid Java SDK directory configured. To use a custom SDK path for a command line build, set the 'JavaSdkDirectory' MSBuild property to the custom path. Exception: {2} + An exception occurred while validating the Java SDK installation in '{0}' that was found while searching the paths from '{1}'. Ensure that the Android section of the Visual Studio options has a valid Java SDK directory configured. To use a custom SDK path for a command line build, set the 'JavaSdkDirectory' MSBuild property to the custom path. Exception: {2} + +{0} - The path of the invalid installation +{1} - a "contextual" name for where {0} came from: `Preferred Registry` (Windows Registry), `OpenJDK`, `$JAVA_HOME` (environment variable), etc. +{2} - The exception message of the associated exception. + + + An exception occurred while reading configuration file '{0}'. Exception: {1} + An exception occurred while reading configuration file '{0}'. Exception: {1} + +{0} - The path of the file being read. +{1} - The exception message of the associated exception. + + + + \ No newline at end of file diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.zh-Hant.xlf b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.zh-Hant.xlf new file mode 100644 index 00000000000..c4db954ebaa --- /dev/null +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.zh-Hant.xlf @@ -0,0 +1,22 @@ + + + + + + An exception occurred while validating the Java SDK installation in '{0}' that was found while searching the paths from '{1}'. Ensure that the Android section of the Visual Studio options has a valid Java SDK directory configured. To use a custom SDK path for a command line build, set the 'JavaSdkDirectory' MSBuild property to the custom path. Exception: {2} + An exception occurred while validating the Java SDK installation in '{0}' that was found while searching the paths from '{1}'. Ensure that the Android section of the Visual Studio options has a valid Java SDK directory configured. To use a custom SDK path for a command line build, set the 'JavaSdkDirectory' MSBuild property to the custom path. Exception: {2} + +{0} - The path of the invalid installation +{1} - a "contextual" name for where {0} came from: `Preferred Registry` (Windows Registry), `OpenJDK`, `$JAVA_HOME` (environment variable), etc. +{2} - The exception message of the associated exception. + + + An exception occurred while reading configuration file '{0}'. Exception: {1} + An exception occurred while reading configuration file '{0}'. Exception: {1} + +{0} - The path of the file being read. +{1} - The exception message of the associated exception. + + + + \ No newline at end of file diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkUnix.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkUnix.cs index 34836f3d008..b0bf6229229 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkUnix.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkUnix.cs @@ -8,6 +8,8 @@ using System.Xml; using System.Xml.Linq; +using Xamarin.Android.Tools.AndroidSdk.Properties; + namespace Xamarin.Android.Tools { class AndroidSdkUnix : AndroidSdkBase @@ -232,7 +234,7 @@ internal static XDocument GetUnixConfigFile (Action logger) try { doc = XDocument.Load (file); } catch (Exception ex) { - logger (TraceLevel.Error, "Could not load monodroid configuration file"); + logger (TraceLevel.Error, string.Format (Resources.InvalidMonodroidConfigFile_path_message, file, ex.Message)); logger (TraceLevel.Verbose, ex.ToString ()); // move out of the way and create a new one diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkWindows.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkWindows.cs index 59b20824103..2c826b8fcdf 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkWindows.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkWindows.cs @@ -4,6 +4,8 @@ using System.IO; using System.Linq; +using Xamarin.Android.Tools.AndroidSdk.Properties; + namespace Xamarin.Android.Tools { class AndroidSdkWindows : AndroidSdkBase @@ -114,7 +116,7 @@ internal static IEnumerable GetJdkInfos (Action log jdk = new JdkInfo (path, locator); } catch (Exception e) { - logger (TraceLevel.Warning, $"The directory `{path}`, via locator `{locator}`, is not a valid JDK directory: {e.Message}"); + logger (TraceLevel.Warning, string.Format (Resources.InvalidJdkDirectory_path_locator_message, path, locator, e.Message)); logger (TraceLevel.Verbose, e.ToString ()); } return jdk; diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj index bae6585883d..3d1fee51f95 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj @@ -16,6 +16,11 @@ Xamarin;Xamarin.Android + + cs;de;es;fr;it;ja;ko;pl;pt-BR;ru;tr;zh-Hans;zh-Hant + true + + @@ -29,6 +34,7 @@ all runtime; build; native; contentfiles; analyzers + @@ -37,6 +43,21 @@ + + + True + True + Resources.resx + + + + + + ResXFileCodeGenerator + Resources.Designer.cs + + + PreserveNewest From 38c2ff0bbe8b208192bffa32e6d0437fdf929031 Mon Sep 17 00:00:00 2001 From: Jonathan Pryor Date: Mon, 19 Oct 2020 12:47:14 -0400 Subject: [PATCH 089/308] [Xamarin.Android.Tools.AndroidSdk] Update SDK component for API-30 (#99) Context: https://dl-ssl.google.com/android/repository/repository2-1.xml Update the preferred Android SDK component versions to the current latest versions listed in the [Android Repository file][0]: * `$(AndroidSdkBuildToolsVersion)`/build-tools to 30.0.2 * `$(AndroidCommandLineToolsVersion)`/cmdline-tools to 2.1 * `$(AndroidSdkPlatformToolsVersion)`/platform-tools to 30.0.4 * `$(AndroidSdkPlatformVersion)`/platform to android-30 * `$(AndroidNdkVersion)`/NDK to 21.3.6528147 [0]: https://dl-ssl.google.com/android/repository/repository2-1.xml --- .../Xamarin.Android.Tools.Versions.props | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.Versions.props b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.Versions.props index 9c016e16c04..1a304909536 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.Versions.props +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.Versions.props @@ -9,12 +9,12 @@ If this file is changed the submodule for androidtools should be updated, along with any other repo which references androidtools. --> - 29.0.2 - 1.0 - 29.0.5 + 30.0.2 + 2.1 + 30.0.4 26.1.1 - android-29 - 16.1 + android-30 + 21.3.6528147 \ No newline at end of file From 315527563d847a1f9fa314ac1f285492c3dc67c4 Mon Sep 17 00:00:00 2001 From: Brendan Zagaeski Date: Tue, 15 Dec 2020 16:28:35 -0500 Subject: [PATCH 090/308] [ci] Use the new "main" default branch (#100) The main branch is now the default branch, we need to update CI triggers accordingly. --- external/xamarin-android-tools/azure-pipelines.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/external/xamarin-android-tools/azure-pipelines.yaml b/external/xamarin-android-tools/azure-pipelines.yaml index 7a55b270385..46f7617854e 100644 --- a/external/xamarin-android-tools/azure-pipelines.yaml +++ b/external/xamarin-android-tools/azure-pipelines.yaml @@ -1,11 +1,11 @@ name: Xamarin.Android.Tools $(Rev:r) trigger: - - master + - main - d16-* pr: - - master + - main - d16-* # Global variables From ad511362b32348ec4109df37e7570c12784447b1 Mon Sep 17 00:00:00 2001 From: Peter Collins Date: Mon, 4 Jan 2021 19:46:35 -0500 Subject: [PATCH 091/308] [tests] Use dotnet test to run AndroidSdk-Tests (#102) Update `Xamarin.Android.Tools.AndroidSdk-Tests.csproj` to target only `netcoreapp3.1`, and updates all test running logic to use `dotnet test`. This allows us to remove the `globalPackagesFolder` override from `NuGet.config`, as `NUnit.ConsoleRunner` is no longer referenced or used. The YAML pipeline has also been consolidated a bit to reduce step duplication. A matrix strategy is now used to run the same steps in parallel on both macOS and Windows. OS-specific steps are now hidden behind a condition so that we don't duplicate artifact uploading and publishing. --- external/xamarin-android-tools/.gitignore | 3 +- .../.vscode/extensions.json | 3 +- .../.vscode/settings.json | 7 +- .../xamarin-android-tools/.vscode/tasks.json | 6 +- external/xamarin-android-tools/Makefile | 28 +------ external/xamarin-android-tools/NuGet.config | 7 -- external/xamarin-android-tools/README.md | 8 +- .../azure-pipelines.yaml | 77 ++++++++++--------- ...arin.Android.Tools.AndroidSdk-Tests.csproj | 17 ++-- 9 files changed, 69 insertions(+), 87 deletions(-) diff --git a/external/xamarin-android-tools/.gitignore b/external/xamarin-android-tools/.gitignore index ff2e62b0164..541b07af084 100644 --- a/external/xamarin-android-tools/.gitignore +++ b/external/xamarin-android-tools/.gitignore @@ -12,4 +12,5 @@ bin **/obj packages/ TestResult-*.xml -.vs/ \ No newline at end of file +**/TestResults/*.trx +.vs/ diff --git a/external/xamarin-android-tools/.vscode/extensions.json b/external/xamarin-android-tools/.vscode/extensions.json index 0377b399d7b..082d07a6cca 100644 --- a/external/xamarin-android-tools/.vscode/extensions.json +++ b/external/xamarin-android-tools/.vscode/extensions.json @@ -2,7 +2,8 @@ "recommendations": [ "ms-vscode.csharp", "ms-vscode.mono-debug", - "wghats.vscode-nxunit-test-adapter", + "hbenl.vscode-test-explorer", + "derivitec-ltd.vscode-dotnet-adapter", "visualstudioexptteam.vscodeintellicode", ] } \ No newline at end of file diff --git a/external/xamarin-android-tools/.vscode/settings.json b/external/xamarin-android-tools/.vscode/settings.json index bff3f6acf7a..6f3397a0a5c 100644 --- a/external/xamarin-android-tools/.vscode/settings.json +++ b/external/xamarin-android-tools/.vscode/settings.json @@ -1,6 +1,3 @@ { - "nxunitExplorer.nunit": "packages/NUnit.ConsoleRunner.3.9.0/tools/nunit3-console.exe", - "nxunitExplorer.modules": [ - "bin/TestDebug/Xamarin.Android.Tools.AndroidSdk-Tests.dll" - ] -} \ No newline at end of file + "dotnetCoreExplorer.searchpatterns": "bin/Test*net*/**/*-Tests.dll", +} diff --git a/external/xamarin-android-tools/.vscode/tasks.json b/external/xamarin-android-tools/.vscode/tasks.json index 87828d66f95..ae99578adf1 100644 --- a/external/xamarin-android-tools/.vscode/tasks.json +++ b/external/xamarin-android-tools/.vscode/tasks.json @@ -30,7 +30,11 @@ { "label": "Run Unit Tests", "type": "shell", - "command": "make run-all-tests", + "command": "dotnet", + "args": [ + "test", + "${workspaceFolder}/tests/Xamarin.Android.Tools.AndroidSdk-Tests/Xamarin.Android.Tools.AndroidSdk-Tests.csproj" + ], "group": { "kind": "test", "isDefault": true diff --git a/external/xamarin-android-tools/Makefile b/external/xamarin-android-tools/Makefile index 1c686c441f9..ccb2a29e349 100644 --- a/external/xamarin-android-tools/Makefile +++ b/external/xamarin-android-tools/Makefile @@ -1,7 +1,5 @@ CONFIGURATION := Debug -NUNIT_CONSOLE := packages/nunit.consolerunner/3.9.0/tools/nunit3-console.exe OS := $(shell uname) -RUNTIME := mono --debug=casts V ?= 0 include build-tools/scripts/msbuild.mk @@ -15,26 +13,6 @@ clean: prepare: nuget restore Xamarin.Android.Tools.sln -run-all-tests: run-nunit-tests - -# $(call RUN_NUNIT_TEST,filename,log-lref?) -define RUN_NUNIT_TEST - MONO_TRACE_LISTENER=Console.Out \ - $(RUNTIME) \ - $(NUNIT_CONSOLE) $(NUNIT_EXTRA) $(1) \ - $(if $(RUN),-run:$(RUN)) \ - --result="TestResult-$(basename $(notdir $(1))).xml" \ - -output=bin/Test$(CONFIGURATION)/TestOutput-$(basename $(notdir $(1))).txt \ - || true ; \ - if [ -f "bin/Test$(CONFIGURATION)/TestOutput-$(basename $(notdir $(1))).txt" ] ; then \ - cat bin/Test$(CONFIGURATION)/TestOutput-$(basename $(notdir $(1))).txt ; \ - fi -endef - -$(NUNIT_CONSOLE): prepare - -NUNIT_TESTS = \ - bin/Test$(CONFIGURATION)/Xamarin.Android.Tools.AndroidSdk-Tests.dll - -run-nunit-tests: $(NUNIT_TESTS) - $(foreach t,$(NUNIT_TESTS), $(call RUN_NUNIT_TEST,$(t),1)) +run-all-tests: + dotnet test -l "console;verbosity=detailed" -l trx \ + tests/Xamarin.Android.Tools.AndroidSdk-Tests/Xamarin.Android.Tools.AndroidSdk-Tests.csproj diff --git a/external/xamarin-android-tools/NuGet.config b/external/xamarin-android-tools/NuGet.config index 713d9fcef2a..4600a23b314 100644 --- a/external/xamarin-android-tools/NuGet.config +++ b/external/xamarin-android-tools/NuGet.config @@ -1,8 +1,4 @@ - @@ -15,7 +11,4 @@ - - - diff --git a/external/xamarin-android-tools/README.md b/external/xamarin-android-tools/README.md index 419b3223ac8..22b57ca45b7 100644 --- a/external/xamarin-android-tools/README.md +++ b/external/xamarin-android-tools/README.md @@ -38,7 +38,11 @@ The default `make all` target accepts the following optional # Build -To build **xamarin-android-tools**, first prepare the project: +To build **xamarin-android-tools**: + + msbuild /restore Xamarin.Android.Tools.sln + +Alternatively, first prepare the project: make prepare @@ -53,7 +57,7 @@ Next, run `make`: To run the unit tests: - make run-all-tests + dotnet test tests/Xamarin.Android.Tools.AndroidSdk-Tests/Xamarin.Android.Tools.AndroidSdk-Tests.csproj -l "console;verbosity=detailed" # Build Output Directory Structure diff --git a/external/xamarin-android-tools/azure-pipelines.yaml b/external/xamarin-android-tools/azure-pipelines.yaml index 46f7617854e..b2719ca10e7 100644 --- a/external/xamarin-android-tools/azure-pipelines.yaml +++ b/external/xamarin-android-tools/azure-pipelines.yaml @@ -11,77 +11,82 @@ pr: # Global variables variables: DotNetCoreVersion: 3.1.100 - HostedMac: Hosted Mac Internal - HostedWinVS2019: Hosted Windows 2019 with VS2019 jobs: -- job: windows - displayName: windows - pool: $(HostedWinVS2019) +- job: build + displayName: Build and Test + timeoutInMinutes: 60 + cancelTimeoutInMinutes: 2 + + strategy: + matrix: + macOS: + vmImage: macOS-10.15 + win2019: + vmImage: windows-2019 + + pool: + vmImage: $(vmImage) + + workspace: + clean: all + steps: + - checkout: self + clean: true + - task: UseDotNet@2 displayName: Use .NET Core $(DotNetCoreVersion) inputs: version: $(DotNetCoreVersion) + - task: NuGetToolInstaller@0 displayName: 'Install NuGet' inputs: versionSpec: 5.x + + - script: | + dotnet tool install --global boots + boots https://download.mono-project.com/archive/6.12.0/macos-10-universal/MonoFramework-MDK-6.12.0.107.macos10.xamarin.universal.pkg + displayName: Install Mono 6.12 + condition: and(succeeded(), eq(variables['agent.os'], 'Darwin')) + - task: NuGetCommand@2 displayName: 'NuGet Restore' inputs: restoreSolution: Xamarin.Android.Tools.sln feedsToUse: config nugetConfigPath: NuGet.config + - task: MSBuild@1 displayName: 'Build solution Xamarin.Android.Tools.sln' inputs: solution: Xamarin.Android.Tools.sln - - task: VSTest@2 + + - task: DotNetCoreCLI@2 displayName: 'Run Tests' inputs: - testAssemblyVer2: 'bin\TestDebug\*-Tests.dll' - testRunTitle: windows-tests + command: test + projects: bin/TestDebug-net*/**/*-Tests.dll + testRunTitle: Xamarin.Android.Tools Tests - $(vmImage) + - powershell: | $hashOfLastVersionChange = & "git" "log" "--follow" "-1" "--pretty=%H" "nuget.version" $commitsSinceVersionChange = & "git" "rev-list" "--count" "$hashOfLastVersionChange..HEAD" $majorMinor = Get-Content "nuget.version" $version = "$majorMinor.$commitsSinceVersionChange" Write-Host "##vso[task.setvariable variable=xat.nuget.version]$version" + condition: and(succeeded(), eq(variables['agent.os'], 'Windows_NT')) + - task: MSBuild@1 displayName: 'Build NuGet' inputs: solution: 'src\Xamarin.Android.Tools.AndroidSdk\Xamarin.Android.Tools.AndroidSdk.csproj' msbuildArguments: '/t:pack /p:Version=$(xat.nuget.version) /p:OutputPath=$(Build.ArtifactStagingDirectory)' + condition: and(succeeded(), eq(variables['agent.os'], 'Windows_NT')) + - task: PublishBuildArtifacts@1 displayName: Upload Artifacts inputs: pathtoPublish: $(Build.ArtifactStagingDirectory) -- job: mac - displayName: mac - pool: $(HostedMac) - steps: - - task: UseDotNet@2 - displayName: Use .NET Core $(DotNetCoreVersion) - inputs: - version: $(DotNetCoreVersion) - - script: | - dotnet tool install --global boots - boots https://download.mono-project.com/archive/6.4.0/macos-10-universal/MonoFramework-MDK-6.4.0.198.macos10.xamarin.universal.pkg - displayName: Install Mono 6.4 - - script: make prepare CONFIGURATION=$(Build.Configuration) - displayName: make prepare - - - script: make all CONFIGURATION=$(Build.Configuration) - displayName: make all - - - script: make run-all-tests CONFIGURATION=$(Build.Configuration) - displayName: make run-all-tests - - - task: PublishTestResults@2 - condition: always() - inputs: - testResultsFormat: NUnit - testResultsFiles: TestResult*.xml - testRunTitle: mac-tests - failTaskOnFailedTests: true + condition: and(succeeded(), eq(variables['agent.os'], 'Windows_NT')) diff --git a/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/Xamarin.Android.Tools.AndroidSdk-Tests.csproj b/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/Xamarin.Android.Tools.AndroidSdk-Tests.csproj index 2de72a8e1d2..ca3992e108f 100644 --- a/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/Xamarin.Android.Tools.AndroidSdk-Tests.csproj +++ b/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/Xamarin.Android.Tools.AndroidSdk-Tests.csproj @@ -1,20 +1,19 @@ - net461;netcoreapp3.1 + + netcoreapp3.1 false - false - - - $(TestOutputFullPath) + false - - - - + + + From c22503df7bf604eff1bbb910f65d6df336f51bfd Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Wed, 20 Jan 2021 22:19:27 +0100 Subject: [PATCH 092/308] [NDK] Locate and select only compatible NDK versions (#103) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Context: https://github.com/actions/virtual-environments/issues/2420 Context: https://github.com/xamarin/xamarin-android/issues/5499 Context: https://github.com/xamarin/xamarin-android/issues/5526 Context: https://github.com/android/ndk/issues/1427 Context: https://developer.android.com/studio/command-line/variables#envar Xamarin.Android is not (yet) compatible with the recently released Android NDK r22 version. Azure build images have recently rolled out an update which includes NDK r22 and, thus, it breaks builds for customers using any form of Xamarin.Android AOT build. In an attempt to detect broken/incompatible NDK versions as well as to select the "best one", this commit adds code to scan the known NDK locations in search of the preferred version. Given an `AndroidSdkInfo` creation of: var info = new AndroidSdkInfo (logger:logger, androidSdkPath: sdkPath, androidNdkPath: ndkPath, javaSdkPath: javaPath); var usedNdkPath = info.AndroidNdkPath; If `ndkPath` is not `null` and otherwise valid, then `usedNdkPath` is `ndkPath`. If `ndkPath` is `null` or is otherwise invalid (missing `ndk-stack`, etc.), then we search for an `info.AndroidNdkPath` value as follows: 1. If `androidSdkPath` is not `null` and valid, then we check for Android SDK-relative NDK locations, in: * `{androidSdkPath}/ndk/*` * `{androidSdkPath}/ndk-bundle` For each found SDK-relative NDK directory, we filter out NDKs for which we cannot determine the package version, as well as those which are "too old" (< `MinimumCompatibleNDKMajorVersion`) or "too new" (> `MaximumCompatibleNDKMajorVersion`), currently r22. We prefer the NDK location with the highest version number. 2. If `androidSdkPath` is not `null` and valid and if there are no Android SDK-relative NDK locations, then we use the user-selected "preferred NDK location". See also `AndroidSdkInfo.SetPreferredAndroidNdkPath()`. 3. If `androidSdkPath` is not `null` and valid and if the preferred NDK location isn't set or is invalid, then we check directories specified in `$PATH`, and use the directory which contains `ndk-stack`. 4. If `androidSdkPath` is not `null` and valid and `$PATH` didn't contain `ndk-stack`, then we continue looking for NDK locations within the Android SDK locations specified by the `$ANDROID_HOME` and `$ANDROID_SDK_ROOT` environment variables. As with (1), these likewise look for e.g. `${ANDROID_HOME}/ndk/*` or `${ANDROID_SDK_ROOT}/ndk-bundle` directories and select the NDK with the highest supported version. 5. If `androidSdkPath` is `null`, then *first* we try to find a valid Android SDK directory, using on Unix: a. The preferred Android SDK directory; see also `AndroidSdkInfo.SetPreferredAndroidSdkPath(). b. The `$ANDROID_HOME` and `ANDROID_SDK_ROOT` environment variables. c. Directories within `$PATH` that contain `adb`. Once an Android SDK is found, steps (1)…(4) are performed. In (1) and (4), we now look for the Android SDK packages containing the NDK. There are two kinds of such packages: * `ndk-bundle` is the older package which allows for installation of only one NDK inside the SDK directory * `ndk/*` is a newer package which allows for installation of several NDK versions in parallel. Each subdirectory of `ndk` is an `X.Y.Z` version number of the NDK. In each of these directories we look for the `source.properties` file from which we then extract the NDK version and then we sort thus discovered NDK instances using their version as the key, in the descending order. The latest compatible (currently: less than 22 and more than 15) version is selected and its path returned to the caller. --- .../Sdks/AndroidSdkBase.cs | 88 ++++++++++++++++++- .../Sdks/AndroidSdkUnix.cs | 4 + .../Sdks/AndroidSdkWindows.cs | 4 + .../AndroidSdkInfoTests.cs | 86 +++++++++++++++++- 4 files changed, 175 insertions(+), 7 deletions(-) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkBase.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkBase.cs index 820c81cb111..59fc390b69b 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkBase.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkBase.cs @@ -9,6 +9,15 @@ namespace Xamarin.Android.Tools { abstract class AndroidSdkBase { + // When this changes, update the test: Xamarin.Android.Tools.Tests.AndroidSdkInfoTests.Ndk_MultipleNdkVersionsInSdk + const int MinimumCompatibleNDKMajorVersion = 16; + const int MaximumCompatibleNDKMajorVersion = 21; + + static readonly char[] SourcePropertiesKeyValueSplit = new char[] { '=' }; + + // Per https://developer.android.com/studio/command-line/variables#envar + protected static readonly string[] AndroidSdkEnvVars = {"ANDROID_HOME", "ANDROID_SDK_ROOT"}; + string[]? allAndroidSdks; public string[] AllAndroidSdks { @@ -97,8 +106,9 @@ public virtual void Initialize (string? androidSdkPath = null, string? androidNd if (pathValidator (ctorParam)) return ctorParam; foreach (var path in getAllPaths ()) { - if (pathValidator (path)) + if (pathValidator (path)) { return path; + } } return null; } @@ -108,7 +118,7 @@ public virtual void Initialize (string? androidSdkPath = null, string? androidNd if (ValidateAndroidNdkLocation (ctorParam)) return ctorParam; if (AndroidSdkPath != null) { - string bundle = Path.Combine (AndroidSdkPath, "ndk-bundle"); + string bundle = FindBestNDK (AndroidSdkPath); if (Directory.Exists (bundle) && ValidateAndroidNdkLocation (bundle)) return bundle; } @@ -125,6 +135,18 @@ public virtual void Initialize (string? androidSdkPath = null, string? androidNd protected abstract IEnumerable GetAllAvailableAndroidSdks (); protected abstract string GetShortFormPath (string path); + protected IEnumerable GetSdkFromEnvironmentVariables () + { + foreach (string envVar in AndroidSdkEnvVars) { + string ev = Environment.GetEnvironmentVariable (envVar); + if (String.IsNullOrEmpty (ev)) { + continue; + } + + yield return ev; + } + } + protected virtual IEnumerable GetAllAvailableAndroidNdks () { // Look in PATH @@ -139,7 +161,67 @@ protected virtual IEnumerable GetAllAvailableAndroidNdks () foreach (var sdk in GetAllAvailableAndroidSdks ()) { if (sdk == AndroidSdkPath) continue; - yield return Path.Combine (sdk, "ndk-bundle"); + yield return FindBestNDK (sdk); + } + } + + string FindBestNDK (string androidSdkPath) + { + var ndkInstances = new SortedDictionary (Comparer.Create ((Version l, Version r) => r.CompareTo (l))); + + foreach (string ndkPath in Directory.EnumerateDirectories (androidSdkPath, "ndk*", SearchOption.TopDirectoryOnly)) { + if (String.Compare ("ndk-bundle", Path.GetFileName (ndkPath), StringComparison.OrdinalIgnoreCase) == 0) { + LoadNDKVersion (ndkPath); + continue; + } + + if (String.Compare ("ndk", Path.GetFileName (ndkPath), StringComparison.OrdinalIgnoreCase) != 0) { + continue; + } + + foreach (string versionedNdkPath in Directory.EnumerateDirectories (ndkPath, "*", SearchOption.TopDirectoryOnly)) { + LoadNDKVersion (versionedNdkPath); + } + } + + if (ndkInstances.Count == 0) { + return String.Empty; + } + + var kvp = ndkInstances.First (); + Logger (TraceLevel.Verbose, $"Best NDK selected: v{kvp.Key} in {kvp.Value}"); + return kvp.Value; + + void LoadNDKVersion (string path) + { + string propsFilePath = Path.Combine (path, "source.properties"); + if (!File.Exists (propsFilePath)) { + Logger (TraceLevel.Verbose, $"Skipping NDK in '{path}': no source.properties, cannot determine version"); + return; + } + + foreach (string line in File.ReadLines (propsFilePath)) { + string[] parts = line.Split (SourcePropertiesKeyValueSplit, 2, StringSplitOptions.RemoveEmptyEntries); + if (parts.Length != 2) { + continue; + } + + if (String.Compare ("Pkg.Revision", parts[0].Trim (), StringComparison.Ordinal) != 0) { + continue; + } + + if (!Version.TryParse (parts[1].Trim (), out Version? ndkVer) || ndkVer == null || ndkInstances.ContainsKey (ndkVer)) { + continue; + } + + if (ndkVer.Major < MinimumCompatibleNDKMajorVersion || ndkVer.Major > MaximumCompatibleNDKMajorVersion) { + Logger (TraceLevel.Verbose, $"Skipping NDK in '{path}': version {ndkVer} is out of the accepted range (major version must be between {MinimumCompatibleNDKMajorVersion} and {MaximumCompatibleNDKMajorVersion}"); + continue; + } + + ndkInstances.Add (ndkVer, path); + return; + } } } diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkUnix.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkUnix.cs index b0bf6229229..2f3b2f9cf75 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkUnix.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkUnix.cs @@ -94,6 +94,10 @@ protected override IEnumerable GetAllAvailableAndroidSdks () if (!string.IsNullOrEmpty (preferedSdkPath)) yield return preferedSdkPath!; + foreach (string dir in GetSdkFromEnvironmentVariables ()) { + yield return dir; + } + // Look in PATH foreach (var adb in ProcessUtils.FindExecutablesInPath (Adb)) { var path = Path.GetDirectoryName (adb); diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkWindows.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkWindows.cs index 2c826b8fcdf..739199939c8 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkWindows.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkWindows.cs @@ -230,6 +230,10 @@ protected override IEnumerable GetAllAvailableAndroidNdks () if (CheckRegistryKeyForExecutable (root, regKey, MDREG_ANDROID_NDK, wow, ".", NdkStack)) yield return RegistryEx.GetValueString (root, regKey, MDREG_ANDROID_NDK, wow) ?? ""; + foreach (string dir in GetSdkFromEnvironmentVariables ()) { + yield return dir; + } + /* // Check for the key written by the Xamarin installer if (CheckRegistryKeyForExecutable (RegistryEx.CurrentUser, XAMARIN_ANDROID_INSTALLER_PATH, XAMARIN_ANDROID_INSTALLER_KEY, wow, "platform-tools", Adb)) diff --git a/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/AndroidSdkInfoTests.cs b/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/AndroidSdkInfoTests.cs index 28aad1488fa..7efcaa2a890 100644 --- a/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/AndroidSdkInfoTests.cs +++ b/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/AndroidSdkInfoTests.cs @@ -13,6 +13,8 @@ namespace Xamarin.Android.Tools.Tests [TestFixture] public class AndroidSdkInfoTests { + const string NdkVersion = "21.0.6113669"; + string UnixConfigDirOverridePath; string PreferredJdksOverridePath; @@ -64,14 +66,65 @@ public void Constructor_Paths () } } + [Test] + public void Ndk_MultipleNdkVersionsInSdk () + { + // Must match like-named constants in AndroidSdkBase + const int MinimumCompatibleNDKMajorVersion = 16; + const int MaximumCompatibleNDKMajorVersion = 21; + + CreateSdks(out string root, out string jdk, out string ndk, out string sdk); + + Action logger = (level, message) => { + Console.WriteLine($"[{level}] {message}"); + }; + + var ndkVersions = new List { + "16.1.4479499", + "17.2.4988734", + "18.1.5063045", + "19.2.5345600", + "20.0.5594570", + "20.1.5948944", + "21.0.6113669", + "21.1.6352462", + "21.2.6472646", + "21.3.6528147", + "22.0.7026061", + }; + string expectedVersion = "21.3.6528147"; + string expectedNdkPath = Path.Combine (sdk, "ndk", expectedVersion); + + try { + MakeNdkDir (Path.Combine (sdk, "ndk-bundle"), NdkVersion); + + foreach (string ndkVer in ndkVersions) { + MakeNdkDir (Path.Combine (sdk, "ndk", ndkVer), ndkVer); + } + + var info = new AndroidSdkInfo (logger, androidSdkPath: sdk, androidNdkPath: null, javaSdkPath: jdk); + + Assert.AreEqual (expectedNdkPath, info.AndroidNdkPath, "AndroidNdkPath not found inside sdk!"); + + string ndkVersion = Path.GetFileName (info.AndroidNdkPath); + if (!Version.TryParse (ndkVersion, out Version ver)) { + Assert.Fail ($"Unable to parse '{ndkVersion}' as a valid version."); + } + + Assert.True (ver.Major >= MinimumCompatibleNDKMajorVersion, $"NDK version must be at least {MinimumCompatibleNDKMajorVersion}"); + Assert.True (ver.Major <= MaximumCompatibleNDKMajorVersion, $"NDK version must be at most {MinimumCompatibleNDKMajorVersion}"); + } finally { + Directory.Delete (root, recursive: true); + } + } + [Test] public void Ndk_PathInSdk() { CreateSdks(out string root, out string jdk, out string ndk, out string sdk); - var logs = new StringWriter(); Action logger = (level, message) => { - logs.WriteLine($"[{level}] {message}"); + Console.WriteLine($"[{level}] {message}"); }; try @@ -79,6 +132,7 @@ public void Ndk_PathInSdk() var extension = OS.IsWindows ? ".cmd" : ""; var ndkPath = Path.Combine(sdk, "ndk-bundle"); Directory.CreateDirectory(ndkPath); + File.WriteAllText(Path.Combine (ndkPath, "source.properties"), $"Pkg.Revision = {NdkVersion}"); Directory.CreateDirectory(Path.Combine(ndkPath, "toolchains")); File.WriteAllText(Path.Combine(ndkPath, $"ndk-stack{extension}"), ""); @@ -106,6 +160,8 @@ public void Constructor_SetValuesFromPath () }; var oldPath = Environment.GetEnvironmentVariable ("PATH"); var oldJavaHome = Environment.GetEnvironmentVariable ("JAVA_HOME"); + var oldAndroidHome = Environment.GetEnvironmentVariable ("ANDROID_HOME"); + var oldAndroidSdkRoot = Environment.GetEnvironmentVariable ("ANDROID_SDK_ROOT"); try { var paths = new List () { Path.Combine (jdk, "bin"), @@ -117,6 +173,12 @@ public void Constructor_SetValuesFromPath () if (!string.IsNullOrEmpty (oldJavaHome)) { Environment.SetEnvironmentVariable ("JAVA_HOME", string.Empty); } + if (!string.IsNullOrEmpty (oldAndroidHome)) { + Environment.SetEnvironmentVariable ("ANDROID_HOME", string.Empty); + } + if (!string.IsNullOrEmpty (oldAndroidSdkRoot)) { + Environment.SetEnvironmentVariable ("ANDROID_SDK_ROOT", string.Empty); + } var info = new AndroidSdkInfo (logger); @@ -129,6 +191,12 @@ public void Constructor_SetValuesFromPath () if (!string.IsNullOrEmpty (oldJavaHome)) { Environment.SetEnvironmentVariable ("JAVA_HOME", oldJavaHome); } + if (!string.IsNullOrEmpty (oldAndroidHome)) { + Environment.SetEnvironmentVariable ("ANDROID_HOME", oldAndroidHome); + } + if (!string.IsNullOrEmpty (oldAndroidSdkRoot)) { + Environment.SetEnvironmentVariable ("ANDROID_SDK_ROOT", oldAndroidSdkRoot); + } Directory.Delete (root, recursive: true); } } @@ -243,7 +311,7 @@ static void CreateSdks (out string root, out string jdk, out string ndk, out str Directory.CreateDirectory (jdk); CreateFauxAndroidSdkDirectory (sdk, "26.0.0"); - CreateFauxAndroidNdkDirectory (ndk); + CreateFauxAndroidNdkDirectory (ndk, NdkVersion); CreateFauxJavaSdkDirectory (jdk, "1.8.0", out var _, out var _); } @@ -311,8 +379,9 @@ struct ApiInfo { public string Id; } - static void CreateFauxAndroidNdkDirectory (string androidNdkDirectory) + static void CreateFauxAndroidNdkDirectory (string androidNdkDirectory, string ndkVersion) { + File.WriteAllText (Path.Combine (androidNdkDirectory, "source.properties"), $"Pkg.Revision = {ndkVersion}"); File.WriteAllText (Path.Combine (androidNdkDirectory, "ndk-stack"), ""); File.WriteAllText (Path.Combine (androidNdkDirectory, "ndk-stack.cmd"), ""); @@ -474,5 +543,14 @@ public void GetBuildToolsPaths_StableVersionsFirst () Directory.Delete (root, recursive: true); } } + + void MakeNdkDir (string rootPath, string version) + { + var extension = OS.IsWindows ? ".cmd" : String.Empty; + Directory.CreateDirectory(rootPath); + File.WriteAllText(Path.Combine (rootPath, "source.properties"), $"Pkg.Revision = {version}"); + Directory.CreateDirectory(Path.Combine(rootPath, "toolchains")); + File.WriteAllText(Path.Combine(rootPath, $"ndk-stack{extension}"), String.Empty); + } } } From ae3509614a950620ea4374b1a805741569b7a3f2 Mon Sep 17 00:00:00 2001 From: Peter Collins Date: Tue, 2 Feb 2021 11:19:29 -0500 Subject: [PATCH 093/308] Add Microsoft.Android.Build.BaseTasks project (#101) We are hoping to sever the xamarin-android dependency from monodroid. In order to do this we'll want to share some common MSBuild code across our two build task assemblies, `Xamarin.Android.Build.Tasks` and `Xamarin.Android.Build.Debugging.Tasks`. This common code has been moved from xamarin/xamarin-android into a new project in this repo named `Microsoft.Android.Build.BaseTasks`. More utilities can be migrated here in the future if we so desire. A new test project `Microsoft.Android.Build.BaseTasks-Tests.csproj` has also been added to bring over the relevant tests from xamarin-android. --- external/xamarin-android-tools/Makefile | 2 + .../Xamarin.Android.Tools.sln | 23 +- .../azure-pipelines.yaml | 1 - .../AndroidAsyncTask.cs | 50 ++ .../AndroidRidAbiHelper.cs | 94 +++ .../AndroidRunToolTask.cs | 76 +++ .../AndroidTask.cs | 26 + .../AndroidToolTask.cs | 26 + .../AsyncTaskExtensions.cs | 106 +++ .../Crc64.Table.cs | 538 ++++++++++++++++ .../Crc64.cs | 65 ++ .../Crc64Helper.cs | 93 +++ .../Files.cs | 528 +++++++++++++++ .../LinePreservedXmlWriter.cs | 146 +++++ .../MSBuildExtensions.cs | 285 ++++++++ .../MSBuildReferences.projitems | 60 ++ .../MemoryStreamPool.cs | 105 +++ .../Microsoft.Android.Build.BaseTasks.csproj | 20 + .../ObjectPool.cs | 39 ++ .../Properties/Resources.resx | 105 +++ .../Properties/xlf/Resources.cs.xlf | 12 + .../Properties/xlf/Resources.de.xlf | 12 + .../Properties/xlf/Resources.es.xlf | 12 + .../Properties/xlf/Resources.fr.xlf | 12 + .../Properties/xlf/Resources.it.xlf | 12 + .../Properties/xlf/Resources.ja.xlf | 12 + .../Properties/xlf/Resources.ko.xlf | 12 + .../Properties/xlf/Resources.pl.xlf | 12 + .../Properties/xlf/Resources.pt-BR.xlf | 12 + .../Properties/xlf/Resources.ru.xlf | 12 + .../Properties/xlf/Resources.tr.xlf | 12 + .../Properties/xlf/Resources.zh-Hans.xlf | 12 + .../Properties/xlf/Resources.zh-Hant.xlf | 12 + .../UnhandledExceptionLogger.cs | 89 +++ .../XDocumentExtensions.cs | 56 ++ .../global.json | 5 + .../FilesTests.cs | 606 ++++++++++++++++++ .../MemoryStreamPoolTests.cs | 70 ++ ...osoft.Android.Build.BaseTasks-Tests.csproj | 26 + 39 files changed, 3392 insertions(+), 4 deletions(-) create mode 100644 external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/AndroidAsyncTask.cs create mode 100644 external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/AndroidRidAbiHelper.cs create mode 100644 external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/AndroidRunToolTask.cs create mode 100644 external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/AndroidTask.cs create mode 100644 external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/AndroidToolTask.cs create mode 100644 external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/AsyncTaskExtensions.cs create mode 100644 external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Crc64.Table.cs create mode 100644 external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Crc64.cs create mode 100644 external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Crc64Helper.cs create mode 100644 external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Files.cs create mode 100644 external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/LinePreservedXmlWriter.cs create mode 100644 external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildExtensions.cs create mode 100644 external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems create mode 100644 external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MemoryStreamPool.cs create mode 100644 external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Microsoft.Android.Build.BaseTasks.csproj create mode 100644 external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/ObjectPool.cs create mode 100644 external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.resx create mode 100644 external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/xlf/Resources.cs.xlf create mode 100644 external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/xlf/Resources.de.xlf create mode 100644 external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/xlf/Resources.es.xlf create mode 100644 external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/xlf/Resources.fr.xlf create mode 100644 external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/xlf/Resources.it.xlf create mode 100644 external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/xlf/Resources.ja.xlf create mode 100644 external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/xlf/Resources.ko.xlf create mode 100644 external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/xlf/Resources.pl.xlf create mode 100644 external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/xlf/Resources.pt-BR.xlf create mode 100644 external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/xlf/Resources.ru.xlf create mode 100644 external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/xlf/Resources.tr.xlf create mode 100644 external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/xlf/Resources.zh-Hans.xlf create mode 100644 external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/xlf/Resources.zh-Hant.xlf create mode 100644 external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/UnhandledExceptionLogger.cs create mode 100644 external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/XDocumentExtensions.cs create mode 100644 external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/global.json create mode 100644 external/xamarin-android-tools/tests/Microsoft.Android.Build.BaseTasks-Tests/FilesTests.cs create mode 100644 external/xamarin-android-tools/tests/Microsoft.Android.Build.BaseTasks-Tests/MemoryStreamPoolTests.cs create mode 100644 external/xamarin-android-tools/tests/Microsoft.Android.Build.BaseTasks-Tests/Microsoft.Android.Build.BaseTasks-Tests.csproj diff --git a/external/xamarin-android-tools/Makefile b/external/xamarin-android-tools/Makefile index ccb2a29e349..f9af94f984b 100644 --- a/external/xamarin-android-tools/Makefile +++ b/external/xamarin-android-tools/Makefile @@ -16,3 +16,5 @@ prepare: run-all-tests: dotnet test -l "console;verbosity=detailed" -l trx \ tests/Xamarin.Android.Tools.AndroidSdk-Tests/Xamarin.Android.Tools.AndroidSdk-Tests.csproj + dotnet test -l "console;verbosity=detailed" -l trx \ + tests/Microsoft.Android.Build.BaseTasks-Tests/Microsoft.Android.Build.BaseTasks-Tests.csproj diff --git a/external/xamarin-android-tools/Xamarin.Android.Tools.sln b/external/xamarin-android-tools/Xamarin.Android.Tools.sln index c7e13b76de5..9dd35bc4534 100644 --- a/external/xamarin-android-tools/Xamarin.Android.Tools.sln +++ b/external/xamarin-android-tools/Xamarin.Android.Tools.sln @@ -1,8 +1,14 @@ Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 2012 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Xamarin.Android.Tools.AndroidSdk", "src\Xamarin.Android.Tools.AndroidSdk\Xamarin.Android.Tools.AndroidSdk.csproj", "{E34BCFA0-CAA4-412C-AA1C-75DB8D67D157}" +# Visual Studio Version 16 +VisualStudioVersion = 16.0.30709.64 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Xamarin.Android.Tools.AndroidSdk", "src\Xamarin.Android.Tools.AndroidSdk\Xamarin.Android.Tools.AndroidSdk.csproj", "{E34BCFA0-CAA4-412C-AA1C-75DB8D67D157}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Xamarin.Android.Tools.AndroidSdk-Tests", "tests\Xamarin.Android.Tools.AndroidSdk-Tests\Xamarin.Android.Tools.AndroidSdk-Tests.csproj", "{1E5501E8-49C1-4659-838D-CC9720C5208F}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Xamarin.Android.Tools.AndroidSdk-Tests", "tests\Xamarin.Android.Tools.AndroidSdk-Tests\Xamarin.Android.Tools.AndroidSdk-Tests.csproj", "{1E5501E8-49C1-4659-838D-CC9720C5208F}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Android.Build.BaseTasks", "src\Microsoft.Android.Build.BaseTasks\Microsoft.Android.Build.BaseTasks.csproj", "{C8E51A26-F7A5-4B81-B0BC-F9A4BFB43D38}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Android.Build.BaseTasks-Tests", "tests\Microsoft.Android.Build.BaseTasks-Tests\Microsoft.Android.Build.BaseTasks-Tests.csproj", "{4F5FD01C-B5A6-4F9F-91CE-3E78B3F942AC}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -18,8 +24,19 @@ Global {1E5501E8-49C1-4659-838D-CC9720C5208F}.Debug|Any CPU.Build.0 = Debug|Any CPU {1E5501E8-49C1-4659-838D-CC9720C5208F}.Release|Any CPU.ActiveCfg = Release|Any CPU {1E5501E8-49C1-4659-838D-CC9720C5208F}.Release|Any CPU.Build.0 = Release|Any CPU + {C8E51A26-F7A5-4B81-B0BC-F9A4BFB43D38}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C8E51A26-F7A5-4B81-B0BC-F9A4BFB43D38}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C8E51A26-F7A5-4B81-B0BC-F9A4BFB43D38}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C8E51A26-F7A5-4B81-B0BC-F9A4BFB43D38}.Release|Any CPU.Build.0 = Release|Any CPU + {4F5FD01C-B5A6-4F9F-91CE-3E78B3F942AC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4F5FD01C-B5A6-4F9F-91CE-3E78B3F942AC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4F5FD01C-B5A6-4F9F-91CE-3E78B3F942AC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4F5FD01C-B5A6-4F9F-91CE-3E78B3F942AC}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {BA1CD771-F00B-4DE8-93EE-7690D81F6A5A} + EndGlobalSection EndGlobal diff --git a/external/xamarin-android-tools/azure-pipelines.yaml b/external/xamarin-android-tools/azure-pipelines.yaml index b2719ca10e7..ae5c086147c 100644 --- a/external/xamarin-android-tools/azure-pipelines.yaml +++ b/external/xamarin-android-tools/azure-pipelines.yaml @@ -68,7 +68,6 @@ jobs: inputs: command: test projects: bin/TestDebug-net*/**/*-Tests.dll - testRunTitle: Xamarin.Android.Tools Tests - $(vmImage) - powershell: | $hashOfLastVersionChange = & "git" "log" "--follow" "-1" "--pretty=%H" "nuget.version" diff --git a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/AndroidAsyncTask.cs b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/AndroidAsyncTask.cs new file mode 100644 index 00000000000..f3859e5093d --- /dev/null +++ b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/AndroidAsyncTask.cs @@ -0,0 +1,50 @@ +// https://github.com/xamarin/xamarin-android/blob/9fca138604c53989e1cff7fc0c2e939583b4da28/src/Xamarin.Android.Build.Tasks/Tasks/AndroidTask.cs#L27 + +using System; +using Xamarin.Build; +using static System.Threading.Tasks.TaskExtensions; + +namespace Microsoft.Android.Build.Tasks +{ + public abstract class AndroidAsyncTask : AsyncTask + { + public abstract string TaskPrefix { get; } + + public override bool Execute () + { + try { + return RunTask (); + } catch (Exception ex) { + this.LogUnhandledException (TaskPrefix, ex); + return false; + } + } + + /// + /// Typically `RunTaskAsync` will be the preferred method to override, + /// however this method can be overridden instead for Tasks that will + /// run quickly and do not need to be asynchronous. + /// + public virtual bool RunTask () + { + Yield (); + try { + this.RunTask (() => RunTaskAsync ()) + .Unwrap () + .ContinueWith (Complete); + + // This blocks on AsyncTask.Execute, until Complete is called + return base.Execute (); + } finally { + Reacquire (); + } + } + + /// + /// Override this method for simplicity of AsyncTask usage: + /// * Yield / Reacquire is handled for you + /// * RunTaskAsync is already on a background thread + /// + public virtual System.Threading.Tasks.Task RunTaskAsync () => System.Threading.Tasks.Task.CompletedTask; + } +} diff --git a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/AndroidRidAbiHelper.cs b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/AndroidRidAbiHelper.cs new file mode 100644 index 00000000000..229d069062b --- /dev/null +++ b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/AndroidRidAbiHelper.cs @@ -0,0 +1,94 @@ +// https://github.com/xamarin/xamarin-android/blob/34acbbae6795854cc4e9f8eb7167ab011e0266b4/src/Xamarin.Android.Build.Tasks/Utilities/MonoAndroidHelper.cs#L251 + +using System; +using System.IO; +using System.Linq; +using Microsoft.Build.Framework; + +namespace Microsoft.Android.Build.Tasks +{ + public static class AndroidRidAbiHelper + { + static readonly string[] ValidAbis = new[]{ + "arm64-v8a", + "armeabi-v7a", + "x86", + "x86_64", + }; + + public static string GetNativeLibraryAbi (string lib) + { + // The topmost directory the .so file is contained within + var dir = Path.GetFileName (Path.GetDirectoryName (lib)).ToLowerInvariant (); + if (dir.StartsWith ("interpreter-", StringComparison.Ordinal)) { + dir = dir.Substring (12); + } + if (ValidAbis.Contains (dir)) { + return dir; + } + return null; + } + + public static string GetNativeLibraryAbi (ITaskItem lib) + { + // If Abi is explicitly specified, simply return it. + var lib_abi = lib.GetMetadata ("Abi"); + + if (!string.IsNullOrWhiteSpace (lib_abi)) + return lib_abi; + + // Try to figure out what type of abi this is from the path + // First, try nominal "Link" path. + var link = lib.GetMetadata ("Link"); + if (!string.IsNullOrWhiteSpace (link)) { + var linkdirs = link.ToLowerInvariant ().Split ('/', '\\'); + lib_abi = ValidAbis.Where (p => linkdirs.Contains (p)).FirstOrDefault (); + } + + // Check for a RuntimeIdentifier + var rid = lib.GetMetadata ("RuntimeIdentifier"); + if (!string.IsNullOrWhiteSpace (rid)) { + lib_abi = RuntimeIdentifierToAbi (rid); + } + + if (!string.IsNullOrWhiteSpace (lib_abi)) + return lib_abi; + + // If not resolved, use ItemSpec + return GetNativeLibraryAbi (lib.ItemSpec); + } + + /// + /// Converts .NET 5 RIDs to Android ABIs or an empty string if no match. + /// + /// Known RIDs: + /// "android.21-arm64" -> "arm64-v8a" + /// "android.21-arm" -> "armeabi-v7a" + /// "android.21-x86" -> "x86" + /// "android.21-x64" -> "x86_64" + /// "android-arm64" -> "arm64-v8a" + /// "android-arm" -> "armeabi-v7a" + /// "android-x86" -> "x86" + /// "android-x64" -> "x86_64" + /// + public static string RuntimeIdentifierToAbi (string runtimeIdentifier) + { + if (string.IsNullOrEmpty (runtimeIdentifier) || !runtimeIdentifier.StartsWith ("android", StringComparison.OrdinalIgnoreCase)) { + return ""; + } + if (runtimeIdentifier.EndsWith ("-arm64", StringComparison.OrdinalIgnoreCase)) { + return "arm64-v8a"; + } + if (runtimeIdentifier.EndsWith ("-arm", StringComparison.OrdinalIgnoreCase)) { + return "armeabi-v7a"; + } + if (runtimeIdentifier.EndsWith ("-x86", StringComparison.OrdinalIgnoreCase)) { + return "x86"; + } + if (runtimeIdentifier.EndsWith ("-x64", StringComparison.OrdinalIgnoreCase)) { + return "x86_64"; + } + return ""; + } + } +} diff --git a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/AndroidRunToolTask.cs b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/AndroidRunToolTask.cs new file mode 100644 index 00000000000..db8bf8b592d --- /dev/null +++ b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/AndroidRunToolTask.cs @@ -0,0 +1,76 @@ +// https://github.com/xamarin/xamarin-android/blob/0134c2fb20f2f20127b24ef49177d9fe8226efdb/src/Xamarin.Android.Build.Tasks/Tasks/AndroidToolTask.cs#L9 + +using System.IO; +using System.Text.RegularExpressions; +using Microsoft.Build.Framework; + +namespace Microsoft.Android.Build.Tasks +{ + public abstract class AndroidRunToolTask : AndroidToolTask + { + protected static bool IsWindows = Path.DirectorySeparatorChar == '\\'; + + protected abstract string DefaultErrorCode { get; } + + protected override void LogEventsFromTextOutput (string singleLine, MessageImportance messageImportance) + { + base.LogEventsFromTextOutput (singleLine, messageImportance); + + if (messageImportance != StandardErrorLoggingImportance) + return; + + Log.LogFromStandardError (DefaultErrorCode, singleLine); + } + + protected virtual Regex ErrorRegex { + get { return AndroidErrorRegex; } + } + + /* This gets pre-pended to any filenames that we get from error strings */ + protected string BaseDirectory { get; set; } + + // Aapt errors looks like this: + // res\layout\main.axml:7: error: No resource identifier found for attribute 'id2' in package 'android' (TaskId:22) + // Resources/values/theme.xml(2): error APT0000: Error retrieving parent for item: No resource found that matches the given name '@android:style/Theme.AppCompat'. + // Resources/values/theme.xml:2: error APT0000: Error retrieving parent for item: No resource found that matches the given name '@android:style/Theme.AppCompat'. + // res/drawable/foo-bar.jpg: Invalid file name: must contain only [a-z0-9_.] + // Warnings can be like this + // aapt2 W 09-17 18:15:27 98796 12879433 ApkAssets.cpp:138] resources.arsc in APK 'android.jar' is compressed. + // Look for them and convert them to MSBuild compatible errors. + static Regex androidErrorRegex; + public static Regex AndroidErrorRegex { + get { + if (androidErrorRegex == null) + androidErrorRegex = new Regex (@" +^ +( # start optional path followed by `:` + (? + (?.+[\\/][^:\(]+) + ( + ([:](?[\d ]+)) + | + (\((?[\d ]+)\)) + )? + ) + \s* + : +)? +( # optional warning|error|aapt2\sW|aapt2.exe\sW: + \s* + (?(warning|error|aapt2\sW|aapt2.exe\sW)[^:]*)\s* + : +)? +\s* +(?.*) +$ +", RegexOptions.Compiled | RegexOptions.ExplicitCapture | RegexOptions.IgnorePatternWhitespace | RegexOptions.IgnoreCase); + return androidErrorRegex; + } + } + + protected static string QuoteString (string value) + { + return string.Format ("\"{0}\"", value); + } + } +} diff --git a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/AndroidTask.cs b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/AndroidTask.cs new file mode 100644 index 00000000000..4b511c3ca0a --- /dev/null +++ b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/AndroidTask.cs @@ -0,0 +1,26 @@ +// https://github.com/xamarin/xamarin-android/blob/9fca138604c53989e1cff7fc0c2e939583b4da28/src/Xamarin.Android.Build.Tasks/Tasks/AndroidTask.cs#L10 + +using System; +using Microsoft.Build.Utilities; + +namespace Microsoft.Android.Build.Tasks +{ + // We use this task to ensure that no unhandled exceptions + // escape our tasks which would cause an MSB4018 + public abstract class AndroidTask : Task + { + public abstract string TaskPrefix { get; } + + public override bool Execute () + { + try { + return RunTask (); + } catch (Exception ex) { + Log.LogUnhandledException (TaskPrefix, ex); + return false; + } + } + + public abstract bool RunTask (); + } +} diff --git a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/AndroidToolTask.cs b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/AndroidToolTask.cs new file mode 100644 index 00000000000..38093a61a3c --- /dev/null +++ b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/AndroidToolTask.cs @@ -0,0 +1,26 @@ +// https://github.com/xamarin/xamarin-android/blob/9fca138604c53989e1cff7fc0c2e939583b4da28/src/Xamarin.Android.Build.Tasks/Tasks/AndroidTask.cs#L75 + +using System; +using Microsoft.Build.Utilities; + +namespace Microsoft.Android.Build.Tasks +{ + public abstract class AndroidToolTask : ToolTask + { + public abstract string TaskPrefix { get; } + + public override bool Execute () + { + try { + return RunTask (); + } catch (Exception ex) { + Log.LogUnhandledException (TaskPrefix, ex); + return false; + } + } + + // Most ToolTask's do not override Execute and + // just expect the base to be called + public virtual bool RunTask () => base.Execute (); + } +} diff --git a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/AsyncTaskExtensions.cs b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/AsyncTaskExtensions.cs new file mode 100644 index 00000000000..3911cc36890 --- /dev/null +++ b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/AsyncTaskExtensions.cs @@ -0,0 +1,106 @@ +// https://github.com/xamarin/xamarin-android/blob/83854738b8e01747f9536f426fe17ad784cc2081/src/Xamarin.Android.Build.Tasks/Utilities/AsyncTaskExtensions.cs + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Xamarin.Build; + +namespace Microsoft.Android.Build.Tasks +{ + public static class AsyncTaskExtensions + { + /// + /// Creates a collection of Task with proper CancellationToken and error handling and waits via Task.WhenAll + /// + public static Task WhenAll(this AsyncTask asyncTask, IEnumerable source, Action body) + { + var tasks = new List (); + foreach (var s in source) { + tasks.Add (Task.Run (() => { + try { + body (s); + } catch (Exception exc) { + LogErrorAndCancel (asyncTask, exc); + } + }, asyncTask.CancellationToken)); + } + return Task.WhenAll (tasks); + } + + /// + /// Creates a collection of Task with proper CancellationToken and error handling and waits via Task.WhenAll + /// Passes an object the inner method can use for locking. The callback is of the form: (T item, object lockObject) + /// + public static Task WhenAllWithLock (this AsyncTask asyncTask, IEnumerable source, Action body) + { + var lockObject = new object (); + var tasks = new List (); + foreach (var s in source) { + tasks.Add (Task.Run (() => { + try { + body (s, lockObject); + } catch (Exception exc) { + LogErrorAndCancel (asyncTask, exc); + } + }, asyncTask.CancellationToken)); + } + return Task.WhenAll (tasks); + } + + /// + /// Calls Parallel.ForEach() with appropriate ParallelOptions and exception handling. + /// + public static ParallelLoopResult ParallelForEach (this AsyncTask asyncTask, IEnumerable source, Action body) + { + var options = ParallelOptions (asyncTask); + return Parallel.ForEach (source, options, s => { + try { + body (s); + } catch (Exception exc) { + LogErrorAndCancel (asyncTask, exc); + } + }); + } + + /// + /// Calls Parallel.ForEach() with appropriate ParallelOptions and exception handling. + /// Passes an object the inner method can use for locking. The callback is of the form: (T item, object lockObject) + /// + public static ParallelLoopResult ParallelForEachWithLock (this AsyncTask asyncTask, IEnumerable source, Action body) + { + var options = ParallelOptions (asyncTask); + var lockObject = new object (); + return Parallel.ForEach (source, options, s => { + try { + body (s, lockObject); + } catch (Exception exc) { + LogErrorAndCancel (asyncTask, exc); + } + }); + } + + static ParallelOptions ParallelOptions (AsyncTask asyncTask) => new ParallelOptions { + CancellationToken = asyncTask.CancellationToken, + TaskScheduler = TaskScheduler.Default, + }; + + static void LogErrorAndCancel (AsyncTask asyncTask, Exception exc) + { + asyncTask.LogCodedError ("XA0000", Properties.Resources.XA0000_Exception, exc); + asyncTask.Cancel (); + } + + /// + /// Calls Task.Run() with a proper CancellationToken. + /// + public static Task RunTask (this AsyncTask asyncTask, Action body) => + Task.Run (body, asyncTask.CancellationToken); + + + /// + /// Calls Task.Run() with a proper CancellationToken. + /// + public static Task RunTask (this AsyncTask asyncTask, Func body) => + Task.Run (body, asyncTask.CancellationToken); + } +} diff --git a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Crc64.Table.cs b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Crc64.Table.cs new file mode 100644 index 00000000000..20f94499bbf --- /dev/null +++ b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Crc64.Table.cs @@ -0,0 +1,538 @@ +// https://github.com/xamarin/java.interop/blob/7d197f17a0f9d73854522d6e1d68deafbcdbcaf6/src/Java.Interop.Tools.JavaCallableWrappers/Java.Interop.Tools.JavaCallableWrappers/Crc64.Table.cs + +namespace Microsoft.Android.Build.Tasks +{ + partial class Crc64Helper + { + static readonly ulong[,] table = { + { + 0x0000000000000000, 0x7ad870c830358979, 0xf5b0e190606b12f2, 0x8f689158505e9b8b, + 0xc038e5739841b68f, 0xbae095bba8743ff6, 0x358804e3f82aa47d, 0x4f50742bc81f2d04, + 0xab28ecb46814fe75, 0xd1f09c7c5821770c, 0x5e980d24087fec87, 0x24407dec384a65fe, + 0x6b1009c7f05548fa, 0x11c8790fc060c183, 0x9ea0e857903e5a08, 0xe478989fa00bd371, + 0x7d08ff3b88be6f81, 0x07d08ff3b88be6f8, 0x88b81eabe8d57d73, 0xf2606e63d8e0f40a, + 0xbd301a4810ffd90e, 0xc7e86a8020ca5077, 0x4880fbd87094cbfc, 0x32588b1040a14285, + 0xd620138fe0aa91f4, 0xacf86347d09f188d, 0x2390f21f80c18306, 0x594882d7b0f40a7f, + 0x1618f6fc78eb277b, 0x6cc0863448deae02, 0xe3a8176c18803589, 0x997067a428b5bcf0, + 0xfa11fe77117cdf02, 0x80c98ebf2149567b, 0x0fa11fe77117cdf0, 0x75796f2f41224489, + 0x3a291b04893d698d, 0x40f16bccb908e0f4, 0xcf99fa94e9567b7f, 0xb5418a5cd963f206, + 0x513912c379682177, 0x2be1620b495da80e, 0xa489f35319033385, 0xde51839b2936bafc, + 0x9101f7b0e12997f8, 0xebd98778d11c1e81, 0x64b116208142850a, 0x1e6966e8b1770c73, + 0x8719014c99c2b083, 0xfdc17184a9f739fa, 0x72a9e0dcf9a9a271, 0x08719014c99c2b08, + 0x4721e43f0183060c, 0x3df994f731b68f75, 0xb29105af61e814fe, 0xc849756751dd9d87, + 0x2c31edf8f1d64ef6, 0x56e99d30c1e3c78f, 0xd9810c6891bd5c04, 0xa3597ca0a188d57d, + 0xec09088b6997f879, 0x96d1784359a27100, 0x19b9e91b09fcea8b, 0x636199d339c963f2, + 0xdf7adabd7a6e2d6f, 0xa5a2aa754a5ba416, 0x2aca3b2d1a053f9d, 0x50124be52a30b6e4, + 0x1f423fcee22f9be0, 0x659a4f06d21a1299, 0xeaf2de5e82448912, 0x902aae96b271006b, + 0x74523609127ad31a, 0x0e8a46c1224f5a63, 0x81e2d7997211c1e8, 0xfb3aa75142244891, + 0xb46ad37a8a3b6595, 0xceb2a3b2ba0eecec, 0x41da32eaea507767, 0x3b024222da65fe1e, + 0xa2722586f2d042ee, 0xd8aa554ec2e5cb97, 0x57c2c41692bb501c, 0x2d1ab4dea28ed965, + 0x624ac0f56a91f461, 0x1892b03d5aa47d18, 0x97fa21650afae693, 0xed2251ad3acf6fea, + 0x095ac9329ac4bc9b, 0x7382b9faaaf135e2, 0xfcea28a2faafae69, 0x8632586aca9a2710, + 0xc9622c4102850a14, 0xb3ba5c8932b0836d, 0x3cd2cdd162ee18e6, 0x460abd1952db919f, + 0x256b24ca6b12f26d, 0x5fb354025b277b14, 0xd0dbc55a0b79e09f, 0xaa03b5923b4c69e6, + 0xe553c1b9f35344e2, 0x9f8bb171c366cd9b, 0x10e3202993385610, 0x6a3b50e1a30ddf69, + 0x8e43c87e03060c18, 0xf49bb8b633338561, 0x7bf329ee636d1eea, 0x012b592653589793, + 0x4e7b2d0d9b47ba97, 0x34a35dc5ab7233ee, 0xbbcbcc9dfb2ca865, 0xc113bc55cb19211c, + 0x5863dbf1e3ac9dec, 0x22bbab39d3991495, 0xadd33a6183c78f1e, 0xd70b4aa9b3f20667, + 0x985b3e827bed2b63, 0xe2834e4a4bd8a21a, 0x6debdf121b863991, 0x1733afda2bb3b0e8, + 0xf34b37458bb86399, 0x8993478dbb8deae0, 0x06fbd6d5ebd3716b, 0x7c23a61ddbe6f812, + 0x3373d23613f9d516, 0x49aba2fe23cc5c6f, 0xc6c333a67392c7e4, 0xbc1b436e43a74e9d, + 0x95ac9329ac4bc9b5, 0xef74e3e19c7e40cc, 0x601c72b9cc20db47, 0x1ac40271fc15523e, + 0x5594765a340a7f3a, 0x2f4c0692043ff643, 0xa02497ca54616dc8, 0xdafce7026454e4b1, + 0x3e847f9dc45f37c0, 0x445c0f55f46abeb9, 0xcb349e0da4342532, 0xb1eceec59401ac4b, + 0xfebc9aee5c1e814f, 0x8464ea266c2b0836, 0x0b0c7b7e3c7593bd, 0x71d40bb60c401ac4, + 0xe8a46c1224f5a634, 0x927c1cda14c02f4d, 0x1d148d82449eb4c6, 0x67ccfd4a74ab3dbf, + 0x289c8961bcb410bb, 0x5244f9a98c8199c2, 0xdd2c68f1dcdf0249, 0xa7f41839ecea8b30, + 0x438c80a64ce15841, 0x3954f06e7cd4d138, 0xb63c61362c8a4ab3, 0xcce411fe1cbfc3ca, + 0x83b465d5d4a0eece, 0xf96c151de49567b7, 0x76048445b4cbfc3c, 0x0cdcf48d84fe7545, + 0x6fbd6d5ebd3716b7, 0x15651d968d029fce, 0x9a0d8ccedd5c0445, 0xe0d5fc06ed698d3c, + 0xaf85882d2576a038, 0xd55df8e515432941, 0x5a3569bd451db2ca, 0x20ed197575283bb3, + 0xc49581ead523e8c2, 0xbe4df122e51661bb, 0x3125607ab548fa30, 0x4bfd10b2857d7349, + 0x04ad64994d625e4d, 0x7e7514517d57d734, 0xf11d85092d094cbf, 0x8bc5f5c11d3cc5c6, + 0x12b5926535897936, 0x686de2ad05bcf04f, 0xe70573f555e26bc4, 0x9ddd033d65d7e2bd, + 0xd28d7716adc8cfb9, 0xa85507de9dfd46c0, 0x273d9686cda3dd4b, 0x5de5e64efd965432, + 0xb99d7ed15d9d8743, 0xc3450e196da80e3a, 0x4c2d9f413df695b1, 0x36f5ef890dc31cc8, + 0x79a59ba2c5dc31cc, 0x037deb6af5e9b8b5, 0x8c157a32a5b7233e, 0xf6cd0afa9582aa47, + 0x4ad64994d625e4da, 0x300e395ce6106da3, 0xbf66a804b64ef628, 0xc5bed8cc867b7f51, + 0x8aeeace74e645255, 0xf036dc2f7e51db2c, 0x7f5e4d772e0f40a7, 0x05863dbf1e3ac9de, + 0xe1fea520be311aaf, 0x9b26d5e88e0493d6, 0x144e44b0de5a085d, 0x6e963478ee6f8124, + 0x21c640532670ac20, 0x5b1e309b16452559, 0xd476a1c3461bbed2, 0xaeaed10b762e37ab, + 0x37deb6af5e9b8b5b, 0x4d06c6676eae0222, 0xc26e573f3ef099a9, 0xb8b627f70ec510d0, + 0xf7e653dcc6da3dd4, 0x8d3e2314f6efb4ad, 0x0256b24ca6b12f26, 0x788ec2849684a65f, + 0x9cf65a1b368f752e, 0xe62e2ad306bafc57, 0x6946bb8b56e467dc, 0x139ecb4366d1eea5, + 0x5ccebf68aecec3a1, 0x2616cfa09efb4ad8, 0xa97e5ef8cea5d153, 0xd3a62e30fe90582a, + 0xb0c7b7e3c7593bd8, 0xca1fc72bf76cb2a1, 0x45775673a732292a, 0x3faf26bb9707a053, + 0x70ff52905f188d57, 0x0a2722586f2d042e, 0x854fb3003f739fa5, 0xff97c3c80f4616dc, + 0x1bef5b57af4dc5ad, 0x61372b9f9f784cd4, 0xee5fbac7cf26d75f, 0x9487ca0fff135e26, + 0xdbd7be24370c7322, 0xa10fceec0739fa5b, 0x2e675fb4576761d0, 0x54bf2f7c6752e8a9, + 0xcdcf48d84fe75459, 0xb71738107fd2dd20, 0x387fa9482f8c46ab, 0x42a7d9801fb9cfd2, + 0x0df7adabd7a6e2d6, 0x772fdd63e7936baf, 0xf8474c3bb7cdf024, 0x829f3cf387f8795d, + 0x66e7a46c27f3aa2c, 0x1c3fd4a417c62355, 0x935745fc4798b8de, 0xe98f353477ad31a7, + 0xa6df411fbfb21ca3, 0xdc0731d78f8795da, 0x536fa08fdfd90e51, 0x29b7d047efec8728 + }, + { + 0x0000000000000000, 0x89e99ffd73bddf69, 0x388a19a9bfec2db9, 0xb1638654cc51f2d0, + 0x711433537fd85b72, 0xf8fdacae0c65841b, 0x499e2afac03476cb, 0xc077b507b389a9a2, + 0xe22866a6ffb0b6e4, 0x6bc1f95b8c0d698d, 0xdaa27f0f405c9b5d, 0x534be0f233e14434, + 0x933c55f58068ed96, 0x1ad5ca08f3d532ff, 0xabb64c5c3f84c02f, 0x225fd3a14c391f46, + 0xef09eb1ea7f6fea3, 0x66e074e3d44b21ca, 0xd783f2b7181ad31a, 0x5e6a6d4a6ba70c73, + 0x9e1dd84dd82ea5d1, 0x17f447b0ab937ab8, 0xa697c1e467c28868, 0x2f7e5e19147f5701, + 0x0d218db858464847, 0x84c812452bfb972e, 0x35ab9411e7aa65fe, 0xbc420bec9417ba97, + 0x7c35beeb279e1335, 0xf5dc21165423cc5c, 0x44bfa74298723e8c, 0xcd5638bfebcfe1e5, + 0xf54af06e177a6e2d, 0x7ca36f9364c7b144, 0xcdc0e9c7a8964394, 0x4429763adb2b9cfd, + 0x845ec33d68a2355f, 0x0db75cc01b1fea36, 0xbcd4da94d74e18e6, 0x353d4569a4f3c78f, + 0x176296c8e8cad8c9, 0x9e8b09359b7707a0, 0x2fe88f615726f570, 0xa601109c249b2a19, + 0x6676a59b971283bb, 0xef9f3a66e4af5cd2, 0x5efcbc3228feae02, 0xd71523cf5b43716b, + 0x1a431b70b08c908e, 0x93aa848dc3314fe7, 0x22c902d90f60bd37, 0xab209d247cdd625e, + 0x6b572823cf54cbfc, 0xe2beb7debce91495, 0x53dd318a70b8e645, 0xda34ae770305392c, + 0xf86b7dd64f3c266a, 0x7182e22b3c81f903, 0xc0e1647ff0d00bd3, 0x4908fb82836dd4ba, + 0x897f4e8530e47d18, 0x0096d1784359a271, 0xb1f5572c8f0850a1, 0x381cc8d1fcb58fc8, + 0xc1ccc68f76634f31, 0x4825597205de9058, 0xf946df26c98f6288, 0x70af40dbba32bde1, + 0xb0d8f5dc09bb1443, 0x39316a217a06cb2a, 0x8852ec75b65739fa, 0x01bb7388c5eae693, + 0x23e4a02989d3f9d5, 0xaa0d3fd4fa6e26bc, 0x1b6eb980363fd46c, 0x9287267d45820b05, + 0x52f0937af60ba2a7, 0xdb190c8785b67dce, 0x6a7a8ad349e78f1e, 0xe393152e3a5a5077, + 0x2ec52d91d195b192, 0xa72cb26ca2286efb, 0x164f34386e799c2b, 0x9fa6abc51dc44342, + 0x5fd11ec2ae4deae0, 0xd638813fddf03589, 0x675b076b11a1c759, 0xeeb29896621c1830, + 0xcced4b372e250776, 0x4504d4ca5d98d81f, 0xf467529e91c92acf, 0x7d8ecd63e274f5a6, + 0xbdf9786451fd5c04, 0x3410e7992240836d, 0x857361cdee1171bd, 0x0c9afe309dacaed4, + 0x348636e16119211c, 0xbd6fa91c12a4fe75, 0x0c0c2f48def50ca5, 0x85e5b0b5ad48d3cc, + 0x459205b21ec17a6e, 0xcc7b9a4f6d7ca507, 0x7d181c1ba12d57d7, 0xf4f183e6d29088be, + 0xd6ae50479ea997f8, 0x5f47cfbaed144891, 0xee2449ee2145ba41, 0x67cdd61352f86528, + 0xa7ba6314e171cc8a, 0x2e53fce992cc13e3, 0x9f307abd5e9de133, 0x16d9e5402d203e5a, + 0xdb8fddffc6efdfbf, 0x52664202b55200d6, 0xe305c4567903f206, 0x6aec5bab0abe2d6f, + 0xaa9beeacb93784cd, 0x23727151ca8a5ba4, 0x9211f70506dba974, 0x1bf868f87566761d, + 0x39a7bb59395f695b, 0xb04e24a44ae2b632, 0x012da2f086b344e2, 0x88c43d0df50e9b8b, + 0x48b3880a46873229, 0xc15a17f7353aed40, 0x703991a3f96b1f90, 0xf9d00e5e8ad6c0f9, + 0xa8c0ab4db4510d09, 0x212934b0c7ecd260, 0x904ab2e40bbd20b0, 0x19a32d197800ffd9, + 0xd9d4981ecb89567b, 0x503d07e3b8348912, 0xe15e81b774657bc2, 0x68b71e4a07d8a4ab, + 0x4ae8cdeb4be1bbed, 0xc3015216385c6484, 0x7262d442f40d9654, 0xfb8b4bbf87b0493d, + 0x3bfcfeb83439e09f, 0xb215614547843ff6, 0x0376e7118bd5cd26, 0x8a9f78ecf868124f, + 0x47c9405313a7f3aa, 0xce20dfae601a2cc3, 0x7f4359faac4bde13, 0xf6aac607dff6017a, + 0x36dd73006c7fa8d8, 0xbf34ecfd1fc277b1, 0x0e576aa9d3938561, 0x87bef554a02e5a08, + 0xa5e126f5ec17454e, 0x2c08b9089faa9a27, 0x9d6b3f5c53fb68f7, 0x1482a0a12046b79e, + 0xd4f515a693cf1e3c, 0x5d1c8a5be072c155, 0xec7f0c0f2c233385, 0x659693f25f9eecec, + 0x5d8a5b23a32b6324, 0xd463c4ded096bc4d, 0x6500428a1cc74e9d, 0xece9dd776f7a91f4, + 0x2c9e6870dcf33856, 0xa577f78daf4ee73f, 0x141471d9631f15ef, 0x9dfdee2410a2ca86, + 0xbfa23d855c9bd5c0, 0x364ba2782f260aa9, 0x8728242ce377f879, 0x0ec1bbd190ca2710, + 0xceb60ed623438eb2, 0x475f912b50fe51db, 0xf63c177f9cafa30b, 0x7fd58882ef127c62, + 0xb283b03d04dd9d87, 0x3b6a2fc0776042ee, 0x8a09a994bb31b03e, 0x03e03669c88c6f57, + 0xc397836e7b05c6f5, 0x4a7e1c9308b8199c, 0xfb1d9ac7c4e9eb4c, 0x72f4053ab7543425, + 0x50abd69bfb6d2b63, 0xd942496688d0f40a, 0x6821cf32448106da, 0xe1c850cf373cd9b3, + 0x21bfe5c884b57011, 0xa8567a35f708af78, 0x1935fc613b595da8, 0x90dc639c48e482c1, + 0x690c6dc2c2324238, 0xe0e5f23fb18f9d51, 0x5186746b7dde6f81, 0xd86feb960e63b0e8, + 0x18185e91bdea194a, 0x91f1c16cce57c623, 0x20924738020634f3, 0xa97bd8c571bbeb9a, + 0x8b240b643d82f4dc, 0x02cd94994e3f2bb5, 0xb3ae12cd826ed965, 0x3a478d30f1d3060c, + 0xfa303837425aafae, 0x73d9a7ca31e770c7, 0xc2ba219efdb68217, 0x4b53be638e0b5d7e, + 0x860586dc65c4bc9b, 0x0fec1921167963f2, 0xbe8f9f75da289122, 0x37660088a9954e4b, + 0xf711b58f1a1ce7e9, 0x7ef82a7269a13880, 0xcf9bac26a5f0ca50, 0x467233dbd64d1539, + 0x642de07a9a740a7f, 0xedc47f87e9c9d516, 0x5ca7f9d3259827c6, 0xd54e662e5625f8af, + 0x1539d329e5ac510d, 0x9cd04cd496118e64, 0x2db3ca805a407cb4, 0xa45a557d29fda3dd, + 0x9c469dacd5482c15, 0x15af0251a6f5f37c, 0xa4cc84056aa401ac, 0x2d251bf81919dec5, + 0xed52aeffaa907767, 0x64bb3102d92da80e, 0xd5d8b756157c5ade, 0x5c3128ab66c185b7, + 0x7e6efb0a2af89af1, 0xf78764f759454598, 0x46e4e2a39514b748, 0xcf0d7d5ee6a96821, + 0x0f7ac8595520c183, 0x869357a4269d1eea, 0x37f0d1f0eaccec3a, 0xbe194e0d99713353, + 0x734f76b272bed2b6, 0xfaa6e94f01030ddf, 0x4bc56f1bcd52ff0f, 0xc22cf0e6beef2066, + 0x025b45e10d6689c4, 0x8bb2da1c7edb56ad, 0x3ad15c48b28aa47d, 0xb338c3b5c1377b14, + 0x916710148d0e6452, 0x188e8fe9feb3bb3b, 0xa9ed09bd32e249eb, 0x20049640415f9682, + 0xe0732347f2d63f20, 0x699abcba816be049, 0xd8f93aee4d3a1299, 0x5110a5133e87cdf0 + }, + { + 0x0000000000000000, 0xf4125129ce4038be, 0xc37d8400c417e217, 0x376fd5290a57daa9, + 0xada22e52d0b85745, 0x59b07f7b1ef86ffb, 0x6edfaa5214afb552, 0x9acdfb7bdaef8dec, + 0x701d7af6f9e73de1, 0x840f2bdf37a7055f, 0xb360fef63df0dff6, 0x4772afdff3b0e748, + 0xddbf54a4295f6aa4, 0x29ad058de71f521a, 0x1ec2d0a4ed4888b3, 0xead0818d2308b00d, + 0xe03af5edf3ce7bc2, 0x1428a4c43d8e437c, 0x234771ed37d999d5, 0xd75520c4f999a16b, + 0x4d98dbbf23762c87, 0xb98a8a96ed361439, 0x8ee55fbfe761ce90, 0x7af70e962921f62e, + 0x90278f1b0a294623, 0x6435de32c4697e9d, 0x535a0b1bce3ea434, 0xa7485a32007e9c8a, + 0x3d85a149da911166, 0xc997f06014d129d8, 0xfef825491e86f371, 0x0aea7460d0c6cbcf, + 0xeb2ccd88bf0b64ef, 0x1f3e9ca1714b5c51, 0x285149887b1c86f8, 0xdc4318a1b55cbe46, + 0x468ee3da6fb333aa, 0xb29cb2f3a1f30b14, 0x85f367daaba4d1bd, 0x71e136f365e4e903, + 0x9b31b77e46ec590e, 0x6f23e65788ac61b0, 0x584c337e82fbbb19, 0xac5e62574cbb83a7, + 0x3693992c96540e4b, 0xc281c805581436f5, 0xf5ee1d2c5243ec5c, 0x01fc4c059c03d4e2, + 0x0b1638654cc51f2d, 0xff04694c82852793, 0xc86bbc6588d2fd3a, 0x3c79ed4c4692c584, + 0xa6b416379c7d4868, 0x52a6471e523d70d6, 0x65c99237586aaa7f, 0x91dbc31e962a92c1, + 0x7b0b4293b52222cc, 0x8f1913ba7b621a72, 0xb876c6937135c0db, 0x4c6497babf75f865, + 0xd6a96cc1659a7589, 0x22bb3de8abda4d37, 0x15d4e8c1a18d979e, 0xe1c6b9e86fcdaf20, + 0xfd00bd4226815ab5, 0x0912ec6be8c1620b, 0x3e7d3942e296b8a2, 0xca6f686b2cd6801c, + 0x50a29310f6390df0, 0xa4b0c2393879354e, 0x93df1710322eefe7, 0x67cd4639fc6ed759, + 0x8d1dc7b4df666754, 0x790f969d11265fea, 0x4e6043b41b718543, 0xba72129dd531bdfd, + 0x20bfe9e60fde3011, 0xd4adb8cfc19e08af, 0xe3c26de6cbc9d206, 0x17d03ccf0589eab8, + 0x1d3a48afd54f2177, 0xe92819861b0f19c9, 0xde47ccaf1158c360, 0x2a559d86df18fbde, + 0xb09866fd05f77632, 0x448a37d4cbb74e8c, 0x73e5e2fdc1e09425, 0x87f7b3d40fa0ac9b, + 0x6d2732592ca81c96, 0x99356370e2e82428, 0xae5ab659e8bffe81, 0x5a48e77026ffc63f, + 0xc0851c0bfc104bd3, 0x34974d223250736d, 0x03f8980b3807a9c4, 0xf7eac922f647917a, + 0x162c70ca998a3e5a, 0xe23e21e357ca06e4, 0xd551f4ca5d9ddc4d, 0x2143a5e393dde4f3, + 0xbb8e5e984932691f, 0x4f9c0fb1877251a1, 0x78f3da988d258b08, 0x8ce18bb14365b3b6, + 0x66310a3c606d03bb, 0x92235b15ae2d3b05, 0xa54c8e3ca47ae1ac, 0x515edf156a3ad912, + 0xcb93246eb0d554fe, 0x3f8175477e956c40, 0x08eea06e74c2b6e9, 0xfcfcf147ba828e57, + 0xf61685276a444598, 0x0204d40ea4047d26, 0x356b0127ae53a78f, 0xc179500e60139f31, + 0x5bb4ab75bafc12dd, 0xafa6fa5c74bc2a63, 0x98c92f757eebf0ca, 0x6cdb7e5cb0abc874, + 0x860bffd193a37879, 0x7219aef85de340c7, 0x45767bd157b49a6e, 0xb1642af899f4a2d0, + 0x2ba9d183431b2f3c, 0xdfbb80aa8d5b1782, 0xe8d45583870ccd2b, 0x1cc604aa494cf595, + 0xd1585cd715952601, 0x254a0dfedbd51ebf, 0x1225d8d7d182c416, 0xe63789fe1fc2fca8, + 0x7cfa7285c52d7144, 0x88e823ac0b6d49fa, 0xbf87f685013a9353, 0x4b95a7accf7aabed, + 0xa1452621ec721be0, 0x555777082232235e, 0x6238a2212865f9f7, 0x962af308e625c149, + 0x0ce708733cca4ca5, 0xf8f5595af28a741b, 0xcf9a8c73f8ddaeb2, 0x3b88dd5a369d960c, + 0x3162a93ae65b5dc3, 0xc570f813281b657d, 0xf21f2d3a224cbfd4, 0x060d7c13ec0c876a, + 0x9cc0876836e30a86, 0x68d2d641f8a33238, 0x5fbd0368f2f4e891, 0xabaf52413cb4d02f, + 0x417fd3cc1fbc6022, 0xb56d82e5d1fc589c, 0x820257ccdbab8235, 0x761006e515ebba8b, + 0xecddfd9ecf043767, 0x18cfacb701440fd9, 0x2fa0799e0b13d570, 0xdbb228b7c553edce, + 0x3a74915faa9e42ee, 0xce66c07664de7a50, 0xf909155f6e89a0f9, 0x0d1b4476a0c99847, + 0x97d6bf0d7a2615ab, 0x63c4ee24b4662d15, 0x54ab3b0dbe31f7bc, 0xa0b96a247071cf02, + 0x4a69eba953797f0f, 0xbe7bba809d3947b1, 0x89146fa9976e9d18, 0x7d063e80592ea5a6, + 0xe7cbc5fb83c1284a, 0x13d994d24d8110f4, 0x24b641fb47d6ca5d, 0xd0a410d28996f2e3, + 0xda4e64b25950392c, 0x2e5c359b97100192, 0x1933e0b29d47db3b, 0xed21b19b5307e385, + 0x77ec4ae089e86e69, 0x83fe1bc947a856d7, 0xb491cee04dff8c7e, 0x40839fc983bfb4c0, + 0xaa531e44a0b704cd, 0x5e414f6d6ef73c73, 0x692e9a4464a0e6da, 0x9d3ccb6daae0de64, + 0x07f13016700f5388, 0xf3e3613fbe4f6b36, 0xc48cb416b418b19f, 0x309ee53f7a588921, + 0x2c58e19533147cb4, 0xd84ab0bcfd54440a, 0xef256595f7039ea3, 0x1b3734bc3943a61d, + 0x81facfc7e3ac2bf1, 0x75e89eee2dec134f, 0x42874bc727bbc9e6, 0xb6951aeee9fbf158, + 0x5c459b63caf34155, 0xa857ca4a04b379eb, 0x9f381f630ee4a342, 0x6b2a4e4ac0a49bfc, + 0xf1e7b5311a4b1610, 0x05f5e418d40b2eae, 0x329a3131de5cf407, 0xc6886018101cccb9, + 0xcc621478c0da0776, 0x387045510e9a3fc8, 0x0f1f907804cde561, 0xfb0dc151ca8ddddf, + 0x61c03a2a10625033, 0x95d26b03de22688d, 0xa2bdbe2ad475b224, 0x56afef031a358a9a, + 0xbc7f6e8e393d3a97, 0x486d3fa7f77d0229, 0x7f02ea8efd2ad880, 0x8b10bba7336ae03e, + 0x11dd40dce9856dd2, 0xe5cf11f527c5556c, 0xd2a0c4dc2d928fc5, 0x26b295f5e3d2b77b, + 0xc7742c1d8c1f185b, 0x33667d34425f20e5, 0x0409a81d4808fa4c, 0xf01bf9348648c2f2, + 0x6ad6024f5ca74f1e, 0x9ec4536692e777a0, 0xa9ab864f98b0ad09, 0x5db9d76656f095b7, + 0xb76956eb75f825ba, 0x437b07c2bbb81d04, 0x7414d2ebb1efc7ad, 0x800683c27fafff13, + 0x1acb78b9a54072ff, 0xeed929906b004a41, 0xd9b6fcb9615790e8, 0x2da4ad90af17a856, + 0x274ed9f07fd16399, 0xd35c88d9b1915b27, 0xe4335df0bbc6818e, 0x10210cd97586b930, + 0x8aecf7a2af6934dc, 0x7efea68b61290c62, 0x499173a26b7ed6cb, 0xbd83228ba53eee75, + 0x5753a30686365e78, 0xa341f22f487666c6, 0x942e27064221bc6f, 0x603c762f8c6184d1, + 0xfaf18d54568e093d, 0x0ee3dc7d98ce3183, 0x398c09549299eb2a, 0xcd9e587d5cd9d394 + }, + { + 0x0000000000000000, 0x8ce168638c796306, 0x329bf69440655567, 0xbe7a9ef7cc1c3661, + 0x6537ed2880caaace, 0xe9d6854b0cb3c9c8, 0x57ac1bbcc0afffa9, 0xdb4d73df4cd69caf, + 0xca6fda510195559c, 0x468eb2328dec369a, 0xf8f42cc541f000fb, 0x741544a6cd8963fd, + 0xaf583779815fff52, 0x23b95f1a0d269c54, 0x9dc3c1edc13aaa35, 0x1122a98e4d43c933, + 0xbf8692f15bbd3853, 0x3367fa92d7c45b55, 0x8d1d64651bd86d34, 0x01fc0c0697a10e32, + 0xdab17fd9db77929d, 0x565017ba570ef19b, 0xe82a894d9b12c7fa, 0x64cbe12e176ba4fc, + 0x75e948a05a286dcf, 0xf90820c3d6510ec9, 0x4772be341a4d38a8, 0xcb93d65796345bae, + 0x10dea588dae2c701, 0x9c3fcdeb569ba407, 0x2245531c9a879266, 0xaea43b7f16fef160, + 0x545403b1efede3cd, 0xd8b56bd2639480cb, 0x66cff525af88b6aa, 0xea2e9d4623f1d5ac, + 0x3163ee996f274903, 0xbd8286fae35e2a05, 0x03f8180d2f421c64, 0x8f19706ea33b7f62, + 0x9e3bd9e0ee78b651, 0x12dab1836201d557, 0xaca02f74ae1de336, 0x2041471722648030, + 0xfb0c34c86eb21c9f, 0x77ed5cabe2cb7f99, 0xc997c25c2ed749f8, 0x4576aa3fa2ae2afe, + 0xebd29140b450db9e, 0x6733f9233829b898, 0xd94967d4f4358ef9, 0x55a80fb7784cedff, + 0x8ee57c68349a7150, 0x0204140bb8e31256, 0xbc7e8afc74ff2437, 0x309fe29ff8864731, + 0x21bd4b11b5c58e02, 0xad5c237239bced04, 0x1326bd85f5a0db65, 0x9fc7d5e679d9b863, + 0x448aa639350f24cc, 0xc86bce5ab97647ca, 0x761150ad756a71ab, 0xfaf038cef91312ad, + 0xa8a80763dfdbc79a, 0x24496f0053a2a49c, 0x9a33f1f79fbe92fd, 0x16d2999413c7f1fb, + 0xcd9fea4b5f116d54, 0x417e8228d3680e52, 0xff041cdf1f743833, 0x73e574bc930d5b35, + 0x62c7dd32de4e9206, 0xee26b5515237f100, 0x505c2ba69e2bc761, 0xdcbd43c51252a467, + 0x07f0301a5e8438c8, 0x8b115879d2fd5bce, 0x356bc68e1ee16daf, 0xb98aaeed92980ea9, + 0x172e95928466ffc9, 0x9bcffdf1081f9ccf, 0x25b56306c403aaae, 0xa9540b65487ac9a8, + 0x721978ba04ac5507, 0xfef810d988d53601, 0x40828e2e44c90060, 0xcc63e64dc8b06366, + 0xdd414fc385f3aa55, 0x51a027a0098ac953, 0xefdab957c596ff32, 0x633bd13449ef9c34, + 0xb876a2eb0539009b, 0x3497ca888940639d, 0x8aed547f455c55fc, 0x060c3c1cc92536fa, + 0xfcfc04d230362457, 0x701d6cb1bc4f4751, 0xce67f24670537130, 0x42869a25fc2a1236, + 0x99cbe9fab0fc8e99, 0x152a81993c85ed9f, 0xab501f6ef099dbfe, 0x27b1770d7ce0b8f8, + 0x3693de8331a371cb, 0xba72b6e0bdda12cd, 0x0408281771c624ac, 0x88e94074fdbf47aa, + 0x53a433abb169db05, 0xdf455bc83d10b803, 0x613fc53ff10c8e62, 0xeddead5c7d75ed64, + 0x437a96236b8b1c04, 0xcf9bfe40e7f27f02, 0x71e160b72bee4963, 0xfd0008d4a7972a65, + 0x264d7b0beb41b6ca, 0xaaac13686738d5cc, 0x14d68d9fab24e3ad, 0x9837e5fc275d80ab, + 0x89154c726a1e4998, 0x05f42411e6672a9e, 0xbb8ebae62a7b1cff, 0x376fd285a6027ff9, + 0xec22a15aead4e356, 0x60c3c93966ad8050, 0xdeb957ceaab1b631, 0x52583fad26c8d537, + 0x7a092894e7201c5f, 0xf6e840f76b597f59, 0x4892de00a7454938, 0xc473b6632b3c2a3e, + 0x1f3ec5bc67eab691, 0x93dfaddfeb93d597, 0x2da53328278fe3f6, 0xa1445b4babf680f0, + 0xb066f2c5e6b549c3, 0x3c879aa66acc2ac5, 0x82fd0451a6d01ca4, 0x0e1c6c322aa97fa2, + 0xd5511fed667fe30d, 0x59b0778eea06800b, 0xe7cae979261ab66a, 0x6b2b811aaa63d56c, + 0xc58fba65bc9d240c, 0x496ed20630e4470a, 0xf7144cf1fcf8716b, 0x7bf524927081126d, + 0xa0b8574d3c578ec2, 0x2c593f2eb02eedc4, 0x9223a1d97c32dba5, 0x1ec2c9baf04bb8a3, + 0x0fe06034bd087190, 0x8301085731711296, 0x3d7b96a0fd6d24f7, 0xb19afec3711447f1, + 0x6ad78d1c3dc2db5e, 0xe636e57fb1bbb858, 0x584c7b887da78e39, 0xd4ad13ebf1deed3f, + 0x2e5d2b2508cdff92, 0xa2bc434684b49c94, 0x1cc6ddb148a8aaf5, 0x9027b5d2c4d1c9f3, + 0x4b6ac60d8807555c, 0xc78bae6e047e365a, 0x79f13099c862003b, 0xf51058fa441b633d, + 0xe432f1740958aa0e, 0x68d399178521c908, 0xd6a907e0493dff69, 0x5a486f83c5449c6f, + 0x81051c5c899200c0, 0x0de4743f05eb63c6, 0xb39eeac8c9f755a7, 0x3f7f82ab458e36a1, + 0x91dbb9d45370c7c1, 0x1d3ad1b7df09a4c7, 0xa3404f40131592a6, 0x2fa127239f6cf1a0, + 0xf4ec54fcd3ba6d0f, 0x780d3c9f5fc30e09, 0xc677a26893df3868, 0x4a96ca0b1fa65b6e, + 0x5bb4638552e5925d, 0xd7550be6de9cf15b, 0x692f95111280c73a, 0xe5cefd729ef9a43c, + 0x3e838eadd22f3893, 0xb262e6ce5e565b95, 0x0c187839924a6df4, 0x80f9105a1e330ef2, + 0xd2a12ff738fbdbc5, 0x5e404794b482b8c3, 0xe03ad963789e8ea2, 0x6cdbb100f4e7eda4, + 0xb796c2dfb831710b, 0x3b77aabc3448120d, 0x850d344bf854246c, 0x09ec5c28742d476a, + 0x18cef5a6396e8e59, 0x942f9dc5b517ed5f, 0x2a550332790bdb3e, 0xa6b46b51f572b838, + 0x7df9188eb9a42497, 0xf11870ed35dd4791, 0x4f62ee1af9c171f0, 0xc383867975b812f6, + 0x6d27bd066346e396, 0xe1c6d565ef3f8090, 0x5fbc4b922323b6f1, 0xd35d23f1af5ad5f7, + 0x0810502ee38c4958, 0x84f1384d6ff52a5e, 0x3a8ba6baa3e91c3f, 0xb66aced92f907f39, + 0xa748675762d3b60a, 0x2ba90f34eeaad50c, 0x95d391c322b6e36d, 0x1932f9a0aecf806b, + 0xc27f8a7fe2191cc4, 0x4e9ee21c6e607fc2, 0xf0e47ceba27c49a3, 0x7c0514882e052aa5, + 0x86f52c46d7163808, 0x0a1444255b6f5b0e, 0xb46edad297736d6f, 0x388fb2b11b0a0e69, + 0xe3c2c16e57dc92c6, 0x6f23a90ddba5f1c0, 0xd15937fa17b9c7a1, 0x5db85f999bc0a4a7, + 0x4c9af617d6836d94, 0xc07b9e745afa0e92, 0x7e01008396e638f3, 0xf2e068e01a9f5bf5, + 0x29ad1b3f5649c75a, 0xa54c735cda30a45c, 0x1b36edab162c923d, 0x97d785c89a55f13b, + 0x3973beb78cab005b, 0xb592d6d400d2635d, 0x0be84823ccce553c, 0x8709204040b7363a, + 0x5c44539f0c61aa95, 0xd0a53bfc8018c993, 0x6edfa50b4c04fff2, 0xe23ecd68c07d9cf4, + 0xf31c64e68d3e55c7, 0x7ffd0c85014736c1, 0xc1879272cd5b00a0, 0x4d66fa11412263a6, + 0x962b89ce0df4ff09, 0x1acae1ad818d9c0f, 0xa4b07f5a4d91aa6e, 0x28511739c1e8c968 + }, + { + 0x0000000000000000, 0x3504e58b9ba6dd1e, 0x6a09cb17374dba3c, 0x5f0d2e9caceb6722, + 0xd413962e6e9b7478, 0xe11773a5f53da966, 0xbe1a5d3959d6ce44, 0x8b1eb8b2c270135a, + 0x837e0a0f85a17b9b, 0xb67aef841e07a685, 0xe977c118b2ecc1a7, 0xdc732493294a1cb9, + 0x576d9c21eb3a0fe3, 0x626979aa709cd2fd, 0x3d645736dc77b5df, 0x0860b2bd47d168c1, + 0x2da5324c53d5645d, 0x18a1d7c7c873b943, 0x47acf95b6498de61, 0x72a81cd0ff3e037f, + 0xf9b6a4623d4e1025, 0xccb241e9a6e8cd3b, 0x93bf6f750a03aa19, 0xa6bb8afe91a57707, + 0xaedb3843d6741fc6, 0x9bdfddc84dd2c2d8, 0xc4d2f354e139a5fa, 0xf1d616df7a9f78e4, + 0x7ac8ae6db8ef6bbe, 0x4fcc4be62349b6a0, 0x10c1657a8fa2d182, 0x25c580f114040c9c, + 0x5b4a6498a7aac8ba, 0x6e4e81133c0c15a4, 0x3143af8f90e77286, 0x04474a040b41af98, + 0x8f59f2b6c931bcc2, 0xba5d173d529761dc, 0xe55039a1fe7c06fe, 0xd054dc2a65dadbe0, + 0xd8346e97220bb321, 0xed308b1cb9ad6e3f, 0xb23da5801546091d, 0x8739400b8ee0d403, + 0x0c27f8b94c90c759, 0x39231d32d7361a47, 0x662e33ae7bdd7d65, 0x532ad625e07ba07b, + 0x76ef56d4f47face7, 0x43ebb35f6fd971f9, 0x1ce69dc3c33216db, 0x29e278485894cbc5, + 0xa2fcc0fa9ae4d89f, 0x97f8257101420581, 0xc8f50bedada962a3, 0xfdf1ee66360fbfbd, + 0xf5915cdb71ded77c, 0xc095b950ea780a62, 0x9f9897cc46936d40, 0xaa9c7247dd35b05e, + 0x2182caf51f45a304, 0x14862f7e84e37e1a, 0x4b8b01e228081938, 0x7e8fe469b3aec426, + 0xb694c9314f559174, 0x83902cbad4f34c6a, 0xdc9d022678182b48, 0xe999e7ade3bef656, + 0x62875f1f21cee50c, 0x5783ba94ba683812, 0x088e940816835f30, 0x3d8a71838d25822e, + 0x35eac33ecaf4eaef, 0x00ee26b5515237f1, 0x5fe30829fdb950d3, 0x6ae7eda2661f8dcd, + 0xe1f95510a46f9e97, 0xd4fdb09b3fc94389, 0x8bf09e07932224ab, 0xbef47b8c0884f9b5, + 0x9b31fb7d1c80f529, 0xae351ef687262837, 0xf138306a2bcd4f15, 0xc43cd5e1b06b920b, + 0x4f226d53721b8151, 0x7a2688d8e9bd5c4f, 0x252ba64445563b6d, 0x102f43cfdef0e673, + 0x184ff17299218eb2, 0x2d4b14f9028753ac, 0x72463a65ae6c348e, 0x4742dfee35cae990, + 0xcc5c675cf7bafaca, 0xf95882d76c1c27d4, 0xa655ac4bc0f740f6, 0x935149c05b519de8, + 0xeddeada9e8ff59ce, 0xd8da4822735984d0, 0x87d766bedfb2e3f2, 0xb2d3833544143eec, + 0x39cd3b8786642db6, 0x0cc9de0c1dc2f0a8, 0x53c4f090b129978a, 0x66c0151b2a8f4a94, + 0x6ea0a7a66d5e2255, 0x5ba4422df6f8ff4b, 0x04a96cb15a139869, 0x31ad893ac1b54577, + 0xbab3318803c5562d, 0x8fb7d40398638b33, 0xd0bafa9f3488ec11, 0xe5be1f14af2e310f, + 0xc07b9fe5bb2a3d93, 0xf57f7a6e208ce08d, 0xaa7254f28c6787af, 0x9f76b17917c15ab1, + 0x146809cbd5b149eb, 0x216cec404e1794f5, 0x7e61c2dce2fcf3d7, 0x4b652757795a2ec9, + 0x430595ea3e8b4608, 0x76017061a52d9b16, 0x290c5efd09c6fc34, 0x1c08bb769260212a, + 0x971603c450103270, 0xa212e64fcbb6ef6e, 0xfd1fc8d3675d884c, 0xc81b2d58fcfb5552, + 0x4670b431c63cb183, 0x737451ba5d9a6c9d, 0x2c797f26f1710bbf, 0x197d9aad6ad7d6a1, + 0x9263221fa8a7c5fb, 0xa767c794330118e5, 0xf86ae9089fea7fc7, 0xcd6e0c83044ca2d9, + 0xc50ebe3e439dca18, 0xf00a5bb5d83b1706, 0xaf07752974d07024, 0x9a0390a2ef76ad3a, + 0x111d28102d06be60, 0x2419cd9bb6a0637e, 0x7b14e3071a4b045c, 0x4e10068c81edd942, + 0x6bd5867d95e9d5de, 0x5ed163f60e4f08c0, 0x01dc4d6aa2a46fe2, 0x34d8a8e13902b2fc, + 0xbfc61053fb72a1a6, 0x8ac2f5d860d47cb8, 0xd5cfdb44cc3f1b9a, 0xe0cb3ecf5799c684, + 0xe8ab8c721048ae45, 0xddaf69f98bee735b, 0x82a2476527051479, 0xb7a6a2eebca3c967, + 0x3cb81a5c7ed3da3d, 0x09bcffd7e5750723, 0x56b1d14b499e6001, 0x63b534c0d238bd1f, + 0x1d3ad0a961967939, 0x283e3522fa30a427, 0x77331bbe56dbc305, 0x4237fe35cd7d1e1b, + 0xc92946870f0d0d41, 0xfc2da30c94abd05f, 0xa3208d903840b77d, 0x9624681ba3e66a63, + 0x9e44daa6e43702a2, 0xab403f2d7f91dfbc, 0xf44d11b1d37ab89e, 0xc149f43a48dc6580, + 0x4a574c888aac76da, 0x7f53a903110aabc4, 0x205e879fbde1cce6, 0x155a6214264711f8, + 0x309fe2e532431d64, 0x059b076ea9e5c07a, 0x5a9629f2050ea758, 0x6f92cc799ea87a46, + 0xe48c74cb5cd8691c, 0xd1889140c77eb402, 0x8e85bfdc6b95d320, 0xbb815a57f0330e3e, + 0xb3e1e8eab7e266ff, 0x86e50d612c44bbe1, 0xd9e823fd80afdcc3, 0xececc6761b0901dd, + 0x67f27ec4d9791287, 0x52f69b4f42dfcf99, 0x0dfbb5d3ee34a8bb, 0x38ff5058759275a5, + 0xf0e47d00896920f7, 0xc5e0988b12cffde9, 0x9aedb617be249acb, 0xafe9539c258247d5, + 0x24f7eb2ee7f2548f, 0x11f30ea57c548991, 0x4efe2039d0bfeeb3, 0x7bfac5b24b1933ad, + 0x739a770f0cc85b6c, 0x469e9284976e8672, 0x1993bc183b85e150, 0x2c975993a0233c4e, + 0xa789e12162532f14, 0x928d04aaf9f5f20a, 0xcd802a36551e9528, 0xf884cfbdceb84836, + 0xdd414f4cdabc44aa, 0xe845aac7411a99b4, 0xb748845bedf1fe96, 0x824c61d076572388, + 0x0952d962b42730d2, 0x3c563ce92f81edcc, 0x635b1275836a8aee, 0x565ff7fe18cc57f0, + 0x5e3f45435f1d3f31, 0x6b3ba0c8c4bbe22f, 0x34368e546850850d, 0x01326bdff3f65813, + 0x8a2cd36d31864b49, 0xbf2836e6aa209657, 0xe025187a06cbf175, 0xd521fdf19d6d2c6b, + 0xabae19982ec3e84d, 0x9eaafc13b5653553, 0xc1a7d28f198e5271, 0xf4a3370482288f6f, + 0x7fbd8fb640589c35, 0x4ab96a3ddbfe412b, 0x15b444a177152609, 0x20b0a12aecb3fb17, + 0x28d01397ab6293d6, 0x1dd4f61c30c44ec8, 0x42d9d8809c2f29ea, 0x77dd3d0b0789f4f4, + 0xfcc385b9c5f9e7ae, 0xc9c760325e5f3ab0, 0x96ca4eaef2b45d92, 0xa3ceab256912808c, + 0x860b2bd47d168c10, 0xb30fce5fe6b0510e, 0xec02e0c34a5b362c, 0xd9060548d1fdeb32, + 0x5218bdfa138df868, 0x671c5871882b2576, 0x381176ed24c04254, 0x0d159366bf669f4a, + 0x057521dbf8b7f78b, 0x3071c45063112a95, 0x6f7ceacccffa4db7, 0x5a780f47545c90a9, + 0xd166b7f5962c83f3, 0xe462527e0d8a5eed, 0xbb6f7ce2a16139cf, 0x8e6b99693ac7e4d1 + }, + { + 0x0000000000000000, 0xe39d1389931b9354, 0xec6301407ea0b5c3, 0x0ffe12c9edbb2697, + 0xf39f24d3a5d6f8ed, 0x1002375a36cd6bb9, 0x1ffc2593db764d2e, 0xfc61361a486dde7a, + 0xcc676ff4133a62b1, 0x2ffa7c7d8021f1e5, 0x20046eb46d9ad772, 0xc3997d3dfe814426, + 0x3ff84b27b6ec9a5c, 0xdc6558ae25f70908, 0xd39b4a67c84c2f9f, 0x300659ee5b57bccb, + 0xb397f9bb7ee35609, 0x500aea32edf8c55d, 0x5ff4f8fb0043e3ca, 0xbc69eb729358709e, + 0x4008dd68db35aee4, 0xa395cee1482e3db0, 0xac6bdc28a5951b27, 0x4ff6cfa1368e8873, + 0x7ff0964f6dd934b8, 0x9c6d85c6fec2a7ec, 0x9393970f1379817b, 0x700e84868062122f, + 0x8c6fb29cc80fcc55, 0x6ff2a1155b145f01, 0x600cb3dcb6af7996, 0x8391a05525b4eac2, + 0x4c76d525a5513f79, 0xafebc6ac364aac2d, 0xa015d465dbf18aba, 0x4388c7ec48ea19ee, + 0xbfe9f1f60087c794, 0x5c74e27f939c54c0, 0x538af0b67e277257, 0xb017e33fed3ce103, + 0x8011bad1b66b5dc8, 0x638ca9582570ce9c, 0x6c72bb91c8cbe80b, 0x8fefa8185bd07b5f, + 0x738e9e0213bda525, 0x90138d8b80a63671, 0x9fed9f426d1d10e6, 0x7c708ccbfe0683b2, + 0xffe12c9edbb26970, 0x1c7c3f1748a9fa24, 0x13822ddea512dcb3, 0xf01f3e5736094fe7, + 0x0c7e084d7e64919d, 0xefe31bc4ed7f02c9, 0xe01d090d00c4245e, 0x03801a8493dfb70a, + 0x3386436ac8880bc1, 0xd01b50e35b939895, 0xdfe5422ab628be02, 0x3c7851a325332d56, + 0xc01967b96d5ef32c, 0x23847430fe456078, 0x2c7a66f913fe46ef, 0xcfe7757080e5d5bb, + 0x98edaa4b4aa27ef2, 0x7b70b9c2d9b9eda6, 0x748eab0b3402cb31, 0x9713b882a7195865, + 0x6b728e98ef74861f, 0x88ef9d117c6f154b, 0x87118fd891d433dc, 0x648c9c5102cfa088, + 0x548ac5bf59981c43, 0xb717d636ca838f17, 0xb8e9c4ff2738a980, 0x5b74d776b4233ad4, + 0xa715e16cfc4ee4ae, 0x4488f2e56f5577fa, 0x4b76e02c82ee516d, 0xa8ebf3a511f5c239, + 0x2b7a53f0344128fb, 0xc8e74079a75abbaf, 0xc71952b04ae19d38, 0x24844139d9fa0e6c, + 0xd8e577239197d016, 0x3b7864aa028c4342, 0x34867663ef3765d5, 0xd71b65ea7c2cf681, + 0xe71d3c04277b4a4a, 0x04802f8db460d91e, 0x0b7e3d4459dbff89, 0xe8e32ecdcac06cdd, + 0x148218d782adb2a7, 0xf71f0b5e11b621f3, 0xf8e11997fc0d0764, 0x1b7c0a1e6f169430, + 0xd49b7f6eeff3418b, 0x37066ce77ce8d2df, 0x38f87e2e9153f448, 0xdb656da70248671c, + 0x27045bbd4a25b966, 0xc4994834d93e2a32, 0xcb675afd34850ca5, 0x28fa4974a79e9ff1, + 0x18fc109afcc9233a, 0xfb6103136fd2b06e, 0xf49f11da826996f9, 0x17020253117205ad, + 0xeb633449591fdbd7, 0x08fe27c0ca044883, 0x0700350927bf6e14, 0xe49d2680b4a4fd40, + 0x670c86d591101782, 0x8491955c020b84d6, 0x8b6f8795efb0a241, 0x68f2941c7cab3115, + 0x9493a20634c6ef6f, 0x770eb18fa7dd7c3b, 0x78f0a3464a665aac, 0x9b6db0cfd97dc9f8, + 0xab6be921822a7533, 0x48f6faa81131e667, 0x4708e861fc8ac0f0, 0xa495fbe86f9153a4, + 0x58f4cdf227fc8dde, 0xbb69de7bb4e71e8a, 0xb497ccb2595c381d, 0x570adf3bca47ab49, + 0x1a8272c5cdd36e8f, 0xf91f614c5ec8fddb, 0xf6e17385b373db4c, 0x157c600c20684818, + 0xe91d561668059662, 0x0a80459ffb1e0536, 0x057e575616a523a1, 0xe6e344df85beb0f5, + 0xd6e51d31dee90c3e, 0x35780eb84df29f6a, 0x3a861c71a049b9fd, 0xd91b0ff833522aa9, + 0x257a39e27b3ff4d3, 0xc6e72a6be8246787, 0xc91938a2059f4110, 0x2a842b2b9684d244, + 0xa9158b7eb3303886, 0x4a8898f7202babd2, 0x45768a3ecd908d45, 0xa6eb99b75e8b1e11, + 0x5a8aafad16e6c06b, 0xb917bc2485fd533f, 0xb6e9aeed684675a8, 0x5574bd64fb5de6fc, + 0x6572e48aa00a5a37, 0x86eff7033311c963, 0x8911e5cadeaaeff4, 0x6a8cf6434db17ca0, + 0x96edc05905dca2da, 0x7570d3d096c7318e, 0x7a8ec1197b7c1719, 0x9913d290e867844d, + 0x56f4a7e0688251f6, 0xb569b469fb99c2a2, 0xba97a6a01622e435, 0x590ab52985397761, + 0xa56b8333cd54a91b, 0x46f690ba5e4f3a4f, 0x49088273b3f41cd8, 0xaa9591fa20ef8f8c, + 0x9a93c8147bb83347, 0x790edb9de8a3a013, 0x76f0c95405188684, 0x956ddadd960315d0, + 0x690cecc7de6ecbaa, 0x8a91ff4e4d7558fe, 0x856fed87a0ce7e69, 0x66f2fe0e33d5ed3d, + 0xe5635e5b166107ff, 0x06fe4dd2857a94ab, 0x09005f1b68c1b23c, 0xea9d4c92fbda2168, + 0x16fc7a88b3b7ff12, 0xf561690120ac6c46, 0xfa9f7bc8cd174ad1, 0x190268415e0cd985, + 0x290431af055b654e, 0xca9922269640f61a, 0xc56730ef7bfbd08d, 0x26fa2366e8e043d9, + 0xda9b157ca08d9da3, 0x390606f533960ef7, 0x36f8143cde2d2860, 0xd56507b54d36bb34, + 0x826fd88e8771107d, 0x61f2cb07146a8329, 0x6e0cd9cef9d1a5be, 0x8d91ca476aca36ea, + 0x71f0fc5d22a7e890, 0x926defd4b1bc7bc4, 0x9d93fd1d5c075d53, 0x7e0eee94cf1cce07, + 0x4e08b77a944b72cc, 0xad95a4f30750e198, 0xa26bb63aeaebc70f, 0x41f6a5b379f0545b, + 0xbd9793a9319d8a21, 0x5e0a8020a2861975, 0x51f492e94f3d3fe2, 0xb2698160dc26acb6, + 0x31f82135f9924674, 0xd26532bc6a89d520, 0xdd9b20758732f3b7, 0x3e0633fc142960e3, + 0xc26705e65c44be99, 0x21fa166fcf5f2dcd, 0x2e0404a622e40b5a, 0xcd99172fb1ff980e, + 0xfd9f4ec1eaa824c5, 0x1e025d4879b3b791, 0x11fc4f8194089106, 0xf2615c0807130252, + 0x0e006a124f7edc28, 0xed9d799bdc654f7c, 0xe2636b5231de69eb, 0x01fe78dba2c5fabf, + 0xce190dab22202f04, 0x2d841e22b13bbc50, 0x227a0ceb5c809ac7, 0xc1e71f62cf9b0993, + 0x3d86297887f6d7e9, 0xde1b3af114ed44bd, 0xd1e52838f956622a, 0x32783bb16a4df17e, + 0x027e625f311a4db5, 0xe1e371d6a201dee1, 0xee1d631f4fbaf876, 0x0d807096dca16b22, + 0xf1e1468c94ccb558, 0x127c550507d7260c, 0x1d8247ccea6c009b, 0xfe1f5445797793cf, + 0x7d8ef4105cc3790d, 0x9e13e799cfd8ea59, 0x91edf5502263ccce, 0x7270e6d9b1785f9a, + 0x8e11d0c3f91581e0, 0x6d8cc34a6a0e12b4, 0x6272d18387b53423, 0x81efc20a14aea777, + 0xb1e99be44ff91bbc, 0x5274886ddce288e8, 0x5d8a9aa43159ae7f, 0xbe17892da2423d2b, + 0x4276bf37ea2fe351, 0xa1ebacbe79347005, 0xae15be77948f5692, 0x4d88adfe0794c5c6 + }, + { + 0x0000000000000000, 0x62a95de6e302eff2, 0xc552bbcdc605dfe4, 0xa7fbe62b25073016, + 0xa1fc51c8d49c2ca3, 0xc3550c2e379ec351, 0x64aeea051299f347, 0x0607b7e3f19b1cb5, + 0x68a185c2f1afca2d, 0x0a08d82412ad25df, 0xadf33e0f37aa15c9, 0xcf5a63e9d4a8fa3b, + 0xc95dd40a2533e68e, 0xabf489ecc631097c, 0x0c0f6fc7e336396a, 0x6ea632210034d698, + 0xd1430b85e35f945a, 0xb3ea5663005d7ba8, 0x1411b048255a4bbe, 0x76b8edaec658a44c, + 0x70bf5a4d37c3b8f9, 0x121607abd4c1570b, 0xb5ede180f1c6671d, 0xd744bc6612c488ef, + 0xb9e28e4712f05e77, 0xdb4bd3a1f1f2b185, 0x7cb0358ad4f58193, 0x1e19686c37f76e61, + 0x181edf8fc66c72d4, 0x7ab78269256e9d26, 0xdd4c64420069ad30, 0xbfe539a4e36b42c2, + 0x89df31589e28bbdf, 0xeb766cbe7d2a542d, 0x4c8d8a95582d643b, 0x2e24d773bb2f8bc9, + 0x282360904ab4977c, 0x4a8a3d76a9b6788e, 0xed71db5d8cb14898, 0x8fd886bb6fb3a76a, + 0xe17eb49a6f8771f2, 0x83d7e97c8c859e00, 0x242c0f57a982ae16, 0x468552b14a8041e4, + 0x4082e552bb1b5d51, 0x222bb8b45819b2a3, 0x85d05e9f7d1e82b5, 0xe77903799e1c6d47, + 0x589c3add7d772f85, 0x3a35673b9e75c077, 0x9dce8110bb72f061, 0xff67dcf658701f93, + 0xf9606b15a9eb0326, 0x9bc936f34ae9ecd4, 0x3c32d0d86feedcc2, 0x5e9b8d3e8cec3330, + 0x303dbf1f8cd8e5a8, 0x5294e2f96fda0a5a, 0xf56f04d24add3a4c, 0x97c65934a9dfd5be, + 0x91c1eed75844c90b, 0xf368b331bb4626f9, 0x5493551a9e4116ef, 0x363a08fc7d43f91d, + 0x38e744e264c6e4d5, 0x5a4e190487c40b27, 0xfdb5ff2fa2c33b31, 0x9f1ca2c941c1d4c3, + 0x991b152ab05ac876, 0xfbb248cc53582784, 0x5c49aee7765f1792, 0x3ee0f301955df860, + 0x5046c12095692ef8, 0x32ef9cc6766bc10a, 0x95147aed536cf11c, 0xf7bd270bb06e1eee, + 0xf1ba90e841f5025b, 0x9313cd0ea2f7eda9, 0x34e82b2587f0ddbf, 0x564176c364f2324d, + 0xe9a44f678799708f, 0x8b0d1281649b9f7d, 0x2cf6f4aa419caf6b, 0x4e5fa94ca29e4099, + 0x48581eaf53055c2c, 0x2af14349b007b3de, 0x8d0aa562950083c8, 0xefa3f88476026c3a, + 0x8105caa57636baa2, 0xe3ac974395345550, 0x44577168b0336546, 0x26fe2c8e53318ab4, + 0x20f99b6da2aa9601, 0x4250c68b41a879f3, 0xe5ab20a064af49e5, 0x87027d4687ada617, + 0xb13875bafaee5f0a, 0xd391285c19ecb0f8, 0x746ace773ceb80ee, 0x16c39391dfe96f1c, + 0x10c424722e7273a9, 0x726d7994cd709c5b, 0xd5969fbfe877ac4d, 0xb73fc2590b7543bf, + 0xd999f0780b419527, 0xbb30ad9ee8437ad5, 0x1ccb4bb5cd444ac3, 0x7e6216532e46a531, + 0x7865a1b0dfddb984, 0x1accfc563cdf5676, 0xbd371a7d19d86660, 0xdf9e479bfada8992, + 0x607b7e3f19b1cb50, 0x02d223d9fab324a2, 0xa529c5f2dfb414b4, 0xc78098143cb6fb46, + 0xc1872ff7cd2de7f3, 0xa32e72112e2f0801, 0x04d5943a0b283817, 0x667cc9dce82ad7e5, + 0x08dafbfde81e017d, 0x6a73a61b0b1cee8f, 0xcd8840302e1bde99, 0xaf211dd6cd19316b, + 0xa926aa353c822dde, 0xcb8ff7d3df80c22c, 0x6c7411f8fa87f23a, 0x0edd4c1e19851dc8, + 0x71ce89c4c98dc9aa, 0x1367d4222a8f2658, 0xb49c32090f88164e, 0xd6356fefec8af9bc, + 0xd032d80c1d11e509, 0xb29b85eafe130afb, 0x156063c1db143aed, 0x77c93e273816d51f, + 0x196f0c0638220387, 0x7bc651e0db20ec75, 0xdc3db7cbfe27dc63, 0xbe94ea2d1d253391, + 0xb8935dceecbe2f24, 0xda3a00280fbcc0d6, 0x7dc1e6032abbf0c0, 0x1f68bbe5c9b91f32, + 0xa08d82412ad25df0, 0xc224dfa7c9d0b202, 0x65df398cecd78214, 0x0776646a0fd56de6, + 0x0171d389fe4e7153, 0x63d88e6f1d4c9ea1, 0xc4236844384baeb7, 0xa68a35a2db494145, + 0xc82c0783db7d97dd, 0xaa855a65387f782f, 0x0d7ebc4e1d784839, 0x6fd7e1a8fe7aa7cb, + 0x69d0564b0fe1bb7e, 0x0b790badece3548c, 0xac82ed86c9e4649a, 0xce2bb0602ae68b68, + 0xf811b89c57a57275, 0x9ab8e57ab4a79d87, 0x3d43035191a0ad91, 0x5fea5eb772a24263, + 0x59ede95483395ed6, 0x3b44b4b2603bb124, 0x9cbf5299453c8132, 0xfe160f7fa63e6ec0, + 0x90b03d5ea60ab858, 0xf21960b8450857aa, 0x55e28693600f67bc, 0x374bdb75830d884e, + 0x314c6c96729694fb, 0x53e5317091947b09, 0xf41ed75bb4934b1f, 0x96b78abd5791a4ed, + 0x2952b319b4fae62f, 0x4bfbeeff57f809dd, 0xec0008d472ff39cb, 0x8ea9553291fdd639, + 0x88aee2d16066ca8c, 0xea07bf378364257e, 0x4dfc591ca6631568, 0x2f5504fa4561fa9a, + 0x41f336db45552c02, 0x235a6b3da657c3f0, 0x84a18d168350f3e6, 0xe608d0f060521c14, + 0xe00f671391c900a1, 0x82a63af572cbef53, 0x255ddcde57ccdf45, 0x47f48138b4ce30b7, + 0x4929cd26ad4b2d7f, 0x2b8090c04e49c28d, 0x8c7b76eb6b4ef29b, 0xeed22b0d884c1d69, + 0xe8d59cee79d701dc, 0x8a7cc1089ad5ee2e, 0x2d872723bfd2de38, 0x4f2e7ac55cd031ca, + 0x218848e45ce4e752, 0x43211502bfe608a0, 0xe4daf3299ae138b6, 0x8673aecf79e3d744, + 0x8074192c8878cbf1, 0xe2dd44ca6b7a2403, 0x4526a2e14e7d1415, 0x278fff07ad7ffbe7, + 0x986ac6a34e14b925, 0xfac39b45ad1656d7, 0x5d387d6e881166c1, 0x3f9120886b138933, + 0x3996976b9a889586, 0x5b3fca8d798a7a74, 0xfcc42ca65c8d4a62, 0x9e6d7140bf8fa590, + 0xf0cb4361bfbb7308, 0x92621e875cb99cfa, 0x3599f8ac79beacec, 0x5730a54a9abc431e, + 0x513712a96b275fab, 0x339e4f4f8825b059, 0x9465a964ad22804f, 0xf6ccf4824e206fbd, + 0xc0f6fc7e336396a0, 0xa25fa198d0617952, 0x05a447b3f5664944, 0x670d1a551664a6b6, + 0x610aadb6e7ffba03, 0x03a3f05004fd55f1, 0xa458167b21fa65e7, 0xc6f14b9dc2f88a15, + 0xa85779bcc2cc5c8d, 0xcafe245a21ceb37f, 0x6d05c27104c98369, 0x0fac9f97e7cb6c9b, + 0x09ab28741650702e, 0x6b027592f5529fdc, 0xccf993b9d055afca, 0xae50ce5f33574038, + 0x11b5f7fbd03c02fa, 0x731caa1d333eed08, 0xd4e74c361639dd1e, 0xb64e11d0f53b32ec, + 0xb049a63304a02e59, 0xd2e0fbd5e7a2c1ab, 0x751b1dfec2a5f1bd, 0x17b2401821a71e4f, + 0x791472392193c8d7, 0x1bbd2fdfc2912725, 0xbc46c9f4e7961733, 0xdeef94120494f8c1, + 0xd8e823f1f50fe474, 0xba417e17160d0b86, 0x1dba983c330a3b90, 0x7f13c5dad008d462 + }, + { + 0x0000000000000000, 0x381d0015c96f4444, 0x703a002b92de8888, 0x4827003e5bb1cccc, + 0xe074005725bd1110, 0xd8690042ecd25554, 0x904e007cb7639998, 0xa85300697e0cdddc, + 0xebb126fd13edb14b, 0xd3ac26e8da82f50f, 0x9b8b26d6813339c3, 0xa39626c3485c7d87, + 0x0bc526aa3650a05b, 0x33d826bfff3fe41f, 0x7bff2681a48e28d3, 0x43e226946de16c97, + 0xfc3b6ba97f4cf1fd, 0xc4266bbcb623b5b9, 0x8c016b82ed927975, 0xb41c6b9724fd3d31, + 0x1c4f6bfe5af1e0ed, 0x24526beb939ea4a9, 0x6c756bd5c82f6865, 0x54686bc001402c21, + 0x178a4d546ca140b6, 0x2f974d41a5ce04f2, 0x67b04d7ffe7fc83e, 0x5fad4d6a37108c7a, + 0xf7fe4d03491c51a6, 0xcfe34d16807315e2, 0x87c44d28dbc2d92e, 0xbfd94d3d12ad9d6a, + 0xd32ff101a60e7091, 0xeb32f1146f6134d5, 0xa315f12a34d0f819, 0x9b08f13ffdbfbc5d, + 0x335bf15683b36181, 0x0b46f1434adc25c5, 0x4361f17d116de909, 0x7b7cf168d802ad4d, + 0x389ed7fcb5e3c1da, 0x0083d7e97c8c859e, 0x48a4d7d7273d4952, 0x70b9d7c2ee520d16, + 0xd8ead7ab905ed0ca, 0xe0f7d7be5931948e, 0xa8d0d78002805842, 0x90cdd795cbef1c06, + 0x2f149aa8d942816c, 0x17099abd102dc528, 0x5f2e9a834b9c09e4, 0x67339a9682f34da0, + 0xcf609afffcff907c, 0xf77d9aea3590d438, 0xbf5a9ad46e2118f4, 0x87479ac1a74e5cb0, + 0xc4a5bc55caaf3027, 0xfcb8bc4003c07463, 0xb49fbc7e5871b8af, 0x8c82bc6b911efceb, + 0x24d1bc02ef122137, 0x1cccbc17267d6573, 0x54ebbc297dcca9bf, 0x6cf6bc3cb4a3edfb, + 0x8d06c450148b7249, 0xb51bc445dde4360d, 0xfd3cc47b8655fac1, 0xc521c46e4f3abe85, + 0x6d72c40731366359, 0x556fc412f859271d, 0x1d48c42ca3e8ebd1, 0x2555c4396a87af95, + 0x66b7e2ad0766c302, 0x5eaae2b8ce098746, 0x168de28695b84b8a, 0x2e90e2935cd70fce, + 0x86c3e2fa22dbd212, 0xbedee2efebb49656, 0xf6f9e2d1b0055a9a, 0xcee4e2c4796a1ede, + 0x713daff96bc783b4, 0x4920afeca2a8c7f0, 0x0107afd2f9190b3c, 0x391aafc730764f78, + 0x9149afae4e7a92a4, 0xa954afbb8715d6e0, 0xe173af85dca41a2c, 0xd96eaf9015cb5e68, + 0x9a8c8904782a32ff, 0xa2918911b14576bb, 0xeab6892feaf4ba77, 0xd2ab893a239bfe33, + 0x7af889535d9723ef, 0x42e5894694f867ab, 0x0ac28978cf49ab67, 0x32df896d0626ef23, + 0x5e293551b28502d8, 0x663435447bea469c, 0x2e13357a205b8a50, 0x160e356fe934ce14, + 0xbe5d3506973813c8, 0x864035135e57578c, 0xce67352d05e69b40, 0xf67a3538cc89df04, + 0xb59813aca168b393, 0x8d8513b96807f7d7, 0xc5a2138733b63b1b, 0xfdbf1392fad97f5f, + 0x55ec13fb84d5a283, 0x6df113ee4dbae6c7, 0x25d613d0160b2a0b, 0x1dcb13c5df646e4f, + 0xa2125ef8cdc9f325, 0x9a0f5eed04a6b761, 0xd2285ed35f177bad, 0xea355ec696783fe9, + 0x42665eafe874e235, 0x7a7b5eba211ba671, 0x325c5e847aaa6abd, 0x0a415e91b3c52ef9, + 0x49a37805de24426e, 0x71be7810174b062a, 0x3999782e4cfacae6, 0x0184783b85958ea2, + 0xa9d77852fb99537e, 0x91ca784732f6173a, 0xd9ed78796947dbf6, 0xe1f0786ca0289fb2, + 0x3154aef3718177f9, 0x0949aee6b8ee33bd, 0x416eaed8e35fff71, 0x7973aecd2a30bb35, + 0xd120aea4543c66e9, 0xe93daeb19d5322ad, 0xa11aae8fc6e2ee61, 0x9907ae9a0f8daa25, + 0xdae5880e626cc6b2, 0xe2f8881bab0382f6, 0xaadf8825f0b24e3a, 0x92c2883039dd0a7e, + 0x3a91885947d1d7a2, 0x028c884c8ebe93e6, 0x4aab8872d50f5f2a, 0x72b688671c601b6e, + 0xcd6fc55a0ecd8604, 0xf572c54fc7a2c240, 0xbd55c5719c130e8c, 0x8548c564557c4ac8, + 0x2d1bc50d2b709714, 0x1506c518e21fd350, 0x5d21c526b9ae1f9c, 0x653cc53370c15bd8, + 0x26dee3a71d20374f, 0x1ec3e3b2d44f730b, 0x56e4e38c8ffebfc7, 0x6ef9e3994691fb83, + 0xc6aae3f0389d265f, 0xfeb7e3e5f1f2621b, 0xb690e3dbaa43aed7, 0x8e8de3ce632cea93, + 0xe27b5ff2d78f0768, 0xda665fe71ee0432c, 0x92415fd945518fe0, 0xaa5c5fcc8c3ecba4, + 0x020f5fa5f2321678, 0x3a125fb03b5d523c, 0x72355f8e60ec9ef0, 0x4a285f9ba983dab4, + 0x09ca790fc462b623, 0x31d7791a0d0df267, 0x79f0792456bc3eab, 0x41ed79319fd37aef, + 0xe9be7958e1dfa733, 0xd1a3794d28b0e377, 0x9984797373012fbb, 0xa1997966ba6e6bff, + 0x1e40345ba8c3f695, 0x265d344e61acb2d1, 0x6e7a34703a1d7e1d, 0x56673465f3723a59, + 0xfe34340c8d7ee785, 0xc62934194411a3c1, 0x8e0e34271fa06f0d, 0xb6133432d6cf2b49, + 0xf5f112a6bb2e47de, 0xcdec12b37241039a, 0x85cb128d29f0cf56, 0xbdd61298e09f8b12, + 0x158512f19e9356ce, 0x2d9812e457fc128a, 0x65bf12da0c4dde46, 0x5da212cfc5229a02, + 0xbc526aa3650a05b0, 0x844f6ab6ac6541f4, 0xcc686a88f7d48d38, 0xf4756a9d3ebbc97c, + 0x5c266af440b714a0, 0x643b6ae189d850e4, 0x2c1c6adfd2699c28, 0x14016aca1b06d86c, + 0x57e34c5e76e7b4fb, 0x6ffe4c4bbf88f0bf, 0x27d94c75e4393c73, 0x1fc44c602d567837, + 0xb7974c09535aa5eb, 0x8f8a4c1c9a35e1af, 0xc7ad4c22c1842d63, 0xffb04c3708eb6927, + 0x4069010a1a46f44d, 0x7874011fd329b009, 0x3053012188987cc5, 0x084e013441f73881, + 0xa01d015d3ffbe55d, 0x98000148f694a119, 0xd0270176ad256dd5, 0xe83a0163644a2991, + 0xabd827f709ab4506, 0x93c527e2c0c40142, 0xdbe227dc9b75cd8e, 0xe3ff27c9521a89ca, + 0x4bac27a02c165416, 0x73b127b5e5791052, 0x3b96278bbec8dc9e, 0x038b279e77a798da, + 0x6f7d9ba2c3047521, 0x57609bb70a6b3165, 0x1f479b8951dafda9, 0x275a9b9c98b5b9ed, + 0x8f099bf5e6b96431, 0xb7149be02fd62075, 0xff339bde7467ecb9, 0xc72e9bcbbd08a8fd, + 0x84ccbd5fd0e9c46a, 0xbcd1bd4a1986802e, 0xf4f6bd7442374ce2, 0xccebbd618b5808a6, + 0x64b8bd08f554d57a, 0x5ca5bd1d3c3b913e, 0x1482bd23678a5df2, 0x2c9fbd36aee519b6, + 0x9346f00bbc4884dc, 0xab5bf01e7527c098, 0xe37cf0202e960c54, 0xdb61f035e7f94810, + 0x7332f05c99f595cc, 0x4b2ff049509ad188, 0x0308f0770b2b1d44, 0x3b15f062c2445900, + 0x78f7d6f6afa53597, 0x40ead6e366ca71d3, 0x08cdd6dd3d7bbd1f, 0x30d0d6c8f414f95b, + 0x9883d6a18a182487, 0xa09ed6b4437760c3, 0xe8b9d68a18c6ac0f, 0xd0a4d69fd1a9e84b + }, + }; + } +} diff --git a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Crc64.cs b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Crc64.cs new file mode 100644 index 00000000000..84dd209dbfb --- /dev/null +++ b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Crc64.cs @@ -0,0 +1,65 @@ +/* + * Originally ported from: https://github.com/gityf/crc/blob/8045f50ba6e4193d4ee5d2539025fef26e613c9f/crc/crc64.c + * + * Copyright (c) 2012, Salvatore Sanfilippo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Redis nor the names of its contributors may be used + * to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. */ + +// https://github.com/xamarin/java.interop/blob/7d197f17a0f9d73854522d6e1d68deafbcdbcaf6/src/Java.Interop.Tools.JavaCallableWrappers/Java.Interop.Tools.JavaCallableWrappers/Crc64.cs + +using System; +using System.Security.Cryptography; + +namespace Microsoft.Android.Build.Tasks +{ + /// + /// CRC64 variant: crc-64-jones 64-bit + /// * Poly: 0xad93d23594c935a9 + /// + /// Changes beyond initial implementation: + /// * Starting Value: ulong.MaxValue + /// * XOR length in HashFinal() + /// * Using spliced table for faster processing + /// + public partial class Crc64 : HashAlgorithm + { + ulong crc = ulong.MaxValue; + ulong length = 0; + + public override void Initialize () + { + crc = ulong.MaxValue; + length = 0; + } + + protected override unsafe void HashCore (byte [] array, int ibStart, int cbSize) + { + Crc64Helper.HashCore (array, ibStart, cbSize, ref crc, ref length); + } + + protected override byte [] HashFinal () => BitConverter.GetBytes (crc ^ length); + } +} diff --git a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Crc64Helper.cs b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Crc64Helper.cs new file mode 100644 index 00000000000..decab0d019e --- /dev/null +++ b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Crc64Helper.cs @@ -0,0 +1,93 @@ +/* + * Originally ported from: https://github.com/gityf/crc/blob/8045f50ba6e4193d4ee5d2539025fef26e613c9f/crc/crc64.c + * + * Copyright (c) 2012, Salvatore Sanfilippo + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Redis nor the names of its contributors may be used + * to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. */ + +// https://github.com/xamarin/java.interop/blob/7d197f17a0f9d73854522d6e1d68deafbcdbcaf6/src/Java.Interop.Tools.JavaCallableWrappers/Java.Interop.Tools.JavaCallableWrappers/Crc64Helper.cs + +using System; +using System.Security.Cryptography; + +namespace Microsoft.Android.Build.Tasks +{ + /// + /// CRC64 variant: crc-64-jones 64-bit + /// * Poly: 0xad93d23594c935a9 + /// + /// Changes beyond initial implementation: + /// * Starting Value: ulong.MaxValue + /// * XOR length in HashFinal() + /// * Using spliced table for faster processing + /// + internal static partial class Crc64Helper + { + + internal static byte [] Compute (byte [] array) + { + ulong crc = ulong.MaxValue; + ulong length = 0; + + HashCore (array, 0, array.Length, ref crc, ref length); + + return BitConverter.GetBytes (crc ^ length); + } + + internal static unsafe void HashCore (byte [] array, int ibStart, int cbSize, ref ulong crc, ref ulong length) + { + int len = cbSize; + int idx = ibStart; + + fixed (ulong* tptr = table) { + fixed (byte* aptr = array) { + while (len >= 8) { + crc ^= *((ulong*) (aptr + idx)); + crc = + tptr [7 * 256 + (crc & 0xff)] ^ + tptr [6 * 256 + ((crc >> 8) & 0xff)] ^ + tptr [5 * 256 + ((crc >> 16) & 0xff)] ^ + tptr [4 * 256 + ((crc >> 24) & 0xff)] ^ + tptr [3 * 256 + ((crc >> 32) & 0xff)] ^ + tptr [2 * 256 + ((crc >> 40) & 0xff)] ^ + tptr [1 * 256 + ((crc >> 48) & 0xff)] ^ + tptr [0 * 256 + (crc >> 56)]; + idx += 8; + len -= 8; + } + + while (len > 0) { + crc = tptr [0 * 256 + ((crc ^ aptr [idx]) & 0xff)] ^ (crc >> 8); + idx++; + len--; + } + } + } + + length += (ulong) cbSize; + } + } +} diff --git a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Files.cs b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Files.cs new file mode 100644 index 00000000000..75b0a3d20f5 --- /dev/null +++ b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Files.cs @@ -0,0 +1,528 @@ +// https://github.com/xamarin/xamarin-android/blob/34acbbae6795854cc4e9f8eb7167ab011e0266b4/src/Xamarin.Android.Build.Tasks/Utilities/Files.cs +// https://github.com/xamarin/xamarin-android/blob/34acbbae6795854cc4e9f8eb7167ab011e0266b4/src/Xamarin.Android.Build.Tasks/Utilities/MonoAndroidHelper.cs#L409 + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Security.Cryptography; +using System.Text; +using Xamarin.Tools.Zip; +using Microsoft.Build.Utilities; + +namespace Microsoft.Android.Build.Tasks +{ + public static class Files + { + /// + /// Windows has a MAX_PATH limit of 260 characters + /// See: https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file#maximum-path-length-limitation + /// + public const int MaxPath = 260; + + /// + /// On Windows, we can opt into a long path with this prefix + /// + public const string LongPathPrefix = @"\\?\"; + + public static readonly Encoding UTF8withoutBOM = new UTF8Encoding (encoderShouldEmitUTF8Identifier: false); + readonly static byte[] Utf8Preamble = Encoding.UTF8.GetPreamble (); + + /// + /// Converts a full path to a \\?\ prefixed path that works on all Windows machines when over 260 characters + /// NOTE: requires a *full path*, use sparingly + /// + public static string ToLongPath (string fullPath) + { + // On non-Windows platforms, return the path unchanged + if (Path.DirectorySeparatorChar != '\\') { + return fullPath; + } + return LongPathPrefix + fullPath; + } + + public static void SetWriteable (string source, bool checkExists = true) + { + if (checkExists && !File.Exists (source)) + return; + + var attributes = File.GetAttributes (source); + if ((attributes & FileAttributes.ReadOnly) == FileAttributes.ReadOnly) + File.SetAttributes (source, attributes & ~FileAttributes.ReadOnly); + } + + public static void SetDirectoryWriteable (string directory) + { + if (!Directory.Exists (directory)) + return; + + var dirInfo = new DirectoryInfo (directory); + if ((dirInfo.Attributes & FileAttributes.ReadOnly) == FileAttributes.ReadOnly) + dirInfo.Attributes &= ~FileAttributes.ReadOnly; + + foreach (var dir in Directory.EnumerateDirectories (directory, "*", SearchOption.AllDirectories)) { + dirInfo = new DirectoryInfo (dir); + if ((dirInfo.Attributes & FileAttributes.ReadOnly) == FileAttributes.ReadOnly) + dirInfo.Attributes &= ~FileAttributes.ReadOnly; + } + + foreach (var file in Directory.EnumerateFiles (directory, "*", SearchOption.AllDirectories)) { + Files.SetWriteable (Path.GetFullPath (file)); + } + } + + public static bool Archive (string target, Action archiver) + { + string newTarget = target + ".new"; + + archiver (newTarget); + + bool changed = CopyIfChanged (newTarget, target); + + try { + File.Delete (newTarget); + } catch { + } + + return changed; + } + + public static bool ArchiveZipUpdate (string target, Action archiver) + { + var lastWrite = File.Exists (target) ? File.GetLastWriteTimeUtc (target) : DateTime.MinValue; + archiver (target); + return lastWrite < File.GetLastWriteTimeUtc (target); + } + + public static bool ArchiveZip (string target, Action archiver) + { + string newTarget = target + ".new"; + + archiver (newTarget); + + bool changed = CopyIfZipChanged (newTarget, target); + + try { + File.Delete (newTarget); + } catch { + } + + return changed; + } + + public static bool CopyIfChanged (string source, string destination) + { + if (HasFileChanged (source, destination)) { + var directory = Path.GetDirectoryName (destination); + if (!string.IsNullOrEmpty (directory)) + Directory.CreateDirectory (directory); + + if (!Directory.Exists (source)) { + if (File.Exists (destination)) { + SetWriteable (destination, checkExists: false); + File.Delete (destination); + } + File.Copy (source, destination, overwrite: true); + SetWriteable (destination, checkExists: false); + File.SetLastWriteTimeUtc (destination, DateTime.UtcNow); + return true; + } + } + + return false; + } + + public static bool CopyIfStringChanged (string contents, string destination) + { + //NOTE: this is not optimal since it allocates a byte[]. We can improve this down the road with Span or System.Buffers. + var bytes = Encoding.UTF8.GetBytes (contents); + return CopyIfBytesChanged (bytes, destination); + } + + public static bool CopyIfBytesChanged (byte[] bytes, string destination) + { + if (HasBytesChanged (bytes, destination)) { + var directory = Path.GetDirectoryName (destination); + if (!string.IsNullOrEmpty (directory)) + Directory.CreateDirectory (directory); + + if (File.Exists (destination)) { + SetWriteable (destination, checkExists: false); + File.Delete (destination); + } + File.WriteAllBytes (destination, bytes); + return true; + } + return false; + } + + public static bool CopyIfStreamChanged (Stream stream, string destination) + { + if (HasStreamChanged (stream, destination)) { + var directory = Path.GetDirectoryName (destination); + if (!string.IsNullOrEmpty (directory)) + Directory.CreateDirectory (directory); + + if (File.Exists (destination)) { + SetWriteable (destination, checkExists: false); + File.Delete (destination); + } + using (var fileStream = File.Create (destination)) { + stream.Position = 0; //HasStreamChanged read to the end + stream.CopyTo (fileStream); + } + return true; + } + return false; + } + + public static bool CopyIfZipChanged (Stream source, string destination) + { + string hash; + if (HasZipChanged (source, destination, out hash)) { + Directory.CreateDirectory (Path.GetDirectoryName (destination)); + source.Position = 0; + using (var f = File.Create (destination)) { + source.CopyTo (f); + } + File.SetLastWriteTimeUtc (destination, DateTime.UtcNow); + return true; + }/* else + Console.WriteLine ("Skipping copying {0}, unchanged", Path.GetFileName (destination));*/ + + return false; + } + + public static bool CopyIfZipChanged (string source, string destination) + { + string hash; + if (HasZipChanged (source, destination, out hash)) { + Directory.CreateDirectory (Path.GetDirectoryName (destination)); + + File.Copy (source, destination, true); + File.SetLastWriteTimeUtc (destination, DateTime.UtcNow); + return true; + } + + return false; + } + + public static bool HasZipChanged (Stream source, string destination, out string hash) + { + hash = null; + + string src_hash = hash = HashZip (source); + + if (!File.Exists (destination)) + return true; + + string dst_hash = HashZip (destination); + + if (src_hash == null || dst_hash == null) + return true; + + return src_hash != dst_hash; + } + + public static bool HasZipChanged (string source, string destination, out string hash) + { + hash = null; + if (!File.Exists (source)) + return true; + + string src_hash = hash = HashZip (source); + + if (!File.Exists (destination)) + return true; + + string dst_hash = HashZip (destination); + + if (src_hash == null || dst_hash == null) + return true; + + return src_hash != dst_hash; + } + + // This is for if the file contents have changed. Often we have to + // regenerate a file, but we don't want to update it if hasn't changed + // so that incremental build is as efficient as possible + public static bool HasFileChanged (string source, string destination) + { + // If either are missing, that's definitely a change + if (!File.Exists (source) || !File.Exists (destination)) + return true; + + var src_hash = HashFile (source); + var dst_hash = HashFile (destination); + + // If the hashes don't match, then the file has changed + if (src_hash != dst_hash) + return true; + + return false; + } + + public static bool HasStreamChanged (Stream source, string destination) + { + //If destination is missing, that's definitely a change + if (!File.Exists (destination)) + return true; + + var src_hash = HashStream (source); + var dst_hash = HashFile (destination); + + // If the hashes don't match, then the file has changed + if (src_hash != dst_hash) + return true; + + return false; + } + + public static bool HasBytesChanged (byte [] bytes, string destination) + { + //If destination is missing, that's definitely a change + if (!File.Exists (destination)) + return true; + + var src_hash = HashBytes (bytes); + var dst_hash = HashFile (destination); + + // If the hashes don't match, then the file has changed + if (src_hash != dst_hash) + return true; + + return false; + } + + static string HashZip (Stream stream) + { + string hashes = String.Empty; + + try { + using (var zip = ZipArchive.Open (stream)) { + foreach (var item in zip) { + hashes += String.Format ("{0}{1}", item.FullName, item.CRC); + } + } + } catch { + return null; + } + return hashes; + } + + static string HashZip (string filename) + { + string hashes = String.Empty; + + try { + // check cache + if (File.Exists (filename + ".hash")) + return File.ReadAllText (filename + ".hash"); + + using (var zip = ReadZipFile (filename)) { + foreach (var item in zip) { + hashes += String.Format ("{0}{1}", item.FullName, item.CRC); + } + } + } catch { + return null; + } + return hashes; + } + + public static ZipArchive ReadZipFile (string filename, bool strictConsistencyChecks = false) + { + return ZipArchive.Open (filename, FileMode.Open, strictConsistencyChecks: strictConsistencyChecks); + } + + public static bool ZipAny (string filename, Func filter) + { + using (var zip = ReadZipFile (filename)) { + return zip.Any (filter); + } + } + + public static bool ExtractAll (ZipArchive zip, string destination, Action progressCallback = null, Func modifyCallback = null, + Func deleteCallback = null, Func skipCallback = null) + { + int i = 0; + int total = (int)zip.EntryCount; + bool updated = false; + var files = new HashSet (); + var memoryStream = MemoryStreamPool.Shared.Rent (); + try { + foreach (var entry in zip) { + progressCallback?.Invoke (i++, total); + if (entry.IsDirectory) + continue; + if (entry.FullName.Contains ("/__MACOSX/") || + entry.FullName.EndsWith ("/__MACOSX", StringComparison.OrdinalIgnoreCase) || + string.Equals (entry.FullName, ".DS_Store", StringComparison.OrdinalIgnoreCase) || + entry.FullName.EndsWith ("/.DS_Store", StringComparison.OrdinalIgnoreCase)) + continue; + if (skipCallback != null && skipCallback (entry.FullName)) + continue; + var fullName = modifyCallback?.Invoke (entry.FullName) ?? entry.FullName; + var outfile = Path.GetFullPath (Path.Combine (destination, fullName)); + files.Add (outfile); + memoryStream.SetLength (0); //Reuse the stream + entry.Extract (memoryStream); + try { + updated |= CopyIfStreamChanged (memoryStream, outfile); + } catch (PathTooLongException) { + throw new PathTooLongException ($"Could not extract \"{fullName}\" to \"{outfile}\". Path is too long."); + } + } + } finally { + MemoryStreamPool.Shared.Return (memoryStream); + } + if (Directory.Exists (destination)) { + foreach (var file in Directory.GetFiles (destination, "*", SearchOption.AllDirectories)) { + var outfile = Path.GetFullPath (file); + if (outfile.Contains ("/__MACOSX/") || + outfile.EndsWith (".flat", StringComparison.OrdinalIgnoreCase) || + outfile.EndsWith ("files.cache", StringComparison.OrdinalIgnoreCase) || + outfile.EndsWith ("__AndroidLibraryProjects__.zip", StringComparison.OrdinalIgnoreCase) || + outfile.EndsWith ("/__MACOSX", StringComparison.OrdinalIgnoreCase) || + outfile.EndsWith ("/.DS_Store", StringComparison.OrdinalIgnoreCase)) + continue; + if (!files.Contains (outfile) && (deleteCallback?.Invoke (outfile) ?? true)) { + File.Delete (outfile); + updated = true; + } + } + } + return updated; + } + + /// + /// Callback that can be used in combination with ExtractAll for extracting .aar files + /// + public static bool ShouldSkipEntryInAar (string entryFullName) + { + // AAR files may contain other jars not needed for compilation + // See: https://developer.android.com/studio/projects/android-library.html#aar-contents + if (!entryFullName.EndsWith (".jar", StringComparison.OrdinalIgnoreCase)) + return false; + if (entryFullName == "classes.jar" || + entryFullName.StartsWith ("libs/", StringComparison.OrdinalIgnoreCase) || + entryFullName.StartsWith ("libs\\", StringComparison.OrdinalIgnoreCase)) + return false; + // This could be `lint.jar` or `api.jar`, etc. + return true; + } + + public static string HashString (string s) + { + var bytes = Encoding.UTF8.GetBytes (s); + return HashBytes (bytes); + } + + public static string HashBytes (byte [] bytes) + { + using (HashAlgorithm hashAlg = new Crc64 ()) { + byte [] hash = hashAlg.ComputeHash (bytes); + return ToHexString (hash); + } + } + + public static string HashFile (string filename) + { + using (HashAlgorithm hashAlg = new Crc64 ()) { + return HashFile (filename, hashAlg); + } + } + + public static string HashFile (string filename, HashAlgorithm hashAlg) + { + using (Stream file = new FileStream (filename, FileMode.Open, FileAccess.Read)) { + byte[] hash = hashAlg.ComputeHash (file); + return ToHexString (hash); + } + } + + public static string HashStream (Stream stream) + { + stream.Position = 0; + + using (HashAlgorithm hashAlg = new Crc64 ()) { + byte[] hash = hashAlg.ComputeHash (stream); + return ToHexString (hash); + } + } + + public static string ToHexString (byte[] hash) + { + char [] array = new char [hash.Length * 2]; + for (int i = 0, j = 0; i < hash.Length; i += 1, j += 2) { + byte b = hash [i]; + array [j] = GetHexValue (b / 16); + array [j + 1] = GetHexValue (b % 16); + } + return new string (array); + } + + static char GetHexValue (int i) => (char) (i < 10 ? i + 48 : i - 10 + 65); + + public static void DeleteFile (string filename, object log) + { + try { + File.Delete (filename); + } catch (Exception ex) { + var helper = log as TaskLoggingHelper; + helper.LogErrorFromException (ex); + } + } + + const uint ppdb_signature = 0x424a5342; + + public static bool IsPortablePdb (string filename) + { + try { + using (var fs = new FileStream (filename, FileMode.Open, FileAccess.Read)) { + using (var br = new BinaryReader (fs)) { + return br.ReadUInt32 () == ppdb_signature; + } + } + } + catch { + return false; + } + } + + /// + /// Open a file given its path and remove the 3 bytes UTF-8 BOM if there is one + /// + public static void CleanBOM (string filePath) + { + if (string.IsNullOrEmpty (filePath) || !File.Exists (filePath)) + return; + + string temp = null; + try { + using (var input = File.OpenRead (filePath)) { + // Check if the file actually has a BOM + for (int i = 0; i < Utf8Preamble.Length; i++) { + var next = input.ReadByte (); + if (next == -1) + return; + if (Utf8Preamble [i] != (byte) next) + return; + } + + temp = Path.GetTempFileName (); + using (var stream = File.OpenWrite (temp)) + input.CopyTo (stream); + } + + Files.SetWriteable (filePath); + File.Delete (filePath); + File.Copy (temp, filePath); + } finally { + if (temp != null) { + File.Delete (temp); + } + } + } + } +} diff --git a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/LinePreservedXmlWriter.cs b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/LinePreservedXmlWriter.cs new file mode 100644 index 00000000000..a2dd0015a91 --- /dev/null +++ b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/LinePreservedXmlWriter.cs @@ -0,0 +1,146 @@ +// https://github.com/xamarin/xamarin-android/blob/2aea0af1da5c46924dd00587701b6c91391d62f8/src/Xamarin.Android.Build.Tasks/Utilities/LinePreservedXmlWriter.cs + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Xml; +using System.Xml.XPath; + +namespace Microsoft.Android.Build.Tasks +{ + public class LinePreservedXmlWriter : XmlTextWriter + { + LinePreservedTextWriter tw; + + static LinePreservedTextWriter GetLinePreservedTextWriter (TextWriter w) + { + var tw = w as LinePreservedTextWriter; + if (tw == null) + tw = new LinePreservedTextWriter (w); + return tw; + } + + public LinePreservedXmlWriter (TextWriter w) + : this (GetLinePreservedTextWriter (w)) + { + } + + internal LinePreservedXmlWriter (LinePreservedTextWriter w) + : base (w) + { + this.tw = w; + } + + XPathNavigator nav; + + public override void WriteNode (XPathNavigator navigator, bool defattr) + { + XPathNavigator bak = nav; + this.nav = navigator; + IXmlLineInfo li = navigator as IXmlLineInfo; + if (li != null) + tw.ProceedTo (li.LineNumber, li.LinePosition); + base.WriteNode (navigator, defattr); + this.nav = bak; + } + + public override void WriteStartAttribute (string prefix, string localName, string namespaceUri) + { + if (nav != null) + Proceed (nav as IXmlLineInfo); + base.WriteStartAttribute (prefix, localName, namespaceUri); + } + + public override void WriteStartElement (string prefix, string localName, string namespaceUri) + { + if (nav != null) + Proceed (nav as IXmlLineInfo); + base.WriteStartElement (prefix, localName, namespaceUri); + } + + void Proceed (IXmlLineInfo li) + { + if (li == null || !li.HasLineInfo ()) + return; + tw.ProceedTo (li.LineNumber, li.LinePosition); + } + } + + class LinePreservedTextWriter : TextWriter + { + TextWriter w; + int line = 1; + + public LinePreservedTextWriter (TextWriter w) + { + this.w = w; + } + + public override System.Text.Encoding Encoding { + get { return Encoding.Unicode; } + } + + public void ProceedTo (int line, int column) + { + if (line <= 0) + return; + bool wrote = this.line < line; + while (this.line < line) + WriteLine (); + if (wrote) + Write (new string (' ', column)); + } + + public override void Close () + { + w.Close (); + } + + public override void Flush () + { + w.Flush (); + } + + public override void Write (char value) + { + w.Write (value); + if (value == '\n') + line++; + } + + public override void Write (char[] buffer, int index, int count) + { + w.Write (buffer, index, count); + int next = index; + while (next < index + count) { + int idx = Array.IndexOf (buffer, '\n', next, count + (index - next)); + if (idx < 0) + break; + line++; + next = idx + 1; + } + } + + public override void Write (string value) + { + w.Write (value); + int next = 0; + while (next < value.Length) { + int idx = value.IndexOf ('\n', next); + if (idx < 0) + break; + line++; + next = idx + 1; + } + } + + public override void WriteLine () + { + w.WriteLine (); + line++; + } + } +} + diff --git a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildExtensions.cs b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildExtensions.cs new file mode 100644 index 00000000000..d50b3d9367d --- /dev/null +++ b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildExtensions.cs @@ -0,0 +1,285 @@ +// https://github.com/xamarin/xamarin-android/blob/eed430e4dc442ee98046fb13956ef49f29ce7b40/src/Xamarin.Android.Build.Tasks/Utilities/MSBuildExtensions.cs + +using System; +using System.Diagnostics; +using System.Collections.Generic; +using System.IO; +using System.Xml; +using System.Text.RegularExpressions; + +using Microsoft.Build.Utilities; +using Microsoft.Build.Framework; +using Xamarin.Build; + +namespace Microsoft.Android.Build.Tasks +{ + public static class MSBuildExtensions + { + public static void LogDebugMessage (this TaskLoggingHelper log, string message, params object[] messageArgs) + { + log.LogMessage (MessageImportance.Low, message, messageArgs); + } + + public static void LogTaskItems (this TaskLoggingHelper log, string message, ITaskItem[] items) + { + log.LogMessage (message); + + if (items == null) + return; + + foreach (var item in items) + log.LogMessage (" {0}", item.ItemSpec); + } + + public static void LogTaskItems (this TaskLoggingHelper log, string message, params string[] items) + { + log.LogMessage (message); + + if (items == null) + return; + + foreach (var item in items) + log.LogMessage (" {0}", item); + } + + public static void LogDebugTaskItems (this TaskLoggingHelper log, string message, ITaskItem[] items, bool logMetadata = false) + { + log.LogMessage (MessageImportance.Low, message); + + if (items == null) + return; + + foreach (var item in items) { + log.LogMessage (MessageImportance.Low, " {0}", item.ItemSpec); + if (!logMetadata || item.MetadataCount <= 0) + continue; + foreach (string name in item.MetadataNames) + log.LogMessage (MessageImportance.Low, $" {name} = {item.GetMetadata (name)}"); + } + } + + public static void LogDebugTaskItems (this TaskLoggingHelper log, string message, params string[] items) + { + log.LogMessage (MessageImportance.Low, message); + + if (items == null) + return; + + foreach (var item in items) + log.LogMessage (MessageImportance.Low, " {0}", item); + } + + // looking for: mandroid: warning XA9000: message... + static readonly Regex Message = new Regex ( + @"^(?[^: ]+)\s*:\s*(?warning|error) (?[^:]+): (?.*)"); + + public static void LogFromStandardError (this TaskLoggingHelper log, string defaultErrorCode, string message) + { + if (string.IsNullOrEmpty (message)) + return; + + var m = Message.Match (message); + if (!m.Success) { + if (message.IndexOf ("error:", StringComparison.InvariantCultureIgnoreCase) != -1) { + log.LogCodedError (defaultErrorCode, message); + } else { + log.LogMessage (null, defaultErrorCode, null, null, 0, 0, 0, 0, MessageImportance.Low, message); + } + return; + } + + string subcategory = m.Groups ["source"].Value; + string type = m.Groups ["type"].Value; + string code = m.Groups ["code"].Value; + string msg = m.Groups ["message"].Value; + + if (string.IsNullOrEmpty (code)) + code = defaultErrorCode; + + if (type == "warning") + log.LogWarning (subcategory, code, string.Empty, string.Empty, 0, 0, 0, 0, "{0}", msg); + else + log.LogError (subcategory, code, string.Empty, string.Empty, 0, 0, 0, 0, "{0}", msg); + } + + public static void LogDebugTaskItemsAndLogical (this TaskLoggingHelper log, string message, ITaskItem[] items) + { + log.LogMessage (MessageImportance.Low, message); + + if (items == null) + return; + + foreach (var item in items) { + log.LogMessage (MessageImportance.Low, " {0}", item.ItemSpec); + log.LogMessage (MessageImportance.Low, " [{0}]", item.GetMetadata ("LogicalName")); + } + } + + public static void LogCodedError (this TaskLoggingHelper log, string code, string message, params object[] messageArgs) + { + log.LogError (string.Empty, code, string.Empty, string.Empty, 0, 0, 0, 0, message, messageArgs); + } + + public static void LogCodedError (this TaskLoggingHelper log, string code, string file, int lineNumber, string message, params object[] messageArgs) + { + log.LogError (string.Empty, code, string.Empty, file, lineNumber, 0, 0, 0, message, messageArgs); + } + + public static void LogCodedWarning (this TaskLoggingHelper log, string code, string message, params object [] messageArgs) + { + log.LogWarning (string.Empty, code, string.Empty, string.Empty, 0, 0, 0, 0, message, messageArgs); + } + + public static void LogCodedWarning (this TaskLoggingHelper log, string code, string file, int lineNumber, string message, params object [] messageArgs) + { + log.LogWarning (string.Empty, code, string.Empty, file, lineNumber, 0, 0, 0, message, messageArgs); + } + + /// + /// Logs a coded warning from a node in an XML document + /// + /// An element that implements IXmlLineInfo + public static void LogWarningForXmlNode (this TaskLoggingHelper log, string code, string file, object node, string message, params object [] messageArgs) + { + int lineNumber = 0; + int columnNumber = 0; + var lineInfo = node as IXmlLineInfo; + if (lineInfo != null && lineInfo.HasLineInfo ()) { + lineNumber = lineInfo.LineNumber; + columnNumber = lineInfo.LinePosition; + } + log.LogWarning ( + subcategory: string.Empty, + warningCode: code, + helpKeyword: string.Empty, + file: file, + lineNumber: lineNumber, + columnNumber: columnNumber, + endLineNumber: 0, + endColumnNumber: 0, + message: message, + messageArgs: messageArgs + ); + } + + public static Action CreateTaskLogger (this Task task) + { + Action logger = (level, value) => { + switch (level) { + case TraceLevel.Error: + task.Log.LogError ("{0}", value); + break; + case TraceLevel.Warning: + task.Log.LogWarning ("{0}", value); + break; + default: + task.Log.LogDebugMessage ("{0}", value); + break; + } + }; + return logger; + } + + public static Action CreateTaskLogger (this AsyncTask task) + { + Action logger = (level, value) => { + switch (level) { + case TraceLevel.Error: + task.LogError (value); + break; + case TraceLevel.Warning: + task.LogWarning (value); + break; + default: + task.LogDebugMessage (value); + break; + } + }; + return logger; + } + + + public static IEnumerable Concat (params ITaskItem[][] values) + { + if (values == null) + yield break; + foreach (ITaskItem[] v in values) { + if (v == null) + continue; + foreach (ITaskItem t in v) + yield return t; + } + } + + public static string FixupResourceFilename (string file, string resourceDir, Dictionary resourceNameCaseMap) + { + var targetfile = file; + if (resourceDir != null && targetfile.StartsWith (resourceDir, StringComparison.InvariantCultureIgnoreCase)) { + targetfile = file.Substring (resourceDir.Length).TrimStart (Path.DirectorySeparatorChar); + if (resourceNameCaseMap.TryGetValue (targetfile, out string temp)) + targetfile = temp; + targetfile = Path.Combine ("Resources", targetfile); + } + return targetfile; + } + + public static void FixupResourceFilenameAndLogCodedError (this TaskLoggingHelper log, string code, string message, string file, string resourceDir, Dictionary resourceNameCaseMap) + { + var targetfile = FixupResourceFilename (file, resourceDir, resourceNameCaseMap); + log.LogCodedError (code, file: targetfile, lineNumber: 0, message: message); + } + + public static void FixupResourceFilenameAndLogCodedWarning (this TaskLoggingHelper log, string code, string message, string file, string resourceDir, Dictionary resourceNameCaseMap) + { + var targetfile = FixupResourceFilename (file, resourceDir, resourceNameCaseMap); + log.LogCodedWarning (code, file: targetfile, lineNumber: 0, message: message); + } + + /// + /// Sets the default value for %(DestinationSubPath) if it is not already set + /// + public static void SetDestinationSubPath (this ITaskItem assembly) + { + if (string.IsNullOrEmpty (assembly.GetMetadata ("DestinationSubPath"))) { + var directory = assembly.GetMetadata ("DestinationSubDirectory"); + var path = Path.Combine (directory, Path.GetFileName (assembly.ItemSpec)); + assembly.SetMetadata ("DestinationSubPath", path); + } + } + + static readonly string AssemblyLocation = typeof (MSBuildExtensions).Assembly.Location; + + /// + /// IBuildEngine4.RegisterTaskObject, but adds the current assembly path into the key + /// + public static void RegisterTaskObjectAssemblyLocal (this IBuildEngine4 engine, object key, object value, RegisteredTaskObjectLifetime lifetime, bool allowEarlyCollection = false) => + engine.RegisterTaskObject ((AssemblyLocation, key), value, lifetime, allowEarlyCollection); + + /// + /// IBuildEngine4.GetRegisteredTaskObject, but adds the current assembly path into the key + /// + public static object GetRegisteredTaskObjectAssemblyLocal (this IBuildEngine4 engine, object key, RegisteredTaskObjectLifetime lifetime) => + engine.GetRegisteredTaskObject ((AssemblyLocation, key), lifetime); + + /// + /// Generic version of IBuildEngine4.GetRegisteredTaskObject, but adds the current assembly path into the key + /// + public static T GetRegisteredTaskObjectAssemblyLocal (this IBuildEngine4 engine, object key, RegisteredTaskObjectLifetime lifetime) + where T : class => + engine.GetRegisteredTaskObject ((AssemblyLocation, key), lifetime) as T; + + + /// + /// IBuildEngine4.UnregisterTaskObject, but adds the current assembly path into the key + /// + public static object UnregisterTaskObjectAssemblyLocal (this IBuildEngine4 engine, object key, RegisteredTaskObjectLifetime lifetime) => + engine.UnregisterTaskObject ((AssemblyLocation, key), lifetime); + + /// + /// Generic version of IBuildEngine4.UnregisterTaskObject, but adds the current assembly path into the key + /// + public static T UnregisterTaskObjectAssemblyLocal (this IBuildEngine4 engine, object key, RegisteredTaskObjectLifetime lifetime) + where T : class => + engine.UnregisterTaskObject ((AssemblyLocation, key), lifetime) as T; + } +} diff --git a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems new file mode 100644 index 00000000000..4fb5136f3a3 --- /dev/null +++ b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems @@ -0,0 +1,60 @@ + + + + + + + 15.1.0.0 + 16.5 + 1.0.20 + + + + + + + + + + + + + + True + $(MSBuildToolsPath)\Microsoft.Build.dll + + + True + $(MSBuildToolsPath)\Microsoft.Build.Framework.dll + + + True + $(MSBuildToolsPath)\Microsoft.Build.Tasks.Core.dll + + + True + $(MSBuildToolsPath)\Microsoft.Build.Utilities.Core.dll + + + + + + + + + + + + + + + + + + + PreserveNewest + + + + diff --git a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MemoryStreamPool.cs b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MemoryStreamPool.cs new file mode 100644 index 00000000000..2a8174a5f5d --- /dev/null +++ b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MemoryStreamPool.cs @@ -0,0 +1,105 @@ +// https://github.com/xamarin/xamarin-android/blob/c9e5f58d2fa92283917ae4e8f9c370adfd40b908/src/Xamarin.Android.Build.Tasks/Utilities/MemoryStreamPool.cs + +using System.IO; +using System.Text; + +namespace Microsoft.Android.Build.Tasks +{ + /// + /// A class for pooling and reusing MemoryStream objects. + /// + /// Based on: + /// https://docs.microsoft.com/dotnet/standard/collections/thread-safe/how-to-create-an-object-pool + /// https://docs.microsoft.com/dotnet/api/system.buffers.arraypool-1 + /// + public class MemoryStreamPool : ObjectPool + { + public static readonly Encoding UTF8withoutBOM = new UTF8Encoding (encoderShouldEmitUTF8Identifier: false); + + /// + /// Static instance across the entire process. Use this most of the time. + /// + public static readonly MemoryStreamPool Shared = new MemoryStreamPool (); + + public MemoryStreamPool () : base (() => new MemoryStream ()) { } + + public override void Return (MemoryStream stream) + { + // We want to throw here before base.Return() if it was disposed + stream.SetLength (0); + base.Return (stream); + } + + /// + /// Creates a StreamWriter that uses the underlying MemoryStreamPool. Calling Dispose() will Return() the MemoryStream. + /// By default uses MonoAndroidHelper.UTF8withoutBOM for the encoding. + /// + public StreamWriter CreateStreamWriter () => CreateStreamWriter (Files.UTF8withoutBOM); + + /// + /// Creates a StreamWriter that uses the underlying MemoryStreamPool. Calling Dispose() will Return() the MemoryStream. + /// + public StreamWriter CreateStreamWriter (Encoding encoding) => new ReturningStreamWriter (this, Rent (), encoding); + + /// + /// Creates a BinaryWriter that uses the underlying MemoryStreamPool. Calling Dispose() will Return() the MemoryStream. + /// By default uses MonoAndroidHelper.UTF8withoutBOM for the encoding. + /// + public BinaryWriter CreateBinaryWriter () => CreateBinaryWriter (Files.UTF8withoutBOM); + + /// + /// Creates a BinaryWriter that uses the underlying MemoryStreamPool. Calling Dispose() will Return() the MemoryStream. + /// + public BinaryWriter CreateBinaryWriter (Encoding encoding) => new ReturningBinaryWriter (this, Rent (), encoding); + + class ReturningStreamWriter : StreamWriter + { + readonly MemoryStreamPool pool; + readonly MemoryStream stream; + bool returned; + + public ReturningStreamWriter (MemoryStreamPool pool, MemoryStream stream, Encoding encoding) + : base (stream, encoding, bufferSize: 8 * 1024, leaveOpen: true) + { + this.pool = pool; + this.stream = stream; + } + + protected override void Dispose (bool disposing) + { + base.Dispose (disposing); + + //NOTE: Dispose() can be called multiple times + if (disposing && !returned) { + returned = true; + pool.Return (stream); + } + } + } + + class ReturningBinaryWriter : BinaryWriter + { + readonly MemoryStreamPool pool; + readonly MemoryStream stream; + bool returned; + + public ReturningBinaryWriter (MemoryStreamPool pool, MemoryStream stream, Encoding encoding) + : base (stream, encoding, leaveOpen: true) + { + this.pool = pool; + this.stream = stream; + } + + protected override void Dispose (bool disposing) + { + base.Dispose (disposing); + + //NOTE: Dispose() can be called multiple times + if (disposing && !returned) { + returned = true; + pool.Return (stream); + } + } + } + } +} diff --git a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Microsoft.Android.Build.BaseTasks.csproj b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Microsoft.Android.Build.BaseTasks.csproj new file mode 100644 index 00000000000..1542aadbd91 --- /dev/null +++ b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Microsoft.Android.Build.BaseTasks.csproj @@ -0,0 +1,20 @@ + + + + + + + netstandard2.0 + Microsoft.Android.Build.Tasks + true + true + true + + + + + + + + + diff --git a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/ObjectPool.cs b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/ObjectPool.cs new file mode 100644 index 00000000000..91018c17bd1 --- /dev/null +++ b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/ObjectPool.cs @@ -0,0 +1,39 @@ +// https://github.com/xamarin/xamarin-android/blob/87ee20c59a09ba4314f4226e1c77ee2331c3f09f/src/Xamarin.Android.Build.Tasks/Utilities/ObjectPool.cs + +using System; +using System.Collections.Concurrent; + +namespace Microsoft.Android.Build.Tasks +{ + /// + /// A class for pooling and reusing objects. See MemoryStreamPool. + /// + /// Based on: + /// https://docs.microsoft.com/dotnet/standard/collections/thread-safe/how-to-create-an-object-pool + /// https://docs.microsoft.com/dotnet/api/system.buffers.arraypool-1 + /// + public class ObjectPool + { + readonly ConcurrentBag bag = new ConcurrentBag(); + readonly Func generator; + + public ObjectPool (Func generator) + { + if (generator == null) + throw new ArgumentNullException (nameof (generator)); + this.generator = generator; + } + + public virtual T Rent () + { + if (bag.TryTake (out T item)) + return item; + return generator (); + } + + public virtual void Return (T item) + { + bag.Add (item); + } + } +} diff --git a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.resx b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.resx new file mode 100644 index 00000000000..c8a29d55556 --- /dev/null +++ b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.resx @@ -0,0 +1,105 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 1.3 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Unhandled exception: {0} + {0} - The exception message of the associated exception + + diff --git a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/xlf/Resources.cs.xlf b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/xlf/Resources.cs.xlf new file mode 100644 index 00000000000..0492b122d5d --- /dev/null +++ b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/xlf/Resources.cs.xlf @@ -0,0 +1,12 @@ + + + + + + Unhandled exception: {0} + Neošetřená výjimka: {0} + {0} - The exception message of the associated exception + + + + \ No newline at end of file diff --git a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/xlf/Resources.de.xlf b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/xlf/Resources.de.xlf new file mode 100644 index 00000000000..1810bc2e15c --- /dev/null +++ b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/xlf/Resources.de.xlf @@ -0,0 +1,12 @@ + + + + + + Unhandled exception: {0} + Ausnahmefehler: {0} + {0} - The exception message of the associated exception + + + + \ No newline at end of file diff --git a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/xlf/Resources.es.xlf b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/xlf/Resources.es.xlf new file mode 100644 index 00000000000..5e1af08d70b --- /dev/null +++ b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/xlf/Resources.es.xlf @@ -0,0 +1,12 @@ + + + + + + Unhandled exception: {0} + Excepción no controlada: {0} + {0} - The exception message of the associated exception + + + + \ No newline at end of file diff --git a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/xlf/Resources.fr.xlf b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/xlf/Resources.fr.xlf new file mode 100644 index 00000000000..b7f2e63566a --- /dev/null +++ b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/xlf/Resources.fr.xlf @@ -0,0 +1,12 @@ + + + + + + Unhandled exception: {0} + Exception non prise en charge : {0} + {0} - The exception message of the associated exception + + + + \ No newline at end of file diff --git a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/xlf/Resources.it.xlf b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/xlf/Resources.it.xlf new file mode 100644 index 00000000000..b8b4d0a1a85 --- /dev/null +++ b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/xlf/Resources.it.xlf @@ -0,0 +1,12 @@ + + + + + + Unhandled exception: {0} + Eccezione non gestita: {0} + {0} - The exception message of the associated exception + + + + \ No newline at end of file diff --git a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/xlf/Resources.ja.xlf b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/xlf/Resources.ja.xlf new file mode 100644 index 00000000000..b63e2db5453 --- /dev/null +++ b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/xlf/Resources.ja.xlf @@ -0,0 +1,12 @@ + + + + + + Unhandled exception: {0} + ハンドルされない例外: {0} + {0} - The exception message of the associated exception + + + + \ No newline at end of file diff --git a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/xlf/Resources.ko.xlf b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/xlf/Resources.ko.xlf new file mode 100644 index 00000000000..2f43d0380be --- /dev/null +++ b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/xlf/Resources.ko.xlf @@ -0,0 +1,12 @@ + + + + + + Unhandled exception: {0} + 처리되지 않은 예외: {0} + {0} - The exception message of the associated exception + + + + \ No newline at end of file diff --git a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/xlf/Resources.pl.xlf b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/xlf/Resources.pl.xlf new file mode 100644 index 00000000000..46fc0ca8649 --- /dev/null +++ b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/xlf/Resources.pl.xlf @@ -0,0 +1,12 @@ + + + + + + Unhandled exception: {0} + Nieobsłużony wyjątek: {0} + {0} - The exception message of the associated exception + + + + \ No newline at end of file diff --git a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/xlf/Resources.pt-BR.xlf b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/xlf/Resources.pt-BR.xlf new file mode 100644 index 00000000000..951acf68266 --- /dev/null +++ b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/xlf/Resources.pt-BR.xlf @@ -0,0 +1,12 @@ + + + + + + Unhandled exception: {0} + Exceção sem tratamento: {0} + {0} - The exception message of the associated exception + + + + \ No newline at end of file diff --git a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/xlf/Resources.ru.xlf b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/xlf/Resources.ru.xlf new file mode 100644 index 00000000000..7984f3c1c1c --- /dev/null +++ b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/xlf/Resources.ru.xlf @@ -0,0 +1,12 @@ + + + + + + Unhandled exception: {0} + Необработанное исключение: {0} + {0} - The exception message of the associated exception + + + + \ No newline at end of file diff --git a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/xlf/Resources.tr.xlf b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/xlf/Resources.tr.xlf new file mode 100644 index 00000000000..79944401b3b --- /dev/null +++ b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/xlf/Resources.tr.xlf @@ -0,0 +1,12 @@ + + + + + + Unhandled exception: {0} + İşlenmeyen özel durum: {0} + {0} - The exception message of the associated exception + + + + \ No newline at end of file diff --git a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/xlf/Resources.zh-Hans.xlf b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/xlf/Resources.zh-Hans.xlf new file mode 100644 index 00000000000..11a918c646d --- /dev/null +++ b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/xlf/Resources.zh-Hans.xlf @@ -0,0 +1,12 @@ + + + + + + Unhandled exception: {0} + 未处理的异常: {0} + {0} - The exception message of the associated exception + + + + \ No newline at end of file diff --git a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/xlf/Resources.zh-Hant.xlf b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/xlf/Resources.zh-Hant.xlf new file mode 100644 index 00000000000..6738c0c00a6 --- /dev/null +++ b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/xlf/Resources.zh-Hant.xlf @@ -0,0 +1,12 @@ + + + + + + Unhandled exception: {0} + 未處理的例外狀況: {0} + {0} - The exception message of the associated exception + + + + \ No newline at end of file diff --git a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/UnhandledExceptionLogger.cs b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/UnhandledExceptionLogger.cs new file mode 100644 index 00000000000..07a3fc3d7c2 --- /dev/null +++ b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/UnhandledExceptionLogger.cs @@ -0,0 +1,89 @@ +// https://github.com/xamarin/xamarin-android/blob/9fca138604c53989e1cff7fc0c2e939583b4da28/src/Xamarin.Android.Build.Tasks/Utilities/UnhandledExceptionLogger.cs + +using System; +using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; +using Microsoft.Build.Utilities; +using Xamarin.Build; + +namespace Microsoft.Android.Build.Tasks +{ + public static class UnhandledExceptionLogger + { + public static void LogUnhandledException (this AsyncTask task, string prefix, Exception ex) + { + LogUnhandledException ((code, message) => task.LogCodedError (code, message), prefix, ex); + } + + public static void LogUnhandledException (this TaskLoggingHelper log, string prefix, Exception ex) + { + LogUnhandledException ((code, message) => log.LogCodedError (code, message), prefix, ex); + } + + static void LogUnhandledException (Action logCodedError, string prefix, Exception ex) + { + prefix = "XA" + prefix; + + // Some ordering is necessary here to ensure exceptions are before their base exceptions + if (ex is NullReferenceException) + logCodedError (prefix + "7001", ex.ToString ()); + else if (ex is ArgumentOutOfRangeException) // ArgumentException + logCodedError (prefix + "7002", ex.ToString ()); + else if (ex is ArgumentNullException) // ArgumentException + logCodedError (prefix + "7003", ex.ToString ()); + else if (ex is ArgumentException) + logCodedError (prefix + "7004", ex.ToString ()); + else if (ex is FormatException) + logCodedError (prefix + "7005", ex.ToString ()); + else if (ex is IndexOutOfRangeException) + logCodedError (prefix + "7006", ex.ToString ()); + else if (ex is InvalidCastException) + logCodedError (prefix + "7007", ex.ToString ()); + else if (ex is ObjectDisposedException) // InvalidOperationException + logCodedError (prefix + "7008", ex.ToString ()); + else if (ex is InvalidOperationException) + logCodedError (prefix + "7009", ex.ToString ()); + else if (ex is InvalidProgramException) + logCodedError (prefix + "7010", ex.ToString ()); + else if (ex is KeyNotFoundException) + logCodedError (prefix + "7011", ex.ToString ()); + else if (ex is TaskCanceledException) // OperationCanceledException + logCodedError (prefix + "7012", ex.ToString ()); + else if (ex is OperationCanceledException) + logCodedError (prefix + "7013", ex.ToString ()); + else if (ex is OutOfMemoryException) + logCodedError (prefix + "7014", ex.ToString ()); + else if (ex is NotSupportedException) + logCodedError (prefix + "7015", ex.ToString ()); + else if (ex is StackOverflowException) + logCodedError (prefix + "7016", ex.ToString ()); + else if (ex is TimeoutException) + logCodedError (prefix + "7017", ex.ToString ()); + else if (ex is TypeInitializationException) + logCodedError (prefix + "7018", ex.ToString ()); + else if (ex is UnauthorizedAccessException) + logCodedError (prefix + "7019", ex.ToString ()); + else if (ex is ApplicationException) + logCodedError (prefix + "7020", ex.ToString ()); + else if (ex is KeyNotFoundException) + logCodedError (prefix + "7021", ex.ToString ()); + else if (ex is PathTooLongException) // IOException + logCodedError (prefix + "7022", ex.ToString ()); + else if (ex is DirectoryNotFoundException) // IOException + logCodedError (prefix + "7023", ex.ToString ()); + else if (ex is DriveNotFoundException) // IOException + logCodedError (prefix + "7025", ex.ToString ()); + else if (ex is EndOfStreamException) // IOException + logCodedError (prefix + "7026", ex.ToString ()); + else if (ex is FileLoadException) // IOException + logCodedError (prefix + "7027", ex.ToString ()); + else if (ex is FileNotFoundException) // IOException + logCodedError (prefix + "7028", ex.ToString ()); + else if (ex is IOException) + logCodedError (prefix + "7024", ex.ToString ()); + else + logCodedError (prefix + "7000", ex.ToString ()); + } + } +} diff --git a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/XDocumentExtensions.cs b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/XDocumentExtensions.cs new file mode 100644 index 00000000000..c3979a786ac --- /dev/null +++ b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/XDocumentExtensions.cs @@ -0,0 +1,56 @@ +// https://github.com/xamarin/xamarin-android/blob/72bb66856814e0b64e02b21be66a6dc03e1ffcb6/src/Xamarin.Android.Build.Tasks/Utilities/XDocumentExtensions.cs + +using System.Linq; +using System.Xml.XPath; +using System.Xml.Linq; +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; + +namespace Microsoft.Android.Build.Tasks +{ + public static class XDocumentExtensions + { + const string PathsElementName = "Paths"; + + public static ITaskItem[] GetPathsAsTaskItems (this XDocument doc, params string[] paths) + { + var e = doc.Elements (PathsElementName); + foreach (var p in paths) + e = e.Elements (p); + return e.Select (ToTaskItem).ToArray (); + } + + static ITaskItem ToTaskItem (XElement element) + { + var taskItem = new TaskItem (element.Value); + foreach (var attribute in element.Attributes ()) { + taskItem.SetMetadata (attribute.Name.LocalName, attribute.Value); + } + return taskItem; + } + + public static string[] GetPaths (this XDocument doc, params string[] paths) + { + var e = doc.Elements (PathsElementName); + foreach (var p in paths) + e = e.Elements (p); + return e.Select (p => p.Value).ToArray (); + } + + public static string ToFullString (this XElement element) + { + return element.ToString (SaveOptions.DisableFormatting); + } + + public static bool SaveIfChanged (this XDocument document, string fileName) + { + using (var sw = MemoryStreamPool.Shared.CreateStreamWriter ()) + using (var xw = new LinePreservedXmlWriter (sw)) { + xw.WriteNode (document.CreateNavigator (), false); + xw.Flush (); + return Files.CopyIfStreamChanged (sw.BaseStream, fileName); + } + } + } +} + diff --git a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/global.json b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/global.json new file mode 100644 index 00000000000..be2ca665125 --- /dev/null +++ b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/global.json @@ -0,0 +1,5 @@ +{ + "msbuild-sdks": { + "Microsoft.DotNet.Arcade.Sdk": "6.0.0-beta.20615.1" + } +} diff --git a/external/xamarin-android-tools/tests/Microsoft.Android.Build.BaseTasks-Tests/FilesTests.cs b/external/xamarin-android-tools/tests/Microsoft.Android.Build.BaseTasks-Tests/FilesTests.cs new file mode 100644 index 00000000000..e1953cb5a54 --- /dev/null +++ b/external/xamarin-android-tools/tests/Microsoft.Android.Build.BaseTasks-Tests/FilesTests.cs @@ -0,0 +1,606 @@ +// https://github.com/xamarin/xamarin-android/blob/34acbbae6795854cc4e9f8eb7167ab011e0266b4/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/MonoAndroidHelperTests.cs +// https://github.com/xamarin/xamarin-android/blob/799506a9dfb746b8bdc8a4ab77e19eee875f00e3/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/FilesTests.cs + +using NUnit.Framework; +using System; +using System.IO; +using System.Runtime.InteropServices; +using System.Text; +using Xamarin.Tools.Zip; +using Microsoft.Android.Build.Tasks; + +namespace Microsoft.Android.Build.BaseTasks.Tests +{ + [TestFixture] + public class FilesTests + { + bool IsWindows = RuntimeInformation.IsOSPlatform (OSPlatform.Windows); + const int MaxFileName = 255; + + static readonly Encoding encoding = Encoding.UTF8; + string tempDir; + MemoryStream stream; + + [SetUp] + public void SetUp () + { + tempDir = Path.Combine (Path.GetTempPath (), TestContext.CurrentContext.Test.Name); + stream = new MemoryStream (); + } + + [TearDown] + public void TearDown () + { + stream.Dispose (); + + var dir = Files.ToLongPath (tempDir); + if (Directory.Exists (dir)) + Directory.Delete (dir, recursive: true); + } + + [Test] + public void CopyIfStringChanged () + { + var foo = "bar"; + Assert.IsTrue (Files.CopyIfStringChanged (foo, tempDir), "Should write on new file."); + FileAssert.Exists (tempDir); + Assert.IsFalse (Files.CopyIfStringChanged (foo, tempDir), "Should *not* write unless changed."); + foo += "\n"; + Assert.IsTrue (Files.CopyIfStringChanged (foo, tempDir), "Should write when changed."); + } + + [Test] + public void CopyIfBytesChanged () + { + var foo = new byte [32]; + Assert.IsTrue (Files.CopyIfBytesChanged (foo, tempDir), "Should write on new file."); + FileAssert.Exists (tempDir); + Assert.IsFalse (Files.CopyIfBytesChanged (foo, tempDir), "Should *not* write unless changed."); + foo [0] = 0xFF; + Assert.IsTrue (Files.CopyIfBytesChanged (foo, tempDir), "Should write when changed."); + } + + [Test] + public void CopyIfStreamChanged () + { + using (var foo = new MemoryStream ()) + using (var writer = new StreamWriter (foo)) { + writer.WriteLine ("bar"); + writer.Flush (); + + Assert.IsTrue (Files.CopyIfStreamChanged (foo, tempDir), "Should write on new file."); + FileAssert.Exists (tempDir); + Assert.IsFalse (Files.CopyIfStreamChanged (foo, tempDir), "Should *not* write unless changed."); + writer.WriteLine (); + writer.Flush (); + Assert.IsTrue (Files.CopyIfStreamChanged (foo, tempDir), "Should write when changed."); + } + } + + [Test] + public void CopyIfStringChanged_NewDirectory () + { + tempDir = Path.Combine (tempDir, "foo.txt"); + + var foo = "bar"; + Assert.IsTrue (Files.CopyIfStringChanged (foo, tempDir), "Should write on new file."); + FileAssert.Exists (tempDir); + } + + [Test] + public void CopyIfBytesChanged_NewDirectory () + { + tempDir = Path.Combine (tempDir, "foo.bin"); + + var foo = new byte [32]; + Assert.IsTrue (Files.CopyIfBytesChanged (foo, tempDir), "Should write on new file."); + FileAssert.Exists (tempDir); + } + + [Test] + public void CopyIfStreamChanged_NewDirectory () + { + tempDir = Path.Combine (tempDir, "foo.txt"); + + using (var foo = new MemoryStream ()) + using (var writer = new StreamWriter (foo)) { + writer.WriteLine ("bar"); + writer.Flush (); + + Assert.IsTrue (Files.CopyIfStreamChanged (foo, tempDir), "Should write on new file."); + FileAssert.Exists (tempDir); + } + } + + [Test] + public void CopyIfBytesChanged_Readonly () + { + if (File.Exists (tempDir)) { + File.SetAttributes (tempDir, FileAttributes.Normal); + } + File.WriteAllText (tempDir, ""); + File.SetAttributes (tempDir, FileAttributes.ReadOnly); + + var foo = new byte [32]; + Assert.IsTrue (Files.CopyIfBytesChanged (foo, tempDir), "Should write on new file."); + FileAssert.Exists (tempDir); + } + + [Test] + public void CleanBOM_Readonly () + { + var encoding = Encoding.UTF8; + Directory.CreateDirectory (tempDir); + tempDir = Path.Combine (tempDir, "foo.txt"); + if (File.Exists (tempDir)) { + File.SetAttributes (tempDir, FileAttributes.Normal); + } + using (var stream = File.Create (tempDir)) + using (var writer = new StreamWriter (stream, encoding)) { + writer.Write ("This will have a BOM"); + } + File.SetAttributes (tempDir, FileAttributes.ReadOnly); + var before = File.ReadAllBytes (tempDir); + Files.CleanBOM (tempDir); + var after = File.ReadAllBytes (tempDir); + var preamble = encoding.GetPreamble (); + Assert.AreEqual (before.Length, after.Length + preamble.Length, "BOM should be removed!"); + } + + [Test] + public void CopyIfStreamChanged_MemoryStreamPool_StreamWriter () + { + var pool = new MemoryStreamPool (); + var expected = pool.Rent (); + pool.Return (expected); + + using (var writer = pool.CreateStreamWriter ()) { + writer.WriteLine ("bar"); + writer.Flush (); + + Assert.IsTrue (Files.CopyIfStreamChanged (writer.BaseStream, tempDir), "Should write on new file."); + FileAssert.Exists (tempDir); + } + + var actual = pool.Rent (); + Assert.AreSame (expected, actual); + Assert.AreEqual (0, actual.Length); + } + + [Test] + public void CopyIfStreamChanged_MemoryStreamPool_BinaryWriter () + { + var pool = new MemoryStreamPool (); + var expected = pool.Rent (); + pool.Return (expected); + + using (var writer = pool.CreateBinaryWriter ()) { + writer.Write (42); + writer.Flush (); + + Assert.IsTrue (Files.CopyIfStreamChanged (writer.BaseStream, tempDir), "Should write on new file."); + FileAssert.Exists (tempDir); + } + + var actual = pool.Rent (); + Assert.AreSame (expected, actual); + Assert.AreEqual (0, actual.Length); + } + + [Test] + public void SetWriteable () + { + File.WriteAllText (tempDir, contents: "foo"); + File.SetAttributes (tempDir, FileAttributes.ReadOnly); + + Files.SetWriteable (tempDir); + + var attributes = File.GetAttributes (tempDir); + Assert.AreEqual (FileAttributes.Normal, attributes); + File.WriteAllText (tempDir, contents: "bar"); + } + + [Test] + public void SetDirectoryWriteable () + { + Directory.CreateDirectory (tempDir); + try { + var directoryInfo = new DirectoryInfo (tempDir); + directoryInfo.Attributes |= FileAttributes.ReadOnly; + Files.SetDirectoryWriteable (tempDir); + + directoryInfo = new DirectoryInfo (tempDir); + Assert.AreEqual (FileAttributes.Directory, directoryInfo.Attributes); + } finally { + Directory.Delete (tempDir); + } + } + + void AssertFile (string path, string contents) + { + var fullPath = Path.Combine (tempDir, path); + FileAssert.Exists (fullPath); + Assert.AreEqual (contents, File.ReadAllText (fullPath), $"Contents did not match at path: {path}"); + } + + void AssertFileDoesNotExist (string path) + { + FileAssert.DoesNotExist (Path.Combine (tempDir, path)); + } + + bool ExtractAll (MemoryStream stream) + { + using (var zip = ZipArchive.Open (stream)) { + return Files.ExtractAll (zip, tempDir); + } + } + + string NewFile (string contents = null, string fileName = "") + { + if (string.IsNullOrEmpty (fileName)) { + fileName = Path.GetRandomFileName (); + } + var path = Path.Combine (tempDir, fileName); + if (!string.IsNullOrEmpty (contents)) { + Directory.CreateDirectory (Path.GetDirectoryName (path)); + if (IsWindows && path.Length >= Files.MaxPath) { + File.WriteAllText (Files.ToLongPath (path), contents); + } else { + File.WriteAllText (path, contents); + } + } + return path; + } + + Stream NewStream (string contents) => new MemoryStream (Encoding.Default.GetBytes (contents)); + + [Test] + public void CopyIfChanged_NoChanges () + { + var src = NewFile ("foo"); + var dest = NewFile ("foo"); + Assert.IsFalse (Files.CopyIfChanged (src, dest), "No change should have occurred"); + FileAssert.AreEqual (src, dest); + } + + [Test] + public void CopyIfChanged_NoExist () + { + var src = NewFile ("foo"); + var dest = NewFile (); + Assert.IsTrue (Files.CopyIfChanged (src, dest), "Changes should have occurred"); + FileAssert.AreEqual (src, dest); + } + + [Test] + public void CopyIfChanged_LongPath () + { + var src = NewFile (contents: "foo"); + var dest = NewFile (contents: "bar", fileName: "bar".PadRight (MaxFileName, 'N')); + dest = Files.ToLongPath (dest); + Assert.IsTrue (Files.CopyIfChanged (src, dest), "Changes should have occurred"); + FileAssert.AreEqual (src, dest); + } + + [Test] + public void CopyIfChanged_Changes () + { + var src = NewFile ("foo"); + var dest = NewFile ("bar"); + Assert.IsTrue (Files.CopyIfChanged (src, dest), "Changes should have occurred"); + FileAssert.AreEqual (src, dest); + } + + [Test] + public void CopyIfChanged_Readonly () + { + var src = NewFile ("foo"); + var dest = NewFile ("bar"); + File.SetAttributes (dest, FileAttributes.ReadOnly); + Assert.IsTrue (Files.CopyIfChanged (src, dest), "Changes should have occurred"); + FileAssert.AreEqual (src, dest); + } + + [Test] + public void CopyIfChanged_CasingChange () + { + var src = NewFile (contents: "foo"); + var dest = NewFile (contents: "Foo", fileName: "foo"); + dest = dest.Replace ("foo", "Foo"); + Assert.IsTrue (Files.CopyIfChanged (src, dest), "Changes should have occurred"); + FileAssert.AreEqual (src, dest); + + var files = Directory.GetFiles (Path.GetDirectoryName (dest), "Foo"); + Assert.AreEqual ("Foo", Path.GetFileName (files [0])); + } + + [Test] + public void CopyIfStringChanged_NoChanges () + { + var dest = NewFile ("foo"); + Assert.IsFalse (Files.CopyIfStringChanged ("foo", dest), "No change should have occurred"); + FileAssert.Exists (dest); + } + + [Test] + public void CopyIfStringChanged_NoExist () + { + var dest = NewFile (); + Assert.IsTrue (Files.CopyIfStringChanged ("foo", dest), "Changes should have occurred"); + FileAssert.Exists (dest); + } + + [Test] + public void CopyIfStringChanged_LongPath () + { + var dest = NewFile (fileName: "bar".PadRight (MaxFileName, 'N')); + dest = Files.ToLongPath (dest); + Assert.IsTrue (Files.CopyIfStringChanged ("foo", dest), "Changes should have occurred"); + FileAssert.Exists (dest); + } + + [Test] + public void CopyIfStringChanged_Changes () + { + var dest = NewFile ("bar"); + Assert.IsTrue (Files.CopyIfStringChanged ("foo", dest), "Changes should have occurred"); + FileAssert.Exists (dest); + } + + [Test] + public void CopyIfStringChanged_Readonly () + { + var dest = NewFile ("bar"); + File.SetAttributes (dest, FileAttributes.ReadOnly); + Assert.IsTrue (Files.CopyIfStringChanged ("foo", dest), "Changes should have occurred"); + FileAssert.Exists (dest); + } + + [Test] + public void CopyIfStringChanged_CasingChange () + { + var dest = NewFile (contents: "foo", fileName: "foo"); + dest = dest.Replace ("foo", "Foo"); + Assert.IsTrue (Files.CopyIfStringChanged ("Foo", dest), "Changes should have occurred"); + FileAssert.Exists (dest); + Assert.AreEqual ("Foo", File.ReadAllText (dest), "File contents should match"); + + var files = Directory.GetFiles (Path.GetDirectoryName (dest), "Foo"); + Assert.AreEqual ("Foo", Path.GetFileName (files [0]), "File name should match"); + } + + [Test] + public void CopyIfStreamChanged_NoChanges () + { + using (var src = NewStream ("foo")) { + var dest = NewFile ("foo"); + Assert.IsFalse (Files.CopyIfStreamChanged (src, dest), "No change should have occurred"); + FileAssert.Exists (dest); + } + } + + [Test] + public void CopyIfStreamChanged_LongPath () + { + using (var src = NewStream ("foo")) { + var dest = NewFile (fileName: "bar".PadRight (MaxFileName, 'N')); + dest = Files.ToLongPath (dest); + Assert.IsTrue (Files.CopyIfStreamChanged (src, dest), "Changes should have occurred"); + FileAssert.Exists (dest); + } + } + + [Test] + public void CopyIfStreamChanged_NoExist () + { + using (var src = NewStream ("foo")) { + var dest = NewFile (); + Assert.IsTrue (Files.CopyIfStreamChanged (src, dest), "Changes should have occurred"); + FileAssert.Exists (dest); + } + } + + [Test] + public void CopyIfStreamChanged_Changes () + { + using (var src = NewStream ("foo")) { + var dest = NewFile ("bar"); + Assert.IsTrue (Files.CopyIfStreamChanged (src, dest), "Changes should have occurred"); + FileAssert.Exists (dest); + } + } + + [Test] + public void CopyIfStreamChanged_Readonly () + { + using (var src = NewStream ("foo")) { + var dest = NewFile ("bar"); + File.SetAttributes (dest, FileAttributes.ReadOnly); + Assert.IsTrue (Files.CopyIfStreamChanged (src, dest), "Changes should have occurred"); + FileAssert.Exists (dest); + } + } + + [Test] + public void CopyIfStreamChanged_CasingChange () + { + using (var src = NewStream ("Foo")) { + var dest = NewFile (contents: "foo", fileName: "foo"); + dest = dest.Replace ("foo", "Foo"); + Assert.IsTrue (Files.CopyIfStreamChanged (src, dest), "Changes should have occurred"); + FileAssert.Exists (dest); + Assert.AreEqual ("Foo", File.ReadAllText (dest), "File contents should match"); + + var files = Directory.GetFiles (Path.GetDirectoryName (dest), "Foo"); + Assert.AreEqual ("Foo", Path.GetFileName (files [0]), "File name should match"); + } + } + + [Test] + public void ExtractAll () + { + using (var zip = ZipArchive.Create (stream)) { + zip.AddEntry ("a.txt", "a", encoding); + zip.AddEntry ("b/b.txt", "b", encoding); + } + + bool changes = ExtractAll (stream); + + Assert.IsTrue (changes, "ExtractAll should report changes."); + AssertFile ("a.txt", "a"); + AssertFile (Path.Combine ("b", "b.txt"), "b"); + } + + [Test] + public void ExtractAll_NoChanges () + { + using (var zip = ZipArchive.Create (stream)) { + zip.AddEntry ("a.txt", "a", encoding); + zip.AddEntry ("b/b.txt", "b", encoding); + } + + bool changes = ExtractAll (stream); + Assert.IsTrue (changes, "ExtractAll should report changes."); + + stream.SetLength (0); + using (var zip = ZipArchive.Open (stream)) { + zip.AddEntry ("a.txt", "a", encoding); + zip.AddEntry ("b/b.txt", "b", encoding); + } + + changes = ExtractAll (stream); + + Assert.IsFalse (changes, "ExtractAll should *not* report changes."); + AssertFile ("a.txt", "a"); + AssertFile (Path.Combine ("b", "b.txt"), "b"); + } + + [Test] + public void ExtractAll_NewFile () + { + using (var zip = ZipArchive.Create (stream)) { + zip.AddEntry ("a.txt", "a", encoding); + zip.AddEntry ("b/b.txt", "b", encoding); + } + + bool changes = ExtractAll (stream); + Assert.IsTrue (changes, "ExtractAll should report changes."); + + stream.SetLength (0); + using (var zip = ZipArchive.Open (stream)) { + zip.AddEntry ("a.txt", "a", encoding); + zip.AddEntry ("b/b.txt", "b", encoding); + zip.AddEntry ("c/c.txt", "c", encoding); + } + + changes = ExtractAll (stream); + + Assert.IsTrue (changes, "ExtractAll should report changes."); + AssertFile ("a.txt", "a"); + AssertFile (Path.Combine ("b", "b.txt"), "b"); + AssertFile (Path.Combine ("c", "c.txt"), "c"); + } + + [Test] + public void ExtractAll_FileChanged () + { + using (var zip = ZipArchive.Create (stream)) { + zip.AddEntry ("foo.txt", "foo", encoding); + } + + bool changes = ExtractAll (stream); + Assert.IsTrue (changes, "ExtractAll should report changes."); + + stream.SetLength (0); + using (var zip = ZipArchive.Create (stream)) { + zip.AddEntry ("foo.txt", "bar", encoding); + } + + changes = ExtractAll (stream); + + Assert.IsTrue (changes, "ExtractAll should report changes."); + AssertFile ("foo.txt", "bar"); + } + + [Test] + public void ExtractAll_FileDeleted () + { + using (var zip = ZipArchive.Create (stream)) { + zip.AddEntry ("a.txt", "a", encoding); + zip.AddEntry ("b/b.txt", "b", encoding); + } + + bool changes = ExtractAll (stream); + Assert.IsTrue (changes, "ExtractAll should report changes."); + + stream.SetLength (0); + using (var zip = ZipArchive.Open (stream)) { + zip.AddEntry ("a.txt", "a", encoding); + } + + changes = ExtractAll (stream); + + Assert.IsTrue (changes, "ExtractAll should report changes."); + AssertFile ("a.txt", "a"); + FileAssert.DoesNotExist (Path.Combine (tempDir, "b", "b.txt")); + } + + [Test] + public void ExtractAll_ModifyCallback () + { + using (var zip = ZipArchive.Create (stream)) { + zip.AddEntry ("foo/a.txt", "a", encoding); + zip.AddEntry ("foo/b/b.txt", "b", encoding); + } + + stream.Position = 0; + using (var zip = ZipArchive.Open (stream)) { + bool changes = Files.ExtractAll (zip, tempDir, modifyCallback: e => e.Replace ("foo/", "")); + Assert.IsTrue (changes, "ExtractAll should report changes."); + } + + AssertFile ("a.txt", "a"); + AssertFile (Path.Combine ("b", "b.txt"), "b"); + } + + [Test] + public void ExtractAll_SkipCallback () + { + using (var zip = ZipArchive.Create (stream)) { + zip.AddEntry ("a.txt", "a", encoding); + zip.AddEntry ("b/b.txt", "b", encoding); + } + + stream.Position = 0; + using (var zip = ZipArchive.Open (stream)) { + bool changes = Files.ExtractAll (zip, tempDir, skipCallback: e => e == "a.txt"); + Assert.IsTrue (changes, "ExtractAll should report changes."); + } + + AssertFileDoesNotExist ("a.txt"); + AssertFile (Path.Combine ("b", "b.txt"), "b"); + } + + [Test] + public void ExtractAll_MacOSFiles () + { + using (var zip = ZipArchive.Create (stream)) { + zip.AddEntry ("a/.DS_Store", "a", encoding); + zip.AddEntry ("b/__MACOSX/b.txt", "b", encoding); + zip.AddEntry ("c/__MACOSX", "c", encoding); + } + + bool changes = ExtractAll (stream); + Assert.IsFalse (changes, "ExtractAll should *not* report changes."); + DirectoryAssert.DoesNotExist (tempDir); + } + + [Test] + public void ToHashString () + { + var bytes = new byte [] { 0x12, 0x34, 0x56, 0x78, 0x90, 0xAB, 0xCD, 0xEF }; + var expected = BitConverter.ToString (bytes).Replace ("-", string.Empty); + Assert.AreEqual (expected, Files.ToHexString (bytes)); + } + } +} diff --git a/external/xamarin-android-tools/tests/Microsoft.Android.Build.BaseTasks-Tests/MemoryStreamPoolTests.cs b/external/xamarin-android-tools/tests/Microsoft.Android.Build.BaseTasks-Tests/MemoryStreamPoolTests.cs new file mode 100644 index 00000000000..17d7b8c6e6c --- /dev/null +++ b/external/xamarin-android-tools/tests/Microsoft.Android.Build.BaseTasks-Tests/MemoryStreamPoolTests.cs @@ -0,0 +1,70 @@ +// https://github.com/xamarin/xamarin-android/blob/799506a9dfb746b8bdc8a4ab77e19eee875f00e3/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/MemoryStreamPoolTests.cs + +using System; +using System.IO; +using NUnit.Framework; +using Microsoft.Android.Build.Tasks; + +namespace Microsoft.Android.Build.BaseTasks.Tests +{ + [TestFixture] + public class MemoryStreamPoolTests + { + MemoryStreamPool pool; + + [SetUp] + public void SetUp () + { + pool = new MemoryStreamPool (); + } + + [Test] + public void Reuse () + { + var expected = pool.Rent (); + expected.Write (new byte [] { 1, 2, 3 }, 0, 3); + pool.Return (expected); + var actual = pool.Rent (); + Assert.AreSame (expected, actual); + Assert.AreEqual (0, actual.Length); + } + + [Test] + public void PutDisposed () + { + var stream = new MemoryStream (); + stream.Dispose (); + Assert.Throws (() => pool.Return (stream)); + } + + [Test] + public void CreateStreamWriter () + { + var pool = new MemoryStreamPool (); + var expected = pool.Rent (); + using (var writer = MemoryStreamPool.Shared.CreateStreamWriter ()) { + writer.WriteLine ("foobar"); + } + pool.Return (expected); + + var actual = pool.Rent (); + Assert.AreSame (expected, actual); + Assert.AreEqual (0, actual.Length); + } + + [Test] + public void CreateBinaryWriter () + { + var pool = new MemoryStreamPool (); + var expected = pool.Rent (); + using (var writer = MemoryStreamPool.Shared.CreateBinaryWriter ()) { + writer.Write (42); + } + pool.Return (expected); + + var actual = pool.Rent (); + Assert.AreSame (expected, actual); + Assert.AreEqual (0, actual.Length); + } + } +} diff --git a/external/xamarin-android-tools/tests/Microsoft.Android.Build.BaseTasks-Tests/Microsoft.Android.Build.BaseTasks-Tests.csproj b/external/xamarin-android-tools/tests/Microsoft.Android.Build.BaseTasks-Tests/Microsoft.Android.Build.BaseTasks-Tests.csproj new file mode 100644 index 00000000000..8e0a2a442c9 --- /dev/null +++ b/external/xamarin-android-tools/tests/Microsoft.Android.Build.BaseTasks-Tests/Microsoft.Android.Build.BaseTasks-Tests.csproj @@ -0,0 +1,26 @@ + + + + + netcoreapp3.1 + Microsoft.Android.Build.BaseTasks.Tests + false + $(TestOutputFullPath) + false + + + + + + + + + + + + + + + From 0aa9447d61ee9a4611800e43a235ba5f6ba7409f Mon Sep 17 00:00:00 2001 From: Dean Ellis Date: Mon, 8 Feb 2021 17:31:12 +0000 Subject: [PATCH 094/308] Bump ta xamarin/LibZipSharp/1.0.22@9f563dd1 (#104) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Changes: https://github.com/xamarin/libzipsharp/compare/1.0.20...1.0.22 * xamarin/libzipsharp@9f563dd: Add a test which changes the CompressionMethod of an item (#78) * xamarin/libzipsharp@3b610c9: Try actually running the tests under .net core (#77) * xamarin/libzipsharp@03cd66b: Archive the SignList.xml * xamarin/libzipsharp@8afe791: Add the signListPath parameter * xamarin/libzipsharp@ed5585c: Add SignList.xml for signing * xamarin/libzipsharp@2ab6943: Add a build_windows.bat file to make it easier to build on windows (#75) * … * xamarin/libzipsharp@9dca4fb: Initial commit of localisation * xamarin/libzipsharp@668babc: Add Unit Test for setting file permissions on extraction. * xamarin/libzipsharp@8f2053c: Build on Windows again (#71) --- .../MSBuildReferences.projitems | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems index 4fb5136f3a3..8c86aef3e52 100644 --- a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems +++ b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems @@ -6,7 +6,7 @@ 15.1.0.0 16.5 - 1.0.20 + 1.0.22 From 9259036a161a7cee100e8474d71227f22361b393 Mon Sep 17 00:00:00 2001 From: Jonathan Pryor Date: Tue, 9 Feb 2021 14:36:09 -0500 Subject: [PATCH 095/308] [ci] Update packageSources in NuGet.config (#105) Context: https://medium.com/@alex.birsan/dependency-confusion-4a5d60fec610 Context: https://azure.microsoft.com/en-us/resources/3-ways-to-mitigate-risk-using-private-package-feeds/ Context: https://devdiv.visualstudio.com/DevDiv/_wiki/wikis/DevDiv.wiki/12676/ncident-help-for-Substitution-attack-risk-from-multiple-package-feeds There is a Package Substitution Attack inherent in NuGet, whereby if multiple package sources provide packages with the same name, it is *indeterminate* which package source will provide the package. For example, consider the [`XliffTasks` package][0], currently provided from the [`dotnet-eng`][1] feed, and *not* present in the NuGet.org feed. If a "hostile attacker" submits an `XliffTasks` package to NuGet.org, then we don't know, and cannot control, whether the build will use the "hostile" `XliffTasks` package from NuGet.org or the "desired" package from `dotnet-eng`. There are two ways to prevent this attack: 1. Use `//packageSources/clear` and have *only one* `//packageSources/add` entry in `NuGet.config` 2. Use `//packageSources/clear` and *fully trust* every `//packageSources/add` entry in `NuGet.config`. `NuGet.org` *cannot* be a trusted source, nor can any feed location which allows "anyone" to add new packages, nor can a feed which itself contains [upstream sources][2]. As the `XliffTasks` package is *not* in `NuGet.org`, option (1) isn't an option. Go with option (2), using the existing `dotnet-eng` source and the new *trusted* [`dotnet-public`][3] package source. [0]: https://github.com/dotnet/xliff-tasks [1]: https://dev.azure.com/dnceng/public/_packaging?_a=feed&feed=dotnet-eng [2]: https://docs.microsoft.com/en-us/azure/devops/artifacts/concepts/upstream-sources?view=azure-devops [3]: https://dev.azure.com/dnceng/public/_packaging?_a=feed&feed=dotnet-public --- external/xamarin-android-tools/NuGet.config | 7 +------ external/xamarin-android-tools/azure-pipelines.yaml | 6 ------ 2 files changed, 1 insertion(+), 12 deletions(-) diff --git a/external/xamarin-android-tools/NuGet.config b/external/xamarin-android-tools/NuGet.config index 4600a23b314..de2f5d6506e 100644 --- a/external/xamarin-android-tools/NuGet.config +++ b/external/xamarin-android-tools/NuGet.config @@ -3,12 +3,7 @@ + - - - - - - diff --git a/external/xamarin-android-tools/azure-pipelines.yaml b/external/xamarin-android-tools/azure-pipelines.yaml index ae5c086147c..9259859c20d 100644 --- a/external/xamarin-android-tools/azure-pipelines.yaml +++ b/external/xamarin-android-tools/azure-pipelines.yaml @@ -45,12 +45,6 @@ jobs: inputs: versionSpec: 5.x - - script: | - dotnet tool install --global boots - boots https://download.mono-project.com/archive/6.12.0/macos-10-universal/MonoFramework-MDK-6.12.0.107.macos10.xamarin.universal.pkg - displayName: Install Mono 6.12 - condition: and(succeeded(), eq(variables['agent.os'], 'Darwin')) - - task: NuGetCommand@2 displayName: 'NuGet Restore' inputs: From fd602f3c1edf3f2799bbe6610c0dbe13bef021ca Mon Sep 17 00:00:00 2001 From: Peter Collins Date: Tue, 9 Feb 2021 16:12:59 -0500 Subject: [PATCH 096/308] [build] Move global.json file to root directory (#106) Context: https://github.com/xamarin/java.interop/pull/796 Context: https://devdiv.visualstudio.com/DevDiv/_build/results?buildId=4454535&view=logs&jobId=f31c9f97-4411-58e7-49ac-fc73f645e6b6&j=f31c9f97-4411-58e7-49ac-fc73f645e6b6&t=35e41a6f-4381-52eb-e0f2-d95c19469a7f We're seeing a Java.Interop build attempt fail on Windows + .NET Framework Windows during a NuGet restore: ##[error]The nuget command failed with exit code(1) and error(D:\a\1\s\external\xamarin-android-tools\src\Microsoft.Android.Build.BaseTasks\Microsoft.Android.Build.BaseTasks.csproj(4,3): error : C:\hostedtoolcache\windows\dotnet\sdk\3.1.300\Sdks\Microsoft.DotNet.Arcade.Sdk\Sdk not found. Check that a recent enough .NET SDK is installed and/or increase the version specified in global.json. The "out of repo" restore attempt is failing to find the `global.json` file next to `Microsoft.Android.Build.BaseTasks.csproj`. Fix this by moving `src/Microsoft.Android.Build.BaseTasks/global.json` to `global.json`, next to `Xamarin.Android.Tools.sln`. --- .../{src/Microsoft.Android.Build.BaseTasks => }/global.json | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename external/xamarin-android-tools/{src/Microsoft.Android.Build.BaseTasks => }/global.json (100%) diff --git a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/global.json b/external/xamarin-android-tools/global.json similarity index 100% rename from external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/global.json rename to external/xamarin-android-tools/global.json From b89927f0d27887566ded42e8851ea4260e96e195 Mon Sep 17 00:00:00 2001 From: Dean Ellis Date: Thu, 25 Feb 2021 13:22:01 +0000 Subject: [PATCH 097/308] Bump to xamarin/LibZipSharp/main@521b54ec [1.0.23] (#109) Changes: https://github.com/xamarin/LibZipSharp/compare/1.0.22...1.0.23 * xamarin/LibZipSharp@521b54e Include Windows pdb in Nuget (#82) * xamarin/LibZipSharp@9853933: [ci] Update packageSources in NuGet.config (#80) --- .../MSBuildReferences.projitems | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems index 8c86aef3e52..ef06ee1610c 100644 --- a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems +++ b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems @@ -6,7 +6,7 @@ 15.1.0.0 16.5 - 1.0.22 + 1.0.23 From 672c5da6d3137b16ffd4b1d6b4eada0781a17571 Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Mon, 1 Mar 2021 15:16:34 -0600 Subject: [PATCH 098/308] [macOS] fix DirectoryNotFoundException on clean systems (#110) @PureWeen was hitting a problem building a .NET 6 app on macOS: Task "ResolveSdks" ... at System.IO.Enumeration.FileSystemEnumerator`1.CreateDirectoryHandle(String path, Boolean ignoreNotFound) at System.IO.Enumeration.FileSystemEnumerator`1.Init() at System.IO.Enumeration.FileSystemEnumerator`1..ctor(String directory, Boolean isNormalized, EnumerationOptions options) at System.IO.Enumeration.FileSystemEnumerable`1..ctor(String directory, FindTransform transform, EnumerationOptions options, Boolean isNormalized) at System.IO.Enumeration.FileSystemEnumerableFactory.UserDirectories(String directory, String expression, EnumerationOptions options) at System.IO.Directory.InternalEnumeratePaths(String path, String searchPattern, SearchTarget searchTarget, EnumerationOptions options) at Xamarin.Android.Tools.AndroidSdkBase.FindBestNDK(String androidSdkPath) at Xamarin.Android.Tools.AndroidSdkBase.GetAllAvailableAndroidNdks()+MoveNext() at Xamarin.Android.Tools.AndroidSdkBase.GetValidNdkPath(String ctorParam) at Xamarin.Android.Tools.AndroidSdkBase.Initialize(String androidSdkPath, String androidNdkPath, String javaSdkPath) at Xamarin.Android.Tools.AndroidSdkInfo..ctor(Action`2 logger, String androidSdkPath, String androidNdkPath, String javaSdkPath) at Xamarin.Android.Tasks.MonoAndroidHelper.RefreshAndroidSdk(String sdkPath, String ndkPath, String javaPath, TaskLoggingHelper logHelper) at Xamarin.Android.Tasks.ResolveSdks.RunTask() at Microsoft.Android.Build.Tasks.AndroidTask.Execute() This was a relatively new Mac which didn't have an Android NDK installed. A way to workaround the problem was to create a directory that was being checked for: mkdir -p ~/Library/Android/sdk It appears this problem was introduced in c22503df, as: 1. `~/Library/Android/sdk` is returned from [`GetAllAvailableAndroidNdks()`][0] 2. `GetAllAvailableAndroidNdks()` calls `GetAllAvailableAndroidSdks()` looking for any `ndk-bundle` folders it can find. 3. `FindBestNDK()` is passed a directory that doesn't exist, and the `Directory.EnumerateDirectories()` call [throw][1]. The fix here is to update `FindBestNDK()` to check `Directory.Exists()` before trying to traverse the directory `androidSdkPath`, as the values from `GetSdkFromEnvironmentVariables()` may not exist either. I was able to add a test for this scenario by adding an invalid directory to `$ANDROID_HOME`. [0]: https://github.com/xamarin/xamarin-android-tools/blob/c22503df7bf604eff1bbb910f65d6df336f51bfd/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkUnix.cs#L113-L115 [1]: https://github.com/xamarin/xamarin-android-tools/blob/c22503df7bf604eff1bbb910f65d6df336f51bfd/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkBase.cs#L172 --- .../Sdks/AndroidSdkBase.cs | 11 ++++++++-- .../AndroidSdkInfoTests.cs | 22 +++++++++++++++++++ 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkBase.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkBase.cs index 59fc390b69b..b39b75f29ed 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkBase.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkBase.cs @@ -152,7 +152,7 @@ protected virtual IEnumerable GetAllAvailableAndroidNdks () // Look in PATH foreach (var ndkStack in ProcessUtils.FindExecutablesInPath (NdkStack)) { var ndkDir = Path.GetDirectoryName (ndkStack); - if (ndkDir == null) + if (string.IsNullOrEmpty (ndkDir)) continue; yield return ndkDir; } @@ -161,12 +161,19 @@ protected virtual IEnumerable GetAllAvailableAndroidNdks () foreach (var sdk in GetAllAvailableAndroidSdks ()) { if (sdk == AndroidSdkPath) continue; - yield return FindBestNDK (sdk); + var ndkDir = FindBestNDK (sdk); + if (string.IsNullOrEmpty (ndkDir)) + continue; + yield return ndkDir; } } string FindBestNDK (string androidSdkPath) { + if (!Directory.Exists (androidSdkPath)) { + return String.Empty; + } + var ndkInstances = new SortedDictionary (Comparer.Create ((Version l, Version r) => r.CompareTo (l))); foreach (string ndkPath in Directory.EnumerateDirectories (androidSdkPath, "ndk*", SearchOption.TopDirectoryOnly)) { diff --git a/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/AndroidSdkInfoTests.cs b/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/AndroidSdkInfoTests.cs index 7efcaa2a890..54dc3531bb1 100644 --- a/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/AndroidSdkInfoTests.cs +++ b/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/AndroidSdkInfoTests.cs @@ -146,6 +146,28 @@ public void Ndk_PathInSdk() } } + [Test] + public void Ndk_AndroidSdkDoesNotExist () + { + CreateSdks (out string root, out string jdk, out string ndk, out string sdk); + + Action logger = (level, message) => { + Console.WriteLine ($"[{level}] {message}"); + if (level == TraceLevel.Error) + Assert.Fail (message); + }; + + var oldAndroidHome = Environment.GetEnvironmentVariable ("ANDROID_HOME"); + try { + Environment.SetEnvironmentVariable ("ANDROID_HOME", "/i/dont/exist"); + // Check that this doesn't throw + new AndroidSdkInfo (logger, androidSdkPath: sdk, androidNdkPath: null, javaSdkPath: jdk); + } finally { + Environment.SetEnvironmentVariable ("ANDROID_HOME", oldAndroidHome); + Directory.Delete (root, recursive: true); + } + } + [Test] public void Constructor_SetValuesFromPath () { From 6b4f68e1d26103d47654e4acdb558d72d14d76ef Mon Sep 17 00:00:00 2001 From: Dean Ellis Date: Tue, 2 Mar 2021 14:17:14 +0000 Subject: [PATCH 099/308] Bump to xamarin/LibZipSharp/main@86f8ae57 [1.0.24] (#111) Changes: https://github.com/xamarin/LibZipSharp/compare/1.0.23...1.0.24 * xamarin/LibZipSharp@86f8ae57: Bump to 1.0.24 * xamarin/LibZipSharp@582eb2c1: Include the Pdb when using 'LibZipSharpBundleAllNativeLibraries' --- .../MSBuildReferences.projitems | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems index ef06ee1610c..0bd77b355fe 100644 --- a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems +++ b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems @@ -6,7 +6,7 @@ 15.1.0.0 16.5 - 1.0.23 + 1.0.24 From d2e7bf7bcdbdeaa69a353102b438b2e05d91cb57 Mon Sep 17 00:00:00 2001 From: Jonathan Pobst Date: Tue, 2 Mar 2021 09:58:36 -0600 Subject: [PATCH 100/308] [Xamarin.Android.Tools.AndroidSdk] Fix CS8600 in AndroidSdkBase (#107) In commit c22503df, this line was added: string ev = Environment.GetEnvironmentVariable (envVar); With C#8 Nullable Reference Types, `Environment.GetEnvironmentVariable()` returns `string?`, not `string`. This causes the warning: Warning CS8600: Converting null literal or possible null value to non-nullable type. We can avoid the CS8600 by using `var` instead of `string`. --- .../src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkBase.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkBase.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkBase.cs index b39b75f29ed..cd6c984d393 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkBase.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkBase.cs @@ -138,7 +138,7 @@ public virtual void Initialize (string? androidSdkPath = null, string? androidNd protected IEnumerable GetSdkFromEnvironmentVariables () { foreach (string envVar in AndroidSdkEnvVars) { - string ev = Environment.GetEnvironmentVariable (envVar); + var ev = Environment.GetEnvironmentVariable (envVar); if (String.IsNullOrEmpty (ev)) { continue; } From e55409597262ff1ee09f6edc25603a236fe2e889 Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Wed, 17 Mar 2021 13:43:09 -0500 Subject: [PATCH 101/308] [Xamarin.Android.Tools.AndroidSdk] Fix quotes in %PATH% or %PATHEXT% (#112) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes: https://developercommunity.visualstudio.com/t/illegal-character-exception-in-xamarinandroid-afte/1363149 In 16.9 we are getting some reports of: (_ResolveSdks target) -> error XARSD7004: System.ArgumentException: Illegal characters in path. error XARSD7004: at System.IO.Path.CheckInvalidPathChars(String path, Boolean checkAdditional) error XARSD7004: at System.IO.Path.Combine(String path1, String path2) error XARSD7004: at Xamarin.Android.Tools.ProcessUtils.d__9.MoveNext() error XARSD7004: at Xamarin.Android.Tools.ProcessUtils.d__8.MoveNext() error XARSD7004: at Xamarin.Android.Tools.AndroidSdkBase.d__73.MoveNext() error XARSD7004: at Xamarin.Android.Tools.AndroidSdkWindows.d__43.MoveNext() error XARSD7004: at Xamarin.Android.Tools.AndroidSdkBase.GetValidNdkPath(String ctorParam) error XARSD7004: at Xamarin.Android.Tools.AndroidSdkBase.Initialize(String androidSdkPath, String androidNdkPath, String javaSdkPath) error XARSD7004: at Xamarin.Android.Tools.AndroidSdkWindows.Initialize(String androidSdkPath, String androidNdkPath, String javaSdkPath) error XARSD7004: at Xamarin.Android.Tools.AndroidSdkInfo..ctor(Action`2 logger, String androidSdkPath, String androidNdkPath, String javaSdkPath) error XARSD7004: at Xamarin.Android.Tasks.MonoAndroidHelper.RefreshAndroidSdk(String sdkPath, String ndkPath, String javaPath, TaskLoggingHelper logHelper) error XARSD7004: at Xamarin.Android.Tasks.ResolveSdks.RunTask() error XARSD7004: at Xamarin.Android.Tasks.AndroidTask.Execute() It appears this is simply a call like this failing: Path.Combine ("foo", "\"") Path.Combine ("\"", "foo") The most likely reason for certain customers to get this exception is because an environment variable contains quotes, e.g. rem Windows CMD.EXE set PATH='…' On Windows+CMD.EXE, the `'` (or `"`) is *not* needed, even if the value has spaces: rem Can cause Pain and Suffering™ set PATH="C:\This Directory Has Spaces;%PATH%" rem Good and Proper™ set PATH=C:\This Directory Has Spaces;%PATH% I could reproduce this in a test by setting `%PATH%` or `%PATHEXT%` to invalid names. For `%PATH%`, I could simply add a `Directory.Exists()` check in the place that makes sense. However, I think a `try`/`catch` of `ArgumentException` is the only way to handle `%PATHEXT%`? I had to put this in two places where the new test found an issue. --- .../ProcessUtils.cs | 9 +++- .../Sdks/AndroidSdkBase.cs | 11 +++-- .../AndroidSdkInfoTests.cs | 44 +++++++++++++++++++ 3 files changed, 60 insertions(+), 4 deletions(-) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/ProcessUtils.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/ProcessUtils.cs index d002730a739..00074a72412 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/ProcessUtils.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/ProcessUtils.cs @@ -173,8 +173,15 @@ internal static IEnumerable FindExecutablesInPath (string executable) internal static IEnumerable FindExecutablesInDirectory (string dir, string executable) { + if (!Directory.Exists (dir)) + yield break; foreach (var exe in ExecutableFiles (executable)) { - var exePath = Path.Combine (dir, exe); + string exePath; + try { + exePath = Path.Combine (dir, exe); + } catch (ArgumentException) { + continue; + } if (File.Exists (exePath)) yield return exePath; } diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkBase.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkBase.cs index cd6c984d393..6aa64b848e9 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkBase.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkBase.cs @@ -292,9 +292,14 @@ static string GetExecutablePath (string? dir, string exe) if (string.IsNullOrEmpty (dir)) return exe; - foreach (var e in ProcessUtils.ExecutableFiles (exe)) - if (File.Exists (Path.Combine (dir, e))) - return e; + foreach (var e in ProcessUtils.ExecutableFiles (exe)) { + try { + if (File.Exists (Path.Combine (dir, e))) + return e; + } catch (ArgumentException) { + continue; + } + } return exe; } } diff --git a/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/AndroidSdkInfoTests.cs b/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/AndroidSdkInfoTests.cs index 54dc3531bb1..d1950926c08 100644 --- a/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/AndroidSdkInfoTests.cs +++ b/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/AndroidSdkInfoTests.cs @@ -146,6 +146,50 @@ public void Ndk_PathInSdk() } } + [Test] + public void Ndk_Path_InvalidChars () + { + CreateSdks (out string root, out string jdk, out string ndk, out string sdk); + + Action logger = (level, message) => { + Console.WriteLine ($"[{level}] {message}"); + if (level == TraceLevel.Error) + Assert.Fail (message); + }; + + var oldPath = Environment.GetEnvironmentVariable ("PATH"); + try { + Environment.SetEnvironmentVariable ("PATH", "\"C:\\IHAVEQUOTES\\\""); + // Check that this doesn't throw + new AndroidSdkInfo (logger, androidSdkPath: sdk, androidNdkPath: null, javaSdkPath: jdk); + } finally { + Environment.SetEnvironmentVariable ("PATH", oldPath); + Directory.Delete (root, recursive: true); + } + } + + [Test] + public void Ndk_PathExt_InvalidChars () + { + CreateSdks (out string root, out string jdk, out string ndk, out string sdk); + + Action logger = (level, message) => { + Console.WriteLine ($"[{level}] {message}"); + if (level == TraceLevel.Error) + Assert.Fail (message); + }; + + var oldPathExt = Environment.GetEnvironmentVariable ("PATHEXT"); + try { + Environment.SetEnvironmentVariable ("PATHEXT", string.Join (Path.PathSeparator.ToString (), "\"", ".EXE", ".BAT")); + // Check that this doesn't throw + new AndroidSdkInfo (logger, androidSdkPath: sdk, androidNdkPath: null, javaSdkPath: jdk); + } finally { + Environment.SetEnvironmentVariable ("PATHEXT", oldPathExt); + Directory.Delete (root, recursive: true); + } + } + [Test] public void Ndk_AndroidSdkDoesNotExist () { From 5dcd2875215498a94ee50ffa241d9e0b43fb56e4 Mon Sep 17 00:00:00 2001 From: Jonathan Pryor Date: Wed, 24 Mar 2021 19:45:01 -0400 Subject: [PATCH 102/308] [Xamarin.Android.Tools.AndroidSdk] Probe for Microsoft OpenJDK dirs (#113) Context: https://aka.ms/getopenjdk Context: https://devblogs.microsoft.com/java Remove support for the obsolete (and never updated) `microsoft_dist_openjdk_*` builds of OpenJDK. Despite having "microsoft" in the name, it was maintained by a different team which has moved on to other things. --- .../AndroidSdkInfo.cs | 2 +- .../JdkInfo.cs | 44 +++++++--- .../Xamarin.Android.Tools.AndroidSdk/OS.cs | 84 ++++++++++++++++++ .../Properties/AssemblyInfo.cs | 10 +++ .../Sdks/AndroidSdkWindows.cs | 85 +++++++++++++++---- .../AndroidSdkWindowsTests.cs | 68 +++++++++++++++ ...arin.Android.Tools.AndroidSdk-Tests.csproj | 2 + 7 files changed, 265 insertions(+), 30 deletions(-) create mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/AssemblyInfo.cs create mode 100644 external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/AndroidSdkWindowsTests.cs diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidSdkInfo.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidSdkInfo.cs index 1297e515f05..7f6cd0c92de 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidSdkInfo.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidSdkInfo.cs @@ -185,7 +185,7 @@ public static void DetectAndSetPreferredJavaSdkPathToLatest (Action GetKnownSystemJdkInfos (Action GetConfiguredJdkPaths (Action log } } - internal static IEnumerable GetMacOSMicrosoftJdks (Action logger) + internal static IEnumerable GetMicrosoftOpenJdks (Action logger) { - return GetMacOSMicrosoftJdkPaths () - .Select (p => TryGetJdkInfo (p, logger, "$HOME/Library/Developer/Xamarin/jdk")) + foreach (var dir in GetMacOSMicrosoftOpenJdks (logger)) + yield return dir; + if (Path.DirectorySeparatorChar != '\\') + yield break; + foreach (var dir in AndroidSdkWindows.GetJdkInfos (logger)) { + yield return dir; + } + } + + static IEnumerable GetMacOSMicrosoftOpenJdks (Action logger) + { + return GetMacOSMicrosoftOpenJdkPaths () + .Select (p => TryGetJdkInfo (p, logger, "/Library/Java/JavaVirtualMachines/microsoft-*.jdk")) .Where (jdk => jdk != null) .Select (jdk => jdk!) .OrderByDescending (jdk => jdk, JdkInfoVersionComparer.Default); } - static IEnumerable GetMacOSMicrosoftJdkPaths () + static IEnumerable GetMacOSMicrosoftOpenJdkPaths () { + var root = "/Library/Java/JavaVirtualMachines"; + var pattern = "microsoft-*.jdk"; + var toHome = Path.Combine ("Contents", "Home"); var jdks = AppDomain.CurrentDomain.GetData ($"GetMacOSMicrosoftJdkPaths jdks override! {typeof (JdkInfo).AssemblyQualifiedName}") ?.ToString (); - if (jdks == null) { - var home = Environment.GetFolderPath (Environment.SpecialFolder.Personal); - jdks = Path.Combine (home, "Library", "Developer", "Xamarin", "jdk"); + if (jdks != null) { + root = jdks; + toHome = ""; + pattern = "*"; + } + if (!Directory.Exists (root)) { + yield break; + } + foreach (var dir in Directory.EnumerateDirectories (root, pattern)) { + var home = Path.Combine (dir, toHome); + if (!Directory.Exists (home)) + continue; + yield return home; } - if (!Directory.Exists (jdks)) - return Enumerable.Empty (); - - return Directory.EnumerateDirectories (jdks); } static JdkInfo? TryGetJdkInfo (string path, Action logger, string locator) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/OS.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/OS.cs index ffb395f1286..338b0fda193 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/OS.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/OS.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Runtime.InteropServices; using System.IO; using System.Text; @@ -139,9 +140,89 @@ static extern int RegSetValueExW (UIntPtr hKey, string lpValueName, int lpReserv static extern int RegCreateKeyEx (UIntPtr hKey, string subKey, uint reserved, string? @class, uint options, uint samDesired, IntPtr lpSecurityAttributes, out UIntPtr phkResult, out Disposition lpdwDisposition); + // https://docs.microsoft.com/en-us/windows/win32/api/winreg/nf-winreg-regenumkeyexw + [DllImport (ADVAPI, CharSet = CharSet.Unicode, SetLastError = true)] + static extern int RegEnumKeyExW ( + UIntPtr hKey, + uint dwIndex, + [Out] char[] lpName, + ref uint lpcchName, + IntPtr lpReserved, + IntPtr lpClass, + IntPtr lpcchClass, + IntPtr lpftLastWriteTime + ); + + // https://docs.microsoft.com/en-us/windows/win32/api/winreg/nf-winreg-regqueryinfokeyw + [DllImport (ADVAPI, CharSet = CharSet.Unicode, SetLastError = true)] + static extern int RegQueryInfoKey ( + UIntPtr hKey, + IntPtr lpClass, + IntPtr lpcchClass, + IntPtr lpReserved, + out uint lpcSubkey, + out uint lpcchMaxSubkeyLen, + IntPtr lpcchMaxClassLen, + IntPtr lpcValues, + IntPtr lpcchMaxValueNameLen, + IntPtr lpcbMaxValueLen, + IntPtr lpSecurityDescriptor, + IntPtr lpftLastWriteTime + ); + [DllImport ("advapi32.dll", SetLastError = true)] static extern int RegCloseKey (UIntPtr hKey); + public static IEnumerable EnumerateSubkeys (UIntPtr key, string subkey, Wow64 wow64) + { + UIntPtr regKeyHandle; + uint sam = (uint)Rights.Read + (uint)wow64; + int r = RegOpenKeyEx (key, subkey, 0, sam, out regKeyHandle); + if (r != 0) { + yield break; + } + try { + r = RegQueryInfoKey ( + hKey: regKeyHandle, + lpClass: IntPtr.Zero, + lpcchClass: IntPtr.Zero, + lpReserved: IntPtr.Zero, + lpcSubkey: out uint cSubkeys, + lpcchMaxSubkeyLen: out uint cchMaxSubkeyLen, + lpcchMaxClassLen: IntPtr.Zero, + lpcValues: IntPtr.Zero, + lpcchMaxValueNameLen: IntPtr.Zero, + lpcbMaxValueLen: IntPtr.Zero, + lpSecurityDescriptor: IntPtr.Zero, + lpftLastWriteTime: IntPtr.Zero + ); + if (r != 0) { + yield break; + } + var name = new char [cchMaxSubkeyLen+1]; + for (uint i = 0; i < cSubkeys; ++i) { + var nameLen = (uint) name.Length; + r = RegEnumKeyExW ( + hKey: regKeyHandle, + dwIndex: i, + lpName: name, + lpcchName: ref nameLen, + lpReserved: IntPtr.Zero, + lpClass: IntPtr.Zero, + lpcchClass: IntPtr.Zero, + lpftLastWriteTime: IntPtr.Zero + ); + if (r != 0) { + continue; + } + yield return new string (name, 0, (int) nameLen); + } + } + finally { + RegCloseKey (regKeyHandle); + } + } + public static string? GetValueString (UIntPtr key, string subkey, string valueName, Wow64 wow64) { UIntPtr regKeyHandle; @@ -192,6 +273,9 @@ enum Rights : uint SetValue = 0x0002, CreateSubKey = 0x0004, EnumerateSubKey = 0x0008, + Notify = 0x0010, + Read = _StandardRead | QueryValue | EnumerateSubKey | Notify, + _StandardRead = 0x20000, } enum Options diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/AssemblyInfo.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/AssemblyInfo.cs new file mode 100644 index 00000000000..05a6dfb830e --- /dev/null +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/AssemblyInfo.cs @@ -0,0 +1,10 @@ +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo ( + "Xamarin.Android.Tools.AndroidSdk-Tests, PublicKey=" + + "0024000004800000940000000602000000240000525341310004000011000000438ac2a5acfbf1" + + "6cbd2b2b47a62762f273df9cb2795ceccdf77d10bf508e69e7a362ea7a45455bbf3ac955e1f2e2" + + "814f144e5d817efc4c6502cc012df310783348304e3ae38573c6d658c234025821fda87a0be8a0" + + "d504df564e2c93b2b878925f42503e9d54dfef9f9586d9e6f38a305769587b1de01f6c0410328b" + + "2c9733db" +)] diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkWindows.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkWindows.cs index 739199939c8..81c909cf4e0 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkWindows.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkWindows.cs @@ -18,6 +18,7 @@ class AndroidSdkWindows : AndroidSdkBase const string ANDROID_INSTALLER_KEY = "Path"; const string XAMARIN_ANDROID_INSTALLER_PATH = @"SOFTWARE\Xamarin\MonoAndroid"; const string XAMARIN_ANDROID_INSTALLER_KEY = "PrivateAndroidSdkPath"; + const string MICROSOFT_OPENJDK_PATH = @"SOFTWARE\Microsoft\JDK"; public AndroidSdkWindows (Action logger) : base (logger) @@ -131,8 +132,9 @@ IEnumerable ToJdkInfos (IEnumerable paths, string locator) } return ToJdkInfos (GetPreferredJdkPaths (), "Preferred Registry") - .Concat (ToJdkInfos (GetOpenJdkPaths (), "OpenJDK")) - .Concat (ToJdkInfos (GetKnownOpenJdkPaths (), "Well-known OpenJDK paths")) + .Concat (ToJdkInfos (GetMicrosoftOpenJdkFilesystemPaths (), "Microsoft OpenJDK Filesystem")) + .Concat (ToJdkInfos (GetMicrosoftOpenJdkRegistryPaths (), "Microsoft OpenJDK Registry")) + .Concat (ToJdkInfos (GetVSAndroidJdkPaths (), @"HKLM\SOFTWARE\Microsoft\VisualStudio\Android@JavaHome")) .Concat (ToJdkInfos (GetOracleJdkPaths (), "Oracle JDK")) ; } @@ -150,7 +152,7 @@ private static IEnumerable GetPreferredJdkPaths () } } - private static IEnumerable GetOpenJdkPaths () + private static IEnumerable GetVSAndroidJdkPaths () { var root = RegistryEx.LocalMachine; var wows = new[] { RegistryEx.Wow64.Key32, RegistryEx.Wow64.Key64 }; @@ -163,28 +165,24 @@ private static IEnumerable GetOpenJdkPaths () } } - /// - /// Locate OpenJDK installations by well known path. - /// - /// List of valid OpenJDK paths in version descending order. - private static IEnumerable GetKnownOpenJdkPaths () + static IEnumerable GetMicrosoftOpenJdkFilesystemPaths () { - string JdkFolderNamePattern = "microsoft_dist_openjdk_"; + const string JdkFolderNamePrefix = "jdk-"; var paths = new List> (); var rootPaths = new List { - Path.Combine (Environment.ExpandEnvironmentVariables ("%ProgramW6432%"), "Android", "jdk"), + Path.Combine (Environment.ExpandEnvironmentVariables ("%ProgramW6432%"), "Microsoft"), Path.Combine (Environment.GetFolderPath (Environment.SpecialFolder.ProgramFilesX86), "Android", "jdk"), }; foreach (var rootPath in rootPaths) { - if (Directory.Exists (rootPath)) { - foreach (var directoryName in Directory.EnumerateDirectories (rootPath, $"{JdkFolderNamePattern}*").ToList ()) { - var versionString = directoryName.Replace ($"{rootPath}\\{JdkFolderNamePattern}", string.Empty); - if (Version.TryParse (versionString, out Version? ver)) { - paths.Add (new Tuple(directoryName, ver)); - } - } + if (!Directory.Exists (rootPath)) + continue; + foreach (var directoryName in Directory.EnumerateDirectories (rootPath, $"{JdkFolderNamePrefix}*")) { + var version = ExtractVersion (directoryName, JdkFolderNamePrefix); + if (version == null) + continue; + paths.Add (Tuple.Create (directoryName, version)); } } @@ -193,6 +191,59 @@ private static IEnumerable GetKnownOpenJdkPaths () .Select (openJdk => openJdk.Item1); } + static IEnumerable GetMicrosoftOpenJdkRegistryPaths () + { + var paths = new List<(Version version, string path)> (); + var roots = new[] { RegistryEx.CurrentUser, RegistryEx.LocalMachine }; + var wows = new[] { RegistryEx.Wow64.Key32, RegistryEx.Wow64.Key64 }; + foreach (var root in roots) + foreach (var wow in wows) { + foreach (var subkeyName in RegistryEx.EnumerateSubkeys (root, MICROSOFT_OPENJDK_PATH, wow)) { + if (!Version.TryParse (subkeyName, out var version)) + continue; + var msiKey = $@"{MICROSOFT_OPENJDK_PATH}\{subkeyName}\hotspot\MSI"; + var path = RegistryEx.GetValueString (root, msiKey, "Path", wow); + if (path == null) + continue; + paths.Add ((version, path)); + } + } + + return paths.OrderByDescending (e => e.version) + .Select (e => e.path); + } + + internal static Version? ExtractVersion (string path, string prefix) + { + var name = Path.GetFileName (path); + if (name.Length <= prefix.Length) + return null; + if (!name.StartsWith (prefix, StringComparison.OrdinalIgnoreCase)) + return null; + + var start = prefix.Length; + while (start < name.Length && !char.IsDigit (name, start)) { + ++start; + } + if (start == name.Length) + return null; + + name = name.Substring (start); + int end = 0; + while (end < name.Length && + (char.IsDigit (name [end]) || name [end] == '.')) { + end++; + } + + do { + if (Version.TryParse (name.Substring (0, end), out var v)) + return v; + end = name.LastIndexOf ('.', end-1); + } while (end > 0); + + return null; + } + private static IEnumerable GetOracleJdkPaths () { string subkey = @"SOFTWARE\JavaSoft\Java Development Kit"; diff --git a/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/AndroidSdkWindowsTests.cs b/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/AndroidSdkWindowsTests.cs new file mode 100644 index 00000000000..cde9f65dd4b --- /dev/null +++ b/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/AndroidSdkWindowsTests.cs @@ -0,0 +1,68 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Text; +using System.Xml.Linq; + +using NUnit.Framework; + +namespace Xamarin.Android.Tools.Tests +{ + [TestFixture] + public class AndroidSdkWindowsTests + { + [Test] + public void ExtractVersion () + { + var sep = Path.DirectorySeparatorChar; + + var tests = new[]{ + new { + Path = $"foo{sep}", + Prefix = "", + Expected = (Version) null, + }, + new { + Path = $"foo{sep}bar-1-extra", + Prefix = "bar-", + Expected = (Version) null, + }, + new { + Path = $"foo{sep}abcdef", + Prefix = "a", + Expected = (Version) null, + }, + new { + Path = $"foo{sep}a{sep}b.c.d", + Prefix = "none-of-the-above", + Expected = (Version) null, + }, + new { + Path = $"jdks{sep}jdk-1.2.3-hotspot-extra", + Prefix = "jdk-", + Expected = new Version (1, 2, 3), + }, + new { + Path = $"jdks{sep}jdk-1.2.3-hotspot-extra", + Prefix = "jdk", + Expected = new Version (1, 2, 3), + }, + new { + Path = $"jdks{sep}jdk-1.2.3.4.5.6-extra", + Prefix = "jdk-", + Expected = new Version (1, 2, 3, 4), + }, + }; + + foreach (var test in tests) { + Assert.AreEqual ( + test.Expected, + AndroidSdkWindows.ExtractVersion (test.Path, test.Prefix), + $"Version couldn't be extracted from Path=`{test.Path}` Prefix=`{test.Prefix}`!" + ); + } + } + } +} diff --git a/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/Xamarin.Android.Tools.AndroidSdk-Tests.csproj b/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/Xamarin.Android.Tools.AndroidSdk-Tests.csproj index ca3992e108f..4100134c6e8 100644 --- a/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/Xamarin.Android.Tools.AndroidSdk-Tests.csproj +++ b/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/Xamarin.Android.Tools.AndroidSdk-Tests.csproj @@ -5,6 +5,8 @@ If $(TargetFramework) is declared here instead, it will not be evaluated before Directory.Build.props is loaded and the wrong $(TestOutputFullPath) will be used. --> netcoreapp3.1 + true + ..\..\product.snk false $(TestOutputFullPath) false From 229cedddfe1a43aadd21c830480f105525cd77fe Mon Sep 17 00:00:00 2001 From: Jonathan Pryor Date: Mon, 19 Apr 2021 12:51:27 -0400 Subject: [PATCH 103/308] [Xamarin.Android.Tools.AndroidSdk] Probe for Zulu JDK (#114) Context: https://www.azul.com/downloads/zulu-community/ Commit 5dcd2875 removed support for the ancient `microsoft_dist_openjdk_*` versions, replacing it with the newer [Microsoft OpenJDK][0]. One problem with this change is one of *compatibility*: `microsoft_dist_openjdk_` was a build of JDK 1.8, while Microsoft OpenJDK only provides builds for JDK 11 and later. This is a problem, as the [Visual Studio Android Designer][1] currently requires JDK 1.8, and is not yet compatible with JDK 11. Add support to probe for for [Azul Zulu OpenJDK][2] installation directories, as Zulu provides updated JDK 1.8 installers. Additionally, refactor and centralize the JDK Location logic. Previously, `JdkInfo.GetKnownSystemJdkInfos()` would return all of the Windows installation locations before the macOS/Linux/etc. locations, which made it possible for Windows and non-Windows platforms to return paths in an inconsistent order. Introduce a new `JdkLocations` helper, and add new subclasses for each specific JDK location, so that `JdkInfo.GetKnownSystemJdkInfos()` can be *unified* across macOS/Linux & Windows, increasing consistency. Add a new `tools/ls-jdks` tool, to make it easier to print all probed JDK locations. [0]: https://www.microsoft.com/openjdk [1]: https://docs.microsoft.com/en-us/xamarin/android/user-interface/android-designer/designer-walkthrough?tabs=windows [2]: https://www.azul.com/downloads/zulu-community/?package=jdk --- .../Xamarin.Android.Tools.sln | 13 +- .../AndroidSdkInfo.cs | 2 +- .../JdkInfo.cs | 79 +------- .../Jdks/AzulJdkLocations.cs | 20 ++ .../Jdks/JdkLocations.MacOS.cs | 62 ++++++ .../Jdks/JdkLocations.Windows.cs | 113 +++++++++++ .../Jdks/JdkLocations.cs | 32 +++ .../Jdks/MicrosoftOpenJdkLocations.cs | 20 ++ .../Jdks/OracleJdkLocations.cs | 42 ++++ .../Jdks/VSAndroidJdkLocations.cs | 38 ++++ .../Jdks/XAPrepareJdkLocations.cs | 26 +++ .../Xamarin.Android.Tools.AndroidSdk/OS.cs | 22 ++ .../Sdks/AndroidSdkBase.cs | 2 +- .../Sdks/AndroidSdkWindows.cs | 188 +----------------- .../AndroidSdkWindowsTests.cs | 51 ----- .../tools/ls-jdks/App.cs | 15 ++ .../tools/ls-jdks/ls-jdks.csproj | 15 ++ 17 files changed, 430 insertions(+), 310 deletions(-) create mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Jdks/AzulJdkLocations.cs create mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Jdks/JdkLocations.MacOS.cs create mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Jdks/JdkLocations.Windows.cs create mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Jdks/JdkLocations.cs create mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Jdks/MicrosoftOpenJdkLocations.cs create mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Jdks/OracleJdkLocations.cs create mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Jdks/VSAndroidJdkLocations.cs create mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Jdks/XAPrepareJdkLocations.cs create mode 100644 external/xamarin-android-tools/tools/ls-jdks/App.cs create mode 100644 external/xamarin-android-tools/tools/ls-jdks/ls-jdks.csproj diff --git a/external/xamarin-android-tools/Xamarin.Android.Tools.sln b/external/xamarin-android-tools/Xamarin.Android.Tools.sln index 9dd35bc4534..13ebd9aece8 100644 --- a/external/xamarin-android-tools/Xamarin.Android.Tools.sln +++ b/external/xamarin-android-tools/Xamarin.Android.Tools.sln @@ -1,4 +1,4 @@ -Microsoft Visual Studio Solution File, Format Version 12.00 +Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 16 VisualStudioVersion = 16.0.30709.64 MinimumVisualStudioVersion = 10.0.40219.1 @@ -10,6 +10,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Android.Build.Bas EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Android.Build.BaseTasks-Tests", "tests\Microsoft.Android.Build.BaseTasks-Tests\Microsoft.Android.Build.BaseTasks-Tests.csproj", "{4F5FD01C-B5A6-4F9F-91CE-3E78B3F942AC}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tools", "tools", "{19FE1B44-DB71-4F97-A07E-085888690DAE}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ls-jdks", "tools\ls-jdks\ls-jdks.csproj", "{3D2ADF77-62C7-4E0E-AB29-BDD266B7D56D}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -32,6 +36,10 @@ Global {4F5FD01C-B5A6-4F9F-91CE-3E78B3F942AC}.Debug|Any CPU.Build.0 = Debug|Any CPU {4F5FD01C-B5A6-4F9F-91CE-3E78B3F942AC}.Release|Any CPU.ActiveCfg = Release|Any CPU {4F5FD01C-B5A6-4F9F-91CE-3E78B3F942AC}.Release|Any CPU.Build.0 = Release|Any CPU + {3D2ADF77-62C7-4E0E-AB29-BDD266B7D56D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3D2ADF77-62C7-4E0E-AB29-BDD266B7D56D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3D2ADF77-62C7-4E0E-AB29-BDD266B7D56D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3D2ADF77-62C7-4E0E-AB29-BDD266B7D56D}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -39,4 +47,7 @@ Global GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {BA1CD771-F00B-4DE8-93EE-7690D81F6A5A} EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {3D2ADF77-62C7-4E0E-AB29-BDD266B7D56D} = {19FE1B44-DB71-4F97-A07E-085888690DAE} + EndGlobalSection EndGlobal diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidSdkInfo.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidSdkInfo.cs index 7f6cd0c92de..feb5865c64b 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidSdkInfo.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidSdkInfo.cs @@ -185,7 +185,7 @@ public static void DetectAndSetPreferredJavaSdkPathToLatest (Action GetKnownSystemJdkInfos (Action GetKnownSystemJdkInfos (Action GetConfiguredJdks (Action logger) - { - return GetConfiguredJdkPaths (logger) - .Select (p => TryGetJdkInfo (p, logger, "monodroid-config.xml")) - .Where (jdk => jdk != null) - .Select (jdk => jdk!) - .OrderByDescending (jdk => jdk, JdkInfoVersionComparer.Default); - } - - static IEnumerable GetConfiguredJdkPaths (Action logger) - { - var config = AndroidSdkUnix.GetUnixConfigFile (logger); - foreach (var java_sdk in config.Root.Elements ("java-sdk")) { - var path = (string) java_sdk.Attribute ("path"); - yield return path; - } - } - - internal static IEnumerable GetMicrosoftOpenJdks (Action logger) - { - foreach (var dir in GetMacOSMicrosoftOpenJdks (logger)) - yield return dir; - if (Path.DirectorySeparatorChar != '\\') - yield break; - foreach (var dir in AndroidSdkWindows.GetJdkInfos (logger)) { - yield return dir; - } - } - - static IEnumerable GetMacOSMicrosoftOpenJdks (Action logger) - { - return GetMacOSMicrosoftOpenJdkPaths () - .Select (p => TryGetJdkInfo (p, logger, "/Library/Java/JavaVirtualMachines/microsoft-*.jdk")) - .Where (jdk => jdk != null) - .Select (jdk => jdk!) - .OrderByDescending (jdk => jdk, JdkInfoVersionComparer.Default); - } - - static IEnumerable GetMacOSMicrosoftOpenJdkPaths () - { - var root = "/Library/Java/JavaVirtualMachines"; - var pattern = "microsoft-*.jdk"; - var toHome = Path.Combine ("Contents", "Home"); - var jdks = AppDomain.CurrentDomain.GetData ($"GetMacOSMicrosoftJdkPaths jdks override! {typeof (JdkInfo).AssemblyQualifiedName}") - ?.ToString (); - if (jdks != null) { - root = jdks; - toHome = ""; - pattern = "*"; - } - if (!Directory.Exists (root)) { - yield break; - } - foreach (var dir in Directory.EnumerateDirectories (root, pattern)) { - var home = Path.Combine (dir, toHome); - if (!Directory.Exists (home)) - continue; - yield return home; - } - } - - static JdkInfo? TryGetJdkInfo (string path, Action logger, string locator) + internal static JdkInfo? TryGetJdkInfo (string path, Action logger, string locator) { JdkInfo? jdk = null; try { @@ -369,13 +311,6 @@ static IEnumerable GetMacOSMicrosoftOpenJdkPaths () return jdk; } - static IEnumerable GetWindowsJdks (Action logger) - { - if (!OS.IsWindows) - return Enumerable.Empty (); - return AndroidSdkWindows.GetJdkInfos (logger); - } - static IEnumerable GetEnvironmentVariableJdks (string envVar, Action logger) { var java_home = Environment.GetEnvironmentVariable (envVar); diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Jdks/AzulJdkLocations.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Jdks/AzulJdkLocations.cs new file mode 100644 index 00000000000..13d10c1ffab --- /dev/null +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Jdks/AzulJdkLocations.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Text; + +namespace Xamarin.Android.Tools { + + class AzulJdkLocations : JdkLocations { + + internal static IEnumerable GetAzulJdks (Action logger) + { + return GetMacOSSystemJdks ("zulu-*.jdk", logger) + .Concat (GetWindowsFileSystemJdks (Path.Combine ("Zulu", "zulu-*"), logger)) + .Concat (GetWindowsRegistryJdks (logger, @"SOFTWARE\Azul Systems\Zulu", "zulu-*", null, "InstallationPath")) + .OrderByDescending (jdk => jdk, JdkInfoVersionComparer.Default); + } + } +} diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Jdks/JdkLocations.MacOS.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Jdks/JdkLocations.MacOS.cs new file mode 100644 index 00000000000..dc2a8f5c6c3 --- /dev/null +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Jdks/JdkLocations.MacOS.cs @@ -0,0 +1,62 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; + +namespace Xamarin.Android.Tools { + + partial class JdkLocations { + + static IEnumerable GetUnixPreferredJdks (Action logger) + { + return GetUnixConfiguredJdkPaths (logger) + .Select (p => JdkInfo.TryGetJdkInfo (p, logger, "monodroid-config.xml")) + .Where (jdk => jdk != null) + .Select (jdk => jdk!) + .OrderByDescending (jdk => jdk, JdkInfoVersionComparer.Default); + } + + static IEnumerable GetUnixConfiguredJdkPaths (Action logger) + { + var config = AndroidSdkUnix.GetUnixConfigFile (logger); + foreach (var java_sdk in config.Root.Elements ("java-sdk")) { + var path = (string) java_sdk.Attribute ("path"); + yield return path; + } + } + + const string MacOSJavaVirtualMachinesRoot = "/Library/Java/JavaVirtualMachines"; + + protected static IEnumerable GetMacOSSystemJdks (string pattern, Action logger, string? locator = null) + { + if (!OS.IsMac) { + return Array.Empty(); + } + locator = locator ?? Path.Combine (MacOSJavaVirtualMachinesRoot, pattern); + return FromPaths (GetMacOSSystemJvmPaths (pattern), logger, locator); + } + + static IEnumerable GetMacOSSystemJvmPaths (string pattern) + { + var root = MacOSJavaVirtualMachinesRoot; + var toHome = Path.Combine ("Contents", "Home"); + var jdks = AppDomain.CurrentDomain.GetData ($"GetMacOSMicrosoftJdkPaths jdks override! {typeof (JdkInfo).AssemblyQualifiedName}") + ?.ToString (); + if (jdks != null) { + root = jdks; + toHome = ""; + pattern = "*"; + } + if (!Directory.Exists (root)) { + yield break; + } + foreach (var dir in Directory.EnumerateDirectories (root, pattern)) { + var home = Path.Combine (dir, toHome); + if (!Directory.Exists (home)) + continue; + yield return home; + } + } + } +} diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Jdks/JdkLocations.Windows.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Jdks/JdkLocations.Windows.cs new file mode 100644 index 00000000000..2380ce6f237 --- /dev/null +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Jdks/JdkLocations.Windows.cs @@ -0,0 +1,113 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; + +namespace Xamarin.Android.Tools { + + partial class JdkLocations { + + internal static string? GetWindowsPreferredJdkPath () + { + var wow = RegistryEx.Wow64.Key32; + var regKey = AndroidSdkWindows.GetMDRegistryKey (); + if (RegistryEx.CheckRegistryKeyForExecutable (RegistryEx.CurrentUser, regKey, AndroidSdkWindows.MDREG_JAVA_SDK, wow, "bin", "java.exe")) + return RegistryEx.GetValueString (RegistryEx.CurrentUser, regKey, AndroidSdkWindows.MDREG_JAVA_SDK, wow); + return null; + + } + + protected static IEnumerable GetWindowsFileSystemJdks (string pattern, Action logger, string? locator = null) + { + if (!OS.IsWindows) { + yield break; + } + + var roots = new List() { + // "Compatibility" with existing codebases; should probably be avoided… + Environment.ExpandEnvironmentVariables ("%ProgramW6432%"), + Environment.GetFolderPath (Environment.SpecialFolder.ProgramFiles), + Environment.GetFolderPath (Environment.SpecialFolder.ProgramFilesX86), + }; + foreach (var root in roots) { + if (!Directory.Exists (root)) + continue; + IEnumerable homes; + try { + homes = Directory.EnumerateDirectories (root, pattern); + } + catch (IOException e) { + continue; + } + foreach (var home in homes) { + if (!File.Exists (Path.Combine (home, "bin", "java.exe"))) + continue; + var loc = locator ?? $"{pattern} via {root}"; + var jdk = JdkInfo.TryGetJdkInfo (home, logger, loc); + if (jdk == null) + continue; + yield return jdk; + } + } + } + + protected static IEnumerable GetWindowsRegistryJdks ( + Action logger, + string parentKey, + string childKeyGlob, + string? grandChildKey, + string valueName, + string? locator = null) + { + if (!OS.IsWindows) { + yield break; + } + + var regex = ToRegex (childKeyGlob); + var paths = new List<(Version version, string path)> (); + var roots = new[] { RegistryEx.CurrentUser, RegistryEx.LocalMachine }; + var wows = new[] { RegistryEx.Wow64.Key32, RegistryEx.Wow64.Key64 }; + foreach (var root in roots) + foreach (var wow in wows) { + foreach (var subkeyName in RegistryEx.EnumerateSubkeys (root, parentKey, wow)) { + if (!regex.Match (subkeyName).Success) { + continue; + } + var key = $@"{parentKey}\{subkeyName}" + + (grandChildKey == null ? "" : "\\" + grandChildKey); + var path = RegistryEx.GetValueString (root, key, valueName, wow); + if (path == null) { + continue; + } + var jdk = JdkInfo.TryGetJdkInfo (path, logger, locator ?? $"Windows Registry @ {parentKey}"); + if (jdk == null) { + continue; + } + yield return jdk; + } + } + } + + static Regex ToRegex (string glob) + { + var r = new StringBuilder (); + foreach (char c in glob) { + switch (c) { + case '*': + r.Append (".*"); + break; + case '?': + r.Append ("."); + break; + default: + r.Append (Regex.Escape (c.ToString ())); + break; + } + } + return new Regex (r.ToString (), RegexOptions.CultureInvariant | RegexOptions.IgnoreCase); + } + } +} diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Jdks/JdkLocations.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Jdks/JdkLocations.cs new file mode 100644 index 00000000000..9f87523eb80 --- /dev/null +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Jdks/JdkLocations.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; + +namespace Xamarin.Android.Tools { + + partial class JdkLocations { + + internal static IEnumerable GetPreferredJdks (Action logger) + { + if (OS.IsWindows) { + var path = GetWindowsPreferredJdkPath (); + var jdk = path == null ? null : JdkInfo.TryGetJdkInfo (path, logger, "Windows Registry"); + if (jdk != null) + yield return jdk; + } + foreach (var jdk in GetUnixPreferredJdks (logger)) { + yield return jdk; + } + } + + protected static IEnumerable FromPaths (IEnumerable paths, Action logger, string locator) + { + return paths + .Select (p => JdkInfo.TryGetJdkInfo (p, logger, locator)) + .Where (jdk => jdk != null) + .Select (jdk => jdk!); + } + } +} diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Jdks/MicrosoftOpenJdkLocations.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Jdks/MicrosoftOpenJdkLocations.cs new file mode 100644 index 00000000000..9274eb6b0cf --- /dev/null +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Jdks/MicrosoftOpenJdkLocations.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Text; + +namespace Xamarin.Android.Tools { + + class MicrosoftOpenJdkLocations : JdkLocations { + + internal static IEnumerable GetMicrosoftOpenJdks (Action logger) + { + return GetMacOSSystemJdks ("microsoft-*.jdk", logger) + .Concat (GetWindowsFileSystemJdks (Path.Combine ("Microsoft", "jdk-*"), logger)) + .Concat (GetWindowsRegistryJdks (logger, @"SOFTWARE\Microsoft\JDK", "*", @"hotspot\MSI", "Path")) + .OrderByDescending (jdk => jdk, JdkInfoVersionComparer.Default); + } + } +} diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Jdks/OracleJdkLocations.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Jdks/OracleJdkLocations.cs new file mode 100644 index 00000000000..0e2a3b01eb3 --- /dev/null +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Jdks/OracleJdkLocations.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Text; + +namespace Xamarin.Android.Tools { + + class OracleJdkLocations : JdkLocations { + + internal static IEnumerable GetOracleJdks (Action logger) + { + if (!OS.IsWindows) { + yield break; + } + foreach (var path in GetOracleJdkPaths ()) { + var jdk = JdkInfo.TryGetJdkInfo (path, logger, "Oracle Registry"); + if (jdk == null) { + continue; + } + yield return jdk; + } + } + + static IEnumerable GetOracleJdkPaths () + { + string subkey = @"SOFTWARE\JavaSoft\Java Development Kit"; + + foreach (var wow64 in new[] { RegistryEx.Wow64.Key32, RegistryEx.Wow64.Key64 }) { + string key_name = string.Format (@"{0}\{1}\{2}", "HKLM", subkey, "CurrentVersion"); + var currentVersion = RegistryEx.GetValueString (RegistryEx.LocalMachine, subkey, "CurrentVersion", wow64); + + if (!string.IsNullOrEmpty (currentVersion)) { + + if (RegistryEx.CheckRegistryKeyForExecutable (RegistryEx.LocalMachine, subkey + "\\" + "1.8", "JavaHome", wow64, "bin", "java.exe")) + yield return RegistryEx.GetValueString (RegistryEx.LocalMachine, subkey + "\\" + "1.8", "JavaHome", wow64) ?? ""; + } + } + } + } +} diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Jdks/VSAndroidJdkLocations.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Jdks/VSAndroidJdkLocations.cs new file mode 100644 index 00000000000..2c5dcc931ad --- /dev/null +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Jdks/VSAndroidJdkLocations.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Text; + +namespace Xamarin.Android.Tools { + + class VSAndroidJdkLocations : JdkLocations { + + internal static IEnumerable GetVSAndroidJdks (Action logger) + { + if (!OS.IsWindows) { + yield break; + } + var root = RegistryEx.LocalMachine; + var wows = new[] { RegistryEx.Wow64.Key32, RegistryEx.Wow64.Key64 }; + var subKey = @"SOFTWARE\Microsoft\VisualStudio\Android"; + var valueName = "JavaHome"; + + foreach (var wow in wows) { + if (!RegistryEx.CheckRegistryKeyForExecutable (root, subKey, valueName, wow, "bin", "java.exe")) { + continue; + } + var path = RegistryEx.GetValueString (root, subKey, valueName, wow) ?? ""; + if (string.IsNullOrEmpty (path)) { + continue; + } + var jdk = JdkInfo.TryGetJdkInfo (path, logger, subKey); + if (jdk == null) { + continue; + } + yield return jdk; + } + } + } +} diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Jdks/XAPrepareJdkLocations.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Jdks/XAPrepareJdkLocations.cs new file mode 100644 index 00000000000..80c1ed8bc30 --- /dev/null +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Jdks/XAPrepareJdkLocations.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Text; + +namespace Xamarin.Android.Tools { + + class XAPrepareJdkLocations : JdkLocations { + + internal static IEnumerable GetXAPrepareJdks (Action logger) + { + return FromPaths (GetXAPrepareJdkPaths (), logger, "android-toolchain"); + } + + static IEnumerable GetXAPrepareJdkPaths () + { + var androidToolchainDir = Path.Combine (Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "android-toolchain"); + if (!Directory.Exists (androidToolchainDir)) { + return Array.Empty (); + } + return Directory.EnumerateDirectories (androidToolchainDir, "jdk*"); + } + } +} diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/OS.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/OS.cs index 338b0fda193..e0e07f7e807 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/OS.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/OS.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Runtime.InteropServices; using System.IO; +using System.Linq; using System.Text; namespace Xamarin.Android.Tools @@ -173,6 +174,27 @@ IntPtr lpftLastWriteTime [DllImport ("advapi32.dll", SetLastError = true)] static extern int RegCloseKey (UIntPtr hKey); + internal static bool CheckRegistryKeyForExecutable (UIntPtr key, string subkey, string valueName, RegistryEx.Wow64 wow64, string subdir, string exe) + { + try { + string key_name = string.Format (@"{0}\{1}\{2}", key == RegistryEx.CurrentUser ? "HKCU" : "HKLM", subkey, valueName); + + var path = AndroidSdkBase.NullIfEmpty (RegistryEx.GetValueString (key, subkey, valueName, wow64)); + + if (path == null) { + return false; + } + + if (!ProcessUtils.FindExecutablesInDirectory (Path.Combine (path, subdir), exe).Any ()) { + return false; + } + + return true; + } catch (Exception) { + return false; + } + } + public static IEnumerable EnumerateSubkeys (UIntPtr key, string subkey, Wow64 wow64) { UIntPtr regKeyHandle; diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkBase.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkBase.cs index 6aa64b848e9..e26233bf62d 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkBase.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkBase.cs @@ -279,7 +279,7 @@ public bool ValidateAndroidNdkLocation ([NotNullWhen (true)] string? loc) return result; } - protected static string? NullIfEmpty (string? s) + internal static string? NullIfEmpty (string? s) { if (s == null || s.Length != 0) return s; diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkWindows.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkWindows.cs index 81c909cf4e0..c3309847d97 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkWindows.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkWindows.cs @@ -8,12 +8,14 @@ namespace Xamarin.Android.Tools { + using static RegistryEx; + class AndroidSdkWindows : AndroidSdkBase { const string MDREG_KEY = @"SOFTWARE\Novell\Mono for Android"; const string MDREG_ANDROID_SDK = "AndroidSdkDirectory"; const string MDREG_ANDROID_NDK = "AndroidNdkDirectory"; - const string MDREG_JAVA_SDK = "JavaSdkDirectory"; + internal const string MDREG_JAVA_SDK = "JavaSdkDirectory"; const string ANDROID_INSTALLER_PATH = @"SOFTWARE\Android SDK Tools"; const string ANDROID_INSTALLER_KEY = "Path"; const string XAMARIN_ANDROID_INSTALLER_PATH = @"SOFTWARE\Xamarin\MonoAndroid"; @@ -63,7 +65,7 @@ public override string? PreferedJavaSdkPath { } } - static string GetMDRegistryKey () + internal static string GetMDRegistryKey () { var regKey = Environment.GetEnvironmentVariable ("XAMARIN_ANDROID_REGKEY"); return string.IsNullOrWhiteSpace (regKey) ? MDREG_KEY : regKey; @@ -108,165 +110,6 @@ protected override IEnumerable GetAllAvailableAndroidSdks () yield return basePath; } - internal static IEnumerable GetJdkInfos (Action logger) - { - JdkInfo? TryGetJdkInfo (string path, string locator) - { - JdkInfo? jdk = null; - try { - jdk = new JdkInfo (path, locator); - } - catch (Exception e) { - logger (TraceLevel.Warning, string.Format (Resources.InvalidJdkDirectory_path_locator_message, path, locator, e.Message)); - logger (TraceLevel.Verbose, e.ToString ()); - } - return jdk; - } - - IEnumerable ToJdkInfos (IEnumerable paths, string locator) - { - return paths.Select (p => TryGetJdkInfo (p, locator)) - .Where (jdk => jdk != null) - .Select(jdk => jdk!) - .OrderByDescending (jdk => jdk, JdkInfoVersionComparer.Default); - } - - return ToJdkInfos (GetPreferredJdkPaths (), "Preferred Registry") - .Concat (ToJdkInfos (GetMicrosoftOpenJdkFilesystemPaths (), "Microsoft OpenJDK Filesystem")) - .Concat (ToJdkInfos (GetMicrosoftOpenJdkRegistryPaths (), "Microsoft OpenJDK Registry")) - .Concat (ToJdkInfos (GetVSAndroidJdkPaths (), @"HKLM\SOFTWARE\Microsoft\VisualStudio\Android@JavaHome")) - .Concat (ToJdkInfos (GetOracleJdkPaths (), "Oracle JDK")) - ; - } - - private static IEnumerable GetPreferredJdkPaths () - { - // check the user specified path - var roots = new[] { RegistryEx.CurrentUser, RegistryEx.LocalMachine }; - const RegistryEx.Wow64 wow = RegistryEx.Wow64.Key32; - var regKey = GetMDRegistryKey (); - - foreach (var root in roots) { - if (CheckRegistryKeyForExecutable (root, regKey, MDREG_JAVA_SDK, wow, "bin", _JarSigner)) - yield return RegistryEx.GetValueString (root, regKey, MDREG_JAVA_SDK, wow) ?? ""; - } - } - - private static IEnumerable GetVSAndroidJdkPaths () - { - var root = RegistryEx.LocalMachine; - var wows = new[] { RegistryEx.Wow64.Key32, RegistryEx.Wow64.Key64 }; - var subKey = @"SOFTWARE\Microsoft\VisualStudio\Android"; - var valueName = "JavaHome"; - - foreach (var wow in wows) { - if (CheckRegistryKeyForExecutable (root, subKey, valueName, wow, "bin", _JarSigner)) - yield return RegistryEx.GetValueString (root, subKey, valueName, wow) ?? ""; - } - } - - static IEnumerable GetMicrosoftOpenJdkFilesystemPaths () - { - const string JdkFolderNamePrefix = "jdk-"; - - var paths = new List> (); - var rootPaths = new List { - Path.Combine (Environment.ExpandEnvironmentVariables ("%ProgramW6432%"), "Microsoft"), - Path.Combine (Environment.GetFolderPath (Environment.SpecialFolder.ProgramFilesX86), "Android", "jdk"), - }; - - foreach (var rootPath in rootPaths) { - if (!Directory.Exists (rootPath)) - continue; - foreach (var directoryName in Directory.EnumerateDirectories (rootPath, $"{JdkFolderNamePrefix}*")) { - var version = ExtractVersion (directoryName, JdkFolderNamePrefix); - if (version == null) - continue; - paths.Add (Tuple.Create (directoryName, version)); - } - } - - return paths.OrderByDescending (v => v.Item2) - .Where (openJdk => ProcessUtils.FindExecutablesInDirectory (Path.Combine (openJdk.Item1, "bin"), _JarSigner).Any ()) - .Select (openJdk => openJdk.Item1); - } - - static IEnumerable GetMicrosoftOpenJdkRegistryPaths () - { - var paths = new List<(Version version, string path)> (); - var roots = new[] { RegistryEx.CurrentUser, RegistryEx.LocalMachine }; - var wows = new[] { RegistryEx.Wow64.Key32, RegistryEx.Wow64.Key64 }; - foreach (var root in roots) - foreach (var wow in wows) { - foreach (var subkeyName in RegistryEx.EnumerateSubkeys (root, MICROSOFT_OPENJDK_PATH, wow)) { - if (!Version.TryParse (subkeyName, out var version)) - continue; - var msiKey = $@"{MICROSOFT_OPENJDK_PATH}\{subkeyName}\hotspot\MSI"; - var path = RegistryEx.GetValueString (root, msiKey, "Path", wow); - if (path == null) - continue; - paths.Add ((version, path)); - } - } - - return paths.OrderByDescending (e => e.version) - .Select (e => e.path); - } - - internal static Version? ExtractVersion (string path, string prefix) - { - var name = Path.GetFileName (path); - if (name.Length <= prefix.Length) - return null; - if (!name.StartsWith (prefix, StringComparison.OrdinalIgnoreCase)) - return null; - - var start = prefix.Length; - while (start < name.Length && !char.IsDigit (name, start)) { - ++start; - } - if (start == name.Length) - return null; - - name = name.Substring (start); - int end = 0; - while (end < name.Length && - (char.IsDigit (name [end]) || name [end] == '.')) { - end++; - } - - do { - if (Version.TryParse (name.Substring (0, end), out var v)) - return v; - end = name.LastIndexOf ('.', end-1); - } while (end > 0); - - return null; - } - - private static IEnumerable GetOracleJdkPaths () - { - string subkey = @"SOFTWARE\JavaSoft\Java Development Kit"; - - foreach (var wow64 in new[] { RegistryEx.Wow64.Key32, RegistryEx.Wow64.Key64 }) { - string key_name = string.Format (@"{0}\{1}\{2}", "HKLM", subkey, "CurrentVersion"); - var currentVersion = RegistryEx.GetValueString (RegistryEx.LocalMachine, subkey, "CurrentVersion", wow64); - - if (!string.IsNullOrEmpty (currentVersion)) { - - // No matter what the CurrentVersion is, look for 1.6 or 1.7 or 1.8 - if (CheckRegistryKeyForExecutable (RegistryEx.LocalMachine, subkey + "\\" + "1.8", "JavaHome", wow64, "bin", _JarSigner)) - yield return RegistryEx.GetValueString (RegistryEx.LocalMachine, subkey + "\\" + "1.8", "JavaHome", wow64) ?? ""; - - if (CheckRegistryKeyForExecutable (RegistryEx.LocalMachine, subkey + "\\" + "1.7", "JavaHome", wow64, "bin", _JarSigner)) - yield return RegistryEx.GetValueString (RegistryEx.LocalMachine, subkey + "\\" + "1.7", "JavaHome", wow64) ?? ""; - - if (CheckRegistryKeyForExecutable (RegistryEx.LocalMachine, subkey + "\\" + "1.6", "JavaHome", wow64, "bin", _JarSigner)) - yield return RegistryEx.GetValueString (RegistryEx.LocalMachine, subkey + "\\" + "1.6", "JavaHome", wow64) ?? ""; - } - } - } - protected override IEnumerable GetAllAvailableAndroidNdks () { @@ -333,29 +176,6 @@ public override void SetPreferredAndroidNdkPath (string? path) RegistryEx.SetValueString (RegistryEx.CurrentUser, regKey, MDREG_ANDROID_NDK, path ?? "", RegistryEx.Wow64.Key32); } - #region Helper Methods - private static bool CheckRegistryKeyForExecutable (UIntPtr key, string subkey, string valueName, RegistryEx.Wow64 wow64, string subdir, string exe) - { - try { - string key_name = string.Format (@"{0}\{1}\{2}", key == RegistryEx.CurrentUser ? "HKCU" : "HKLM", subkey, valueName); - - var path = NullIfEmpty (RegistryEx.GetValueString (key, subkey, valueName, wow64)); - - if (path == null) { - return false; - } - - if (!ProcessUtils.FindExecutablesInDirectory (Path.Combine (path, subdir), exe).Any ()) { - return false; - } - - return true; - } catch (Exception) { - return false; - } - } - #endregion - public override void Initialize (string? androidSdkPath = null, string? androidNdkPath = null, string? javaSdkPath = null) { base.Initialize (androidSdkPath, androidNdkPath, javaSdkPath); diff --git a/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/AndroidSdkWindowsTests.cs b/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/AndroidSdkWindowsTests.cs index cde9f65dd4b..73232a47795 100644 --- a/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/AndroidSdkWindowsTests.cs +++ b/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/AndroidSdkWindowsTests.cs @@ -13,56 +13,5 @@ namespace Xamarin.Android.Tools.Tests [TestFixture] public class AndroidSdkWindowsTests { - [Test] - public void ExtractVersion () - { - var sep = Path.DirectorySeparatorChar; - - var tests = new[]{ - new { - Path = $"foo{sep}", - Prefix = "", - Expected = (Version) null, - }, - new { - Path = $"foo{sep}bar-1-extra", - Prefix = "bar-", - Expected = (Version) null, - }, - new { - Path = $"foo{sep}abcdef", - Prefix = "a", - Expected = (Version) null, - }, - new { - Path = $"foo{sep}a{sep}b.c.d", - Prefix = "none-of-the-above", - Expected = (Version) null, - }, - new { - Path = $"jdks{sep}jdk-1.2.3-hotspot-extra", - Prefix = "jdk-", - Expected = new Version (1, 2, 3), - }, - new { - Path = $"jdks{sep}jdk-1.2.3-hotspot-extra", - Prefix = "jdk", - Expected = new Version (1, 2, 3), - }, - new { - Path = $"jdks{sep}jdk-1.2.3.4.5.6-extra", - Prefix = "jdk-", - Expected = new Version (1, 2, 3, 4), - }, - }; - - foreach (var test in tests) { - Assert.AreEqual ( - test.Expected, - AndroidSdkWindows.ExtractVersion (test.Path, test.Prefix), - $"Version couldn't be extracted from Path=`{test.Path}` Prefix=`{test.Prefix}`!" - ); - } - } } } diff --git a/external/xamarin-android-tools/tools/ls-jdks/App.cs b/external/xamarin-android-tools/tools/ls-jdks/App.cs new file mode 100644 index 00000000000..407dc4b657c --- /dev/null +++ b/external/xamarin-android-tools/tools/ls-jdks/App.cs @@ -0,0 +1,15 @@ +using System; + +namespace Xamarin.Android.Tools +{ + class App + { + static void Main(string[] args) + { + foreach (var jdk in JdkInfo.GetKnownSystemJdkInfos ()) { + Console.WriteLine ($"Found JDK: {jdk.HomePath}"); + Console.WriteLine ($" Locator: {jdk.Locator}"); + } + } + } +} diff --git a/external/xamarin-android-tools/tools/ls-jdks/ls-jdks.csproj b/external/xamarin-android-tools/tools/ls-jdks/ls-jdks.csproj new file mode 100644 index 00000000000..751f34d2e36 --- /dev/null +++ b/external/xamarin-android-tools/tools/ls-jdks/ls-jdks.csproj @@ -0,0 +1,15 @@ + + + + Exe + net472;netcoreapp3.1 + Xamarin.Android.Tools + false + $(ToolOutputFullPath) + + + + + + + From 6a21af24f317279f16e4bfacfccfe6c095c1b015 Mon Sep 17 00:00:00 2001 From: Jonathan Pryor Date: Mon, 19 Apr 2021 13:28:54 -0400 Subject: [PATCH 104/308] [Xamarin.Android.Tools.AndroidSdk] Probe for AdoptOpenJDK Locations (#115) Context: https://adoptopenjdk.net Add support to probe for the [AdoptOpenJDK][0] installation directories. [0]: https://adoptopenjdk.net/?variant=openjdk8&jvmVariant=hotspot --- .../JdkInfo.cs | 1 + .../Jdks/AdoptOpenJdkLocations.cs | 20 +++++++++++++++++++ 2 files changed, 21 insertions(+) create mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Jdks/AdoptOpenJdkLocations.cs diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/JdkInfo.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/JdkInfo.cs index 8cf7239f447..4806565241a 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/JdkInfo.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/JdkInfo.cs @@ -288,6 +288,7 @@ public static IEnumerable GetKnownSystemJdkInfos (Action GetAdoptOpenJdks (Action logger) + { + return GetMacOSSystemJdks ("adoptopenjdk-*.jdk", logger) + .Concat (GetWindowsFileSystemJdks (Path.Combine ("AdoptOpenJDK", "jdk-*"), logger)) + .Concat (GetWindowsRegistryJdks (logger, @"SOFTWARE\AdoptOpenJDK\JDK", "*", @"hotspot\MSI", "Path")) + .OrderByDescending (jdk => jdk, JdkInfoVersionComparer.Default); + } + } +} From ac161835ee0520627d472e274d084c3ac5138e44 Mon Sep 17 00:00:00 2001 From: Jonathan Pryor Date: Tue, 20 Apr 2021 20:48:49 -0400 Subject: [PATCH 105/308] [Xamarin.Android.Tools.AndroidSdk] Fix CS0168 warning (#116) Fix CS0168 warning in `JdkLocations.Windows.cs`: Jdks/JdkLocations.Windows.cs(42,24): warning CS0168: The variable 'e' is declared but never used --- .../Jdks/JdkLocations.Windows.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Jdks/JdkLocations.Windows.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Jdks/JdkLocations.Windows.cs index 2380ce6f237..65e8ad400e3 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Jdks/JdkLocations.Windows.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Jdks/JdkLocations.Windows.cs @@ -39,7 +39,7 @@ protected static IEnumerable GetWindowsFileSystemJdks (string pattern, try { homes = Directory.EnumerateDirectories (root, pattern); } - catch (IOException e) { + catch (IOException) { continue; } foreach (var home in homes) { From 1f3ea0ad42546fc4b297090e6b3d4d6ca3b2fd19 Mon Sep 17 00:00:00 2001 From: Jonathan Pryor Date: Mon, 26 Apr 2021 14:36:25 -0400 Subject: [PATCH 106/308] [Xamarin.Android.Tools.AndroidSdk] Support Microsoft Dist JDK (#117) Partially reverts commit 5dcd2875. The plan to remove support for the obsolete `microsoft_dist_openjdk_` JDK distribution has hit a few "snags", and thus we cannot remove support for the legacy `microsoft_dist_openjdk_` package as quickly as we had hoped. Re-introduce support for the `microsoft_dist_openjdk_` JDK installation location. --- .../JdkInfo.cs | 1 + .../Jdks/MicrosoftDistJdkLocations.cs | 33 +++++++++++++++++++ 2 files changed, 34 insertions(+) create mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Jdks/MicrosoftDistJdkLocations.cs diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/JdkInfo.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/JdkInfo.cs index 4806565241a..e973d192314 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/JdkInfo.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/JdkInfo.cs @@ -292,6 +292,7 @@ public static IEnumerable GetKnownSystemJdkInfos (Action GetMicrosoftDistJdks (Action logger) + { + return FromPaths (GetMacOSMicrosoftDistJdkPaths (), logger, "$HOME/Library/Developer/Xamarin/jdk") + .Concat (GetWindowsFileSystemJdks (Path.Combine ("Android", "jdk", "microsoft_dist_openjdk_*"), logger, locator: "legacy microsoft_dist_openjdk")) + .OrderByDescending (jdk => jdk, JdkInfoVersionComparer.Default); + } + + static IEnumerable GetMacOSMicrosoftDistJdkPaths () + { + var jdks = AppDomain.CurrentDomain.GetData ($"GetMacOSMicrosoftJdkPaths jdks override! {typeof (JdkInfo).AssemblyQualifiedName}") + ?.ToString (); + if (jdks == null) { + var home = Environment.GetFolderPath (Environment.SpecialFolder.Personal); + jdks = Path.Combine (home, "Library", "Developer", "Xamarin", "jdk"); + } + if (!Directory.Exists (jdks)) + return Enumerable.Empty (); + + return Directory.EnumerateDirectories (jdks); + } + } +} From 1e888d6674eeb7482c8218ac19bfab5dcb92af97 Mon Sep 17 00:00:00 2001 From: Peter Collins Date: Thu, 29 Apr 2021 17:36:32 -0400 Subject: [PATCH 107/308] [Microsoft.Android.Build] Use MSBuild NuGets (#118) Context: https://github.com/xamarin/xamarin-android/pull/5891 I've been hitting errors running tests locally on Windows after building with .NET 6 Preview 3: Error Message: System.IO.FileNotFoundException : Could not load file or assembly 'System.Runtime, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'. The system cannot find the file specified. Stack Trace: at Xamarin.ProjectTools.DotNetXamarinProject.SaveProject() at Xamarin.ProjectTools.XamarinProject.Save(Boolean saveProject) in C:\Users\Peter\source\xamarin-android\src\Xamarin.Android.Build.Tasks\Tests\Xamarin.ProjectTools\Common\XamarinProject.cs:line 241 at Xamarin.ProjectTools.ProjectBuilder.Save(XamarinProject project, Boolean doNotCleanupOnUpdate, Boolean saveProject) in C:\Users\Peter\source\xamarin-android\src\Xamarin.Android.Build.Tasks\Tests\Xamarin.ProjectTools\Common\ProjectBuilder.cs:line 43 at Xamarin.ProjectTools.ProjectBuilder.Build(XamarinProject project, Boolean doNotCleanupOnUpdate, String[] parameters, Boolean saveProject, Dictionary`2 environmentVariables) in C:\Users\Peter\source\xamarin-android\src\Xamarin.Android.Build.Tasks\Tests\Xamarin.ProjectTools\Common\ProjectBuilder.cs:line 67 at Xamarin.Android.Build.Tests.WearTests.ResolveLibraryImportsWithReadonlyFiles() in C:\Users\Peter\source\xamarin-android\src\Xamarin.Android.Build.Tasks\Tests\Xamarin.Android.Build.Tests\WearTests.cs:line 28 Rather than copying the version of the MSBuild assemblies used to build our test projects to the output directory, we should be able to use the NuGet package content directly. These files should only be used by our project creation APIs and as a result their version shouldn't matter. --- .../MSBuildReferences.projitems | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems index 0bd77b355fe..4b0dd1852f6 100644 --- a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems +++ b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems @@ -10,12 +10,11 @@ - - - - - + + + + @@ -49,12 +48,4 @@ - - - - PreserveNewest - - - From fa528d25ca153c875bc4c3af10db8a38329bf8ae Mon Sep 17 00:00:00 2001 From: Jonathan Pobst Date: Mon, 24 May 2021 17:08:04 -0500 Subject: [PATCH 108/308] Add SECURITY.md (#119) Copied and adjusted from: * https://github.com/dotnet/runtime/blob/b443b8ccce174ef0e87183e21561c4ff13373cfd/SECURITY.md --- external/xamarin-android-tools/SECURITY.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 external/xamarin-android-tools/SECURITY.md diff --git a/external/xamarin-android-tools/SECURITY.md b/external/xamarin-android-tools/SECURITY.md new file mode 100644 index 00000000000..1fa12d855c0 --- /dev/null +++ b/external/xamarin-android-tools/SECURITY.md @@ -0,0 +1,15 @@ +# Security Policy + +xamarin-android-tools is supported as a component of Xamarin.Android. + +## Supported Versions + +The Xamarin.Android support policy, including supported versions, can be found at the [Xamarin Support Policy](https://dotnet.microsoft.com/platform/support/policy/xamarin). + +## Reporting a Vulnerability + +Security issues and bugs should be reported privately to the Microsoft Security Response Center (MSRC), either by emailing secure@microsoft.com or via the portal at https://msrc.microsoft.com. +You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your +original message. Further information, including the MSRC PGP key, can be found in the [MSRC Report an Issue FAQ](https://www.microsoft.com/en-us/msrc/faqs-report-an-issue). + +Please do not open issues for anything you think might have a security implication. From 391f7a9f5e1ff1d291a785a9a8d0cf83bd567472 Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Fri, 4 Jun 2021 14:55:04 -0500 Subject: [PATCH 109/308] [JdkInfo] handle invalid XML from /usr/libexec/java_home (#120) Context: https://github.com/xamarin/xamarin-android/issues/5992 Context: https://dev.azure.com/xamarin/public/_build/results?buildId=40929&view=logs&j=cfef19e4-4225-5e84-636a-86ba2231ac21&t=cd3bc6c3-ac29-5320-67c9-5fb59fc8e05c&l=732 dotnet/maui has hit some build failures such as: error XARSD7000: System.Xml.XmlException: Root element is missing. error XARSD7000: at System.Xml.XmlTextReaderImpl.Throw (System.Exception e) error XARSD7000: at System.Xml.XmlTextReaderImpl.ThrowWithoutLineInfo (System.String res) error XARSD7000: at System.Xml.XmlTextReaderImpl.ParseDocumentContent () error XARSD7000: at System.Xml.XmlTextReaderImpl.Read () error XARSD7000: at System.Xml.XmlReader.MoveToContent () error XARSD7000: at System.Xml.Linq.XElement.Load (System.Xml.XmlReader reader, System.Xml.Linq.LoadOptions options) error XARSD7000: at System.Xml.Linq.XElement.Parse (System.String text, System.Xml.Linq.LoadOptions options) error XARSD7000: at System.Xml.Linq.XElement.Parse (System.String text) error XARSD7000: at Xamarin.Android.Tools.JdkInfo+d__56.MoveNext () error XARSD7000: at System.Linq.Enumerable+DistinctIterator`1[TSource].MoveNext () error XARSD7000: at System.Linq.Enumerable+SelectEnumerableIterator`2[TSource,TResult].MoveNext () error XARSD7000: at System.Linq.Enumerable+WhereSelectEnumerableIterator`2[TSource,TResult].ToArray () error XARSD7000: at System.Linq.Buffer`1[TElement]..ctor (System.Collections.Generic.IEnumerable`1[T] source) error XARSD7000: at System.Linq.OrderedEnumerable`1+d__3[TElement].MoveNext () error XARSD7000: at System.Linq.Enumerable+ConcatIterator`1[TSource].MoveNext () error XARSD7000: at System.Linq.Enumerable+SelectEnumerableIterator`2[TSource,TResult].MoveNext () error XARSD7000: at Xamarin.Android.Tools.AndroidSdkBase.GetValidPath (System.Func`2[T,TResult] pathValidator, System.String ctorParam, System.Func`1[TResult] getPreferredPath, System.Func`1[TResult] getAllPaths) error XARSD7000: at Xamarin.Android.Tools.AndroidSdkBase.Initialize (System.String androidSdkPath, System.String androidNdkPath, System.String javaSdkPath) error XARSD7000: at Xamarin.Android.Tools.AndroidSdkInfo..ctor (System.Action`2[T1,T2] logger, System.String androidSdkPath, System.String androidNdkPath, System.String javaSdkPath) error XARSD7000: at Xamarin.Android.Tasks.MonoAndroidHelper.RefreshAndroidSdk (System.String sdkPath, System.String ndkPath, System.String javaPath, Microsoft.Build.Utilities.TaskLoggingHelper logHelper) error XARSD7000: at Xamarin.Android.Tasks.ResolveSdks.RunTask () error XARSD7000: at Xamarin.Android.Tasks.AndroidTask.Execute () It looks like `JdkInfo.GetLibexecJdkPaths()` doesn't handle `XmlException` and warn appropriately. I don't have the *actual* output of `/usr/libexec/java_home -X` from these machines, but I suspect it's printing some message before the XML or no XML at all. --- .../src/Xamarin.Android.Tools.AndroidSdk/JdkInfo.cs | 9 ++++++++- .../Properties/Resources.Designer.cs | 9 +++++++++ .../Properties/Resources.resx | 3 +++ .../Properties/xlf/Resources.cs.xlf | 5 +++++ .../Properties/xlf/Resources.de.xlf | 5 +++++ .../Properties/xlf/Resources.es.xlf | 5 +++++ .../Properties/xlf/Resources.fr.xlf | 5 +++++ .../Properties/xlf/Resources.it.xlf | 5 +++++ .../Properties/xlf/Resources.ja.xlf | 5 +++++ .../Properties/xlf/Resources.ko.xlf | 5 +++++ .../Properties/xlf/Resources.pl.xlf | 5 +++++ .../Properties/xlf/Resources.pt-BR.xlf | 5 +++++ .../Properties/xlf/Resources.ru.xlf | 5 +++++ .../Properties/xlf/Resources.tr.xlf | 5 +++++ .../Properties/xlf/Resources.zh-Hans.xlf | 5 +++++ .../Properties/xlf/Resources.zh-Hant.xlf | 5 +++++ 16 files changed, 85 insertions(+), 1 deletion(-) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/JdkInfo.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/JdkInfo.cs index e973d192314..f7a0f0a6476 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/JdkInfo.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/JdkInfo.cs @@ -350,7 +350,14 @@ static IEnumerable GetLibexecJdkPaths (Action logger return; xml.Append (e.Data); }, includeStderr: false); - var plist = XElement.Parse (xml.ToString ()); + + XElement plist; + try { + plist = XElement.Parse (xml.ToString ()); + } catch (XmlException e) { + logger (TraceLevel.Warning, string.Format (Resources.InvalidXmlLibExecJdk_path_args_message, jhp.FileName, jhp.Arguments, e.Message)); + yield break; + } foreach (var info in plist.Elements ("array").Elements ("dict")) { var JVMHomePath = (XNode) info.Elements ("key").FirstOrDefault (e => e.Value == "JVMHomePath"); if (JVMHomePath == null) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.Designer.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.Designer.cs index 06754927128..9013f19feef 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.Designer.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.Designer.cs @@ -77,5 +77,14 @@ internal static string InvalidMonodroidConfigFile_path_message { return ResourceManager.GetString("InvalidMonodroidConfigFile_path_message", resourceCulture); } } + + /// + /// Looks up a localized string similar to An exception occurred while reading the output of '{0} {1}'. Exception: {2}. + /// + internal static string InvalidXmlLibExecJdk_path_args_message { + get { + return ResourceManager.GetString("InvalidXmlLibExecJdk_path_args_message", resourceCulture); + } + } } } diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.resx b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.resx index 4e90d1ac9da..7b047617793 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.resx +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.resx @@ -130,4 +130,7 @@ {0} - The path of the file being read. {1} - The exception message of the associated exception. + + An exception occurred while reading the output of '{0} {1}'. Exception: {2} + \ No newline at end of file diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.cs.xlf b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.cs.xlf index 4ee39f8d13c..bd8c0d8bad0 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.cs.xlf +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.cs.xlf @@ -17,6 +17,11 @@ {0} - The path of the file being read. {1} - The exception message of the associated exception. + + An exception occurred while reading the output of '{0} {1}'. Exception: {2} + An exception occurred while reading the output of '{0} {1}'. Exception: {2} + + \ No newline at end of file diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.de.xlf b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.de.xlf index 0ef03afff9d..f4bf5ddcd78 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.de.xlf +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.de.xlf @@ -17,6 +17,11 @@ {0} - The path of the file being read. {1} - The exception message of the associated exception. + + An exception occurred while reading the output of '{0} {1}'. Exception: {2} + An exception occurred while reading the output of '{0} {1}'. Exception: {2} + + \ No newline at end of file diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.es.xlf b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.es.xlf index 745a7b1eb63..0e1e9987593 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.es.xlf +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.es.xlf @@ -17,6 +17,11 @@ {0} - The path of the file being read. {1} - The exception message of the associated exception. + + An exception occurred while reading the output of '{0} {1}'. Exception: {2} + An exception occurred while reading the output of '{0} {1}'. Exception: {2} + + \ No newline at end of file diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.fr.xlf b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.fr.xlf index 88ef6bd4636..1a577af4912 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.fr.xlf +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.fr.xlf @@ -17,6 +17,11 @@ {0} - The path of the file being read. {1} - The exception message of the associated exception. + + An exception occurred while reading the output of '{0} {1}'. Exception: {2} + An exception occurred while reading the output of '{0} {1}'. Exception: {2} + + \ No newline at end of file diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.it.xlf b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.it.xlf index e450f94fdad..c94f2d39baf 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.it.xlf +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.it.xlf @@ -17,6 +17,11 @@ {0} - The path of the file being read. {1} - The exception message of the associated exception. + + An exception occurred while reading the output of '{0} {1}'. Exception: {2} + An exception occurred while reading the output of '{0} {1}'. Exception: {2} + + \ No newline at end of file diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.ja.xlf b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.ja.xlf index 964e5f8dbb8..6de35c24a83 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.ja.xlf +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.ja.xlf @@ -17,6 +17,11 @@ {0} - The path of the file being read. {1} - The exception message of the associated exception. + + An exception occurred while reading the output of '{0} {1}'. Exception: {2} + An exception occurred while reading the output of '{0} {1}'. Exception: {2} + + \ No newline at end of file diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.ko.xlf b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.ko.xlf index f9c441ffad5..c96a2ccf2f2 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.ko.xlf +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.ko.xlf @@ -17,6 +17,11 @@ {0} - The path of the file being read. {1} - The exception message of the associated exception. + + An exception occurred while reading the output of '{0} {1}'. Exception: {2} + An exception occurred while reading the output of '{0} {1}'. Exception: {2} + + \ No newline at end of file diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.pl.xlf b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.pl.xlf index 124fad07c6a..102d8036668 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.pl.xlf +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.pl.xlf @@ -17,6 +17,11 @@ {0} - The path of the file being read. {1} - The exception message of the associated exception. + + An exception occurred while reading the output of '{0} {1}'. Exception: {2} + An exception occurred while reading the output of '{0} {1}'. Exception: {2} + + \ No newline at end of file diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.pt-BR.xlf b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.pt-BR.xlf index 948c0ba0726..394aa0620be 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.pt-BR.xlf +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.pt-BR.xlf @@ -17,6 +17,11 @@ {0} - The path of the file being read. {1} - The exception message of the associated exception. + + An exception occurred while reading the output of '{0} {1}'. Exception: {2} + An exception occurred while reading the output of '{0} {1}'. Exception: {2} + + \ No newline at end of file diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.ru.xlf b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.ru.xlf index 0b14aed9b73..b39553d1666 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.ru.xlf +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.ru.xlf @@ -17,6 +17,11 @@ {0} - The path of the file being read. {1} - The exception message of the associated exception. + + An exception occurred while reading the output of '{0} {1}'. Exception: {2} + An exception occurred while reading the output of '{0} {1}'. Exception: {2} + + \ No newline at end of file diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.tr.xlf b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.tr.xlf index 3406e2d338f..020759cf4c3 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.tr.xlf +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.tr.xlf @@ -17,6 +17,11 @@ {0} - The path of the file being read. {1} - The exception message of the associated exception. + + An exception occurred while reading the output of '{0} {1}'. Exception: {2} + An exception occurred while reading the output of '{0} {1}'. Exception: {2} + + \ No newline at end of file diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.zh-Hans.xlf b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.zh-Hans.xlf index 1414a0f03b7..4a160a199dc 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.zh-Hans.xlf +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.zh-Hans.xlf @@ -17,6 +17,11 @@ {0} - The path of the file being read. {1} - The exception message of the associated exception. + + An exception occurred while reading the output of '{0} {1}'. Exception: {2} + An exception occurred while reading the output of '{0} {1}'. Exception: {2} + + \ No newline at end of file diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.zh-Hant.xlf b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.zh-Hant.xlf index c4db954ebaa..74d9a2e51c2 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.zh-Hant.xlf +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.zh-Hant.xlf @@ -17,6 +17,11 @@ {0} - The path of the file being read. {1} - The exception message of the associated exception. + + An exception occurred while reading the output of '{0} {1}'. Exception: {2} + An exception occurred while reading the output of '{0} {1}'. Exception: {2} + + \ No newline at end of file From 6db2428e8b6394c4de0834ba4f3abef89139c8a9 Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Fri, 4 Jun 2021 15:58:29 -0500 Subject: [PATCH 110/308] [BaseTasks] add ABI detection for RIDs (#121) Context: https://github.com/xamarin/xamarin-android/issues/5432 Two cases currently do not work in .NET 6: 1. When an Android App project includes native libraries which are in directory names consisting of .NET runtime identifiers instead of Android ABI names, e.g. android-arm/libfoo.so android-arm64/libfoo.so android-x86/libfoo.so android-x64/libfoo.so 2. When a NuGet package places native libraries into a `native` directory *between* the `$(RuntimeIdentifier)` directory and the native library, a'la [`SQLitePCLRaw.lib.e_sqlite3.linux`][0]: runtimes/linux-arm/native/libe_sqlite3.so Fix case (1) by checking using `AndroidRidAbiHelper.RuntimeIdentifierToAbi()` on the directory name to determine the Android ABI of the library. Fix case (2) by also checking the native library's parent parent directory name against Android ABI names and Runtime Identifiers. This allows us to correctly associate runtimes/android-arm64/native/libe_sqlite3.so as an arm64-v8a native library. I implemented these two cases as fallbacks to the existing logic. I think this will be fine for the behavior to be in "legacy" Xamarin.Android as well as .NET 6. I added tests for `AndroidRidAbiHelper`, since we had none before. [0]: https://www.nuget.org/packages/SQLitePCLRaw.lib.e_sqlite3.linux/1.1.14 --- .../AndroidRidAbiHelper.cs | 27 +++- .../AndroidRidAbiHelperTests.cs | 142 ++++++++++++++++++ 2 files changed, 162 insertions(+), 7 deletions(-) create mode 100644 external/xamarin-android-tools/tests/Microsoft.Android.Build.BaseTasks-Tests/AndroidRidAbiHelperTests.cs diff --git a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/AndroidRidAbiHelper.cs b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/AndroidRidAbiHelper.cs index 229d069062b..8e6cce4e560 100644 --- a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/AndroidRidAbiHelper.cs +++ b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/AndroidRidAbiHelper.cs @@ -19,13 +19,27 @@ public static class AndroidRidAbiHelper public static string GetNativeLibraryAbi (string lib) { // The topmost directory the .so file is contained within - var dir = Path.GetFileName (Path.GetDirectoryName (lib)).ToLowerInvariant (); - if (dir.StartsWith ("interpreter-", StringComparison.Ordinal)) { - dir = dir.Substring (12); + var dir = Directory.GetParent (lib); + var dirName = dir.Name.ToLowerInvariant (); + if (dirName.StartsWith ("interpreter-", StringComparison.Ordinal)) { + dirName = dirName.Substring ("interpreter-".Length); } - if (ValidAbis.Contains (dir)) { - return dir; + if (ValidAbis.Contains (dirName)) { + return dirName; } + + // Look for a directory with a RID as a name, such as: + // android-arm64/libfoo.so + var abi = RuntimeIdentifierToAbi (dirName); + if (!string.IsNullOrEmpty (abi)) + return abi; + + // Try one directory higher, such as: + // packages/sqlitepclraw.lib.e_sqlite3.android/1.1.11/runtimes/android-arm64/native/libe_sqlite3.so + abi = RuntimeIdentifierToAbi (dir.Parent.Name.ToLowerInvariant ()); + if (!string.IsNullOrEmpty (abi)) + return abi; + return null; } @@ -41,8 +55,7 @@ public static string GetNativeLibraryAbi (ITaskItem lib) // First, try nominal "Link" path. var link = lib.GetMetadata ("Link"); if (!string.IsNullOrWhiteSpace (link)) { - var linkdirs = link.ToLowerInvariant ().Split ('/', '\\'); - lib_abi = ValidAbis.Where (p => linkdirs.Contains (p)).FirstOrDefault (); + lib_abi = GetNativeLibraryAbi (link); } // Check for a RuntimeIdentifier diff --git a/external/xamarin-android-tools/tests/Microsoft.Android.Build.BaseTasks-Tests/AndroidRidAbiHelperTests.cs b/external/xamarin-android-tools/tests/Microsoft.Android.Build.BaseTasks-Tests/AndroidRidAbiHelperTests.cs new file mode 100644 index 00000000000..bb241cb8745 --- /dev/null +++ b/external/xamarin-android-tools/tests/Microsoft.Android.Build.BaseTasks-Tests/AndroidRidAbiHelperTests.cs @@ -0,0 +1,142 @@ +using System.Collections.Generic; +using Microsoft.Android.Build.Tasks; +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; +using NUnit.Framework; + +namespace Microsoft.Android.Build.BaseTasks.Tests +{ + [TestFixture] + public class AndroidRidAbiHelperTests + { + static object [] StringValueSource = new object [] { + new[] { + /* input */ "armeabi-v7a/libfoo.so", + /* expected */ "armeabi-v7a" + }, + new[] { + /* input */ "arm64-v8a/libfoo.so", + /* expected */ "arm64-v8a" + }, + new[] { + /* input */ "x86/libfoo.so", + /* expected */ "x86" + }, + new[] { + /* input */ "x86_64/libfoo.so", + /* expected */ "x86_64" + }, + new[] { + /* input */ "android-arm/libfoo.so", + /* expected */ "armeabi-v7a" + }, + new[] { + /* input */ "android-arm64/libfoo.so", + /* expected */ "arm64-v8a" + }, + new[] { + /* input */ "android-x86/libfoo.so", + /* expected */ "x86" + }, + new[] { + /* input */ "android-x64/libfoo.so", + /* expected */ "x86_64" + }, + new[] { + /* input */ "android-arm/native/libfoo.so", + /* expected */ "armeabi-v7a" + }, + new[] { + /* input */ "android-arm64/native/libfoo.so", + /* expected */ "arm64-v8a" + }, + new[] { + /* input */ "android-x86/native/libfoo.so", + /* expected */ "x86" + }, + new[] { + /* input */ "android-x64/native/libfoo.so", + /* expected */ "x86_64" + }, + new[] { + /* input */ "android.21-x64/native/libfoo.so", + /* expected */ "x86_64" + }, + new[] { + /* input */ "packages/sqlitepclraw.lib.e_sqlite3.android/1.1.11/runtimes/android-arm64/native/libe_sqlite3.so", + /* expected */ "arm64-v8a" + } + }; + + [Test] + [TestCaseSource (nameof (StringValueSource))] + public void StringValue (string input, string expected) + { + Assert.AreEqual (expected, AndroidRidAbiHelper.GetNativeLibraryAbi (input)); + } + + static object [] ITaskItemValueSource = new object [] { + new object [] { + /* input */ + new TaskItem("armeabi-v7a/libfoo.so"), + /* expected */ + "armeabi-v7a" + }, + new object [] { + /* input */ + new TaskItem("libabi.so", new Dictionary { + { "Abi", "armeabi-v7a" } + }), + /* expected */ + "armeabi-v7a" + }, + new object [] { + /* input */ + new TaskItem("librid.so", new Dictionary { + { "RuntimeIdentifier", "android-arm" } + }), + /* expected */ + "armeabi-v7a" + }, + new object [] { + /* input */ + new TaskItem("liblink.so", new Dictionary { + { "Link", "armeabi-v7a/libfoo.so" } + }), + /* expected */ + "armeabi-v7a" + }, + new object [] { + /* input */ + new TaskItem("liblink.so", new Dictionary { + { "Link", "x86/libfoo.so" } + }), + /* expected */ + "x86" + }, + new object [] { + /* input */ + new TaskItem("liblink.so", new Dictionary { + { "Link", "x86_64/libfoo.so" } + }), + /* expected */ + "x86_64" + }, + new object [] { + /* input */ + new TaskItem("libridlink.so", new Dictionary { + { "Link", "android-arm/libfoo.so" } + }), + /* expected */ + "armeabi-v7a" + }, + }; + + [Test] + [TestCaseSource (nameof (ITaskItemValueSource))] + public void ITaskItemValue (ITaskItem input, string expected) + { + Assert.AreEqual (expected, AndroidRidAbiHelper.GetNativeLibraryAbi (input)); + } + } +} From d48ffd808cbd0ed245bb8da5c29624fe029fdb13 Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Tue, 8 Jun 2021 12:45:31 +0200 Subject: [PATCH 111/308] Reference the new Mono.Unix nuget (#123) Mono.Posix.NETStandard is being replaced with the new Mono.Unix package in Xamarin.Android. Xamarin.Android imports the `MSBuildReferences.projitems` file which referenced the older package. This commit switches to the new package. --- .../MSBuildReferences.projitems | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems index 4b0dd1852f6..1b27a12dd38 100644 --- a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems +++ b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems @@ -6,7 +6,8 @@ 15.1.0.0 16.5 - 1.0.24 + 2.0.0-alpha4 + 7.0.0-alpha8.21302.6 @@ -40,7 +41,7 @@ - + From 64c243a6ba6c58ff1703262ac3b3af04be860439 Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Tue, 8 Jun 2021 08:54:43 -0500 Subject: [PATCH 112/308] [BaseTasks] fix `\`-delimited paths on macOS (#122) Tests in xamarin-android have started failing with: libfoo.so : error XA4301: Cannot determine ABI of native library 'libfoo.so'. Move this file to a directory with a valid Android ABI name such as 'libs/armeabi-v7a/'. Where the test in question sets `Link=x86\libfoo.so`: https://github.com/xamarin/xamarin-android/blob/bf63c3d116b38459678cb3aefd2f5826e78c385e/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/XASdkTests.cs#L115 This only fails on macOS and works fine on Windows. I didn't consider this case in 6db2428e. I've added test cases for `\`, and added a `string.Replace()` that should solve the problem on macOS. I added test cases for `null` input as well. Co-authored-by: Jonathan Pryor --- .../AndroidRidAbiHelper.cs | 4 ++ .../AndroidRidAbiHelperTests.cs | 40 ++++++++++++++++++- 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/AndroidRidAbiHelper.cs b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/AndroidRidAbiHelper.cs index 8e6cce4e560..01aef16b703 100644 --- a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/AndroidRidAbiHelper.cs +++ b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/AndroidRidAbiHelper.cs @@ -18,6 +18,10 @@ public static class AndroidRidAbiHelper public static string GetNativeLibraryAbi (string lib) { + if (string.IsNullOrEmpty (lib)) + return null; + lib = lib.Replace ('\\', Path.DirectorySeparatorChar); + // The topmost directory the .so file is contained within var dir = Directory.GetParent (lib); var dirName = dir.Name.ToLowerInvariant (); diff --git a/external/xamarin-android-tools/tests/Microsoft.Android.Build.BaseTasks-Tests/AndroidRidAbiHelperTests.cs b/external/xamarin-android-tools/tests/Microsoft.Android.Build.BaseTasks-Tests/AndroidRidAbiHelperTests.cs index bb241cb8745..f2db03049ee 100644 --- a/external/xamarin-android-tools/tests/Microsoft.Android.Build.BaseTasks-Tests/AndroidRidAbiHelperTests.cs +++ b/external/xamarin-android-tools/tests/Microsoft.Android.Build.BaseTasks-Tests/AndroidRidAbiHelperTests.cs @@ -10,6 +10,14 @@ namespace Microsoft.Android.Build.BaseTasks.Tests public class AndroidRidAbiHelperTests { static object [] StringValueSource = new object [] { + new[] { + /* input */ default (string), + /* expected */ default (string) + }, + new[] { + /* input */ "", + /* expected */ default + }, new[] { /* input */ "armeabi-v7a/libfoo.so", /* expected */ "armeabi-v7a" @@ -65,7 +73,15 @@ public class AndroidRidAbiHelperTests new[] { /* input */ "packages/sqlitepclraw.lib.e_sqlite3.android/1.1.11/runtimes/android-arm64/native/libe_sqlite3.so", /* expected */ "arm64-v8a" - } + }, + new[] { + /* input */ "arm64-v8a\\libfoo.so", + /* expected */ "arm64-v8a" + }, + new[] { + /* input */ "android-arm64\\libfoo.so", + /* expected */ "arm64-v8a" + }, }; [Test] @@ -76,6 +92,12 @@ public void StringValue (string input, string expected) } static object [] ITaskItemValueSource = new object [] { + new object [] { + /* input */ + new TaskItem(""), + /* expected */ + default (string) + }, new object [] { /* input */ new TaskItem("armeabi-v7a/libfoo.so"), @@ -130,6 +152,22 @@ public void StringValue (string input, string expected) /* expected */ "armeabi-v7a" }, + new object [] { + /* input */ + new TaskItem("liblinkwin.so", new Dictionary { + { "Link", "x86_64\\libfoo.so" } + }), + /* expected */ + "x86_64" + }, + new object [] { + /* input */ + new TaskItem("liblinkwin.so", new Dictionary { + { "Link", "android-arm64\\libfoo.so" }, + }), + /* expected */ + "arm64-v8a", + }, }; [Test] From 10646adff2b5bc3dd1a7697842fe1e1a2a76cb73 Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Thu, 10 Jun 2021 17:27:12 +0200 Subject: [PATCH 113/308] Bump LibZipSharp to 2.0.0-alpha6 The new version switched back to `zlib` from `zlib-ng` and increased the compression level it uses to 9 (from the previous 6) --- .../MSBuildReferences.projitems | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems index 1b27a12dd38..d209eef9703 100644 --- a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems +++ b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems @@ -6,7 +6,7 @@ 15.1.0.0 16.5 - 2.0.0-alpha4 + 2.0.0-alpha6 7.0.0-alpha8.21302.6 From a7da026e6d70fdf72d659d44fc6004b37e690baa Mon Sep 17 00:00:00 2001 From: Peter Collins Date: Thu, 17 Jun 2021 16:10:20 -0400 Subject: [PATCH 114/308] [Microsoft.Android.Build] Bump to MSBuild 16.10.0 (#125) Related: https://github.com/xamarin/xamarin-android/pull/6014 Drops the OS specific MSBuild NuGet and system references and instead uses MSBuild 16.10.0 NuGet packages. This introduces an issue in our usage of `Microsoft.Build.Construction.ProjectRootElement.Create` in xamarin/xamarin-android on macOS, however we can work around it there. --- .../MSBuildReferences.projitems | 33 ++----------------- 1 file changed, 2 insertions(+), 31 deletions(-) diff --git a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems index d209eef9703..a5a47235bb2 100644 --- a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems +++ b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems @@ -4,49 +4,20 @@ - 15.1.0.0 - 16.5 + 16.10.0 2.0.0-alpha6 7.0.0-alpha8.21302.6 - - + - - - - - True - $(MSBuildToolsPath)\Microsoft.Build.dll - - - True - $(MSBuildToolsPath)\Microsoft.Build.Framework.dll - - - True - $(MSBuildToolsPath)\Microsoft.Build.Tasks.Core.dll - - - True - $(MSBuildToolsPath)\Microsoft.Build.Utilities.Core.dll - - - - - - - - - From d307b793948f18f67b5ca80c482b0fcbbeee6bcf Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Tue, 22 Jun 2021 13:37:45 +0200 Subject: [PATCH 115/308] Bump LibZipSharp to 2.0.0-alpha7 (#126) Context: https://github.com/xamarin/LibZipSharp/commit/fcb9323d9d95b06051f67d8d73ba371a4029722d Context: https://libzip.org/news/release-1.8.0.html 2.0.0-alpha7 changes: * bump libzip version to 1.8.0 * add support for ZSTD compression * properly hide all the native library symbols except for the libzip ones --- .../MSBuildReferences.projitems | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems index a5a47235bb2..7d7301294cd 100644 --- a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems +++ b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems @@ -5,7 +5,7 @@ 16.10.0 - 2.0.0-alpha6 + 2.0.0-alpha7 7.0.0-alpha8.21302.6 From 3a795426a4ceb9d4c8f7104f297eea424a3ad96b Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Tue, 6 Jul 2021 22:19:12 +0200 Subject: [PATCH 116/308] [NDK] Properly detect 64-bit NDK The code detecting whether an instance of NDK is a 64-bit one assumed that the GNU binutils paths existed below the toolchains dir and used them to check whether the 64-bit platform directory exists under them in order to determine if the NDK is a 64-bit one. NDK r23, however, will remove the GNU binutils completely and those paths no longer exist. Add a check for the `toolchains/llvm/prebuilt/[PLATFORM]` directory existence before the older check so that detection works correctly for NDK r23+. --- .../Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkBase.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkBase.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkBase.cs index e26233bf62d..c76fe9bbd56 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkBase.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkBase.cs @@ -87,7 +87,11 @@ public virtual void Initialize (string? androidSdkPath = null, string? androidNd if (!string.IsNullOrEmpty (AndroidNdkPath)) { // It would be nice if .NET had real globbing support in System.IO... string toolchainsDir = Path.Combine (AndroidNdkPath, "toolchains"); - if (Directory.Exists (toolchainsDir)) { + string llvmNdkToolchainDir = Path.Combine (toolchainsDir, "llvm", "prebuilt", NdkHostPlatform64Bit); + + if (Directory.Exists (llvmNdkToolchainDir)) { + IsNdk64Bit = true; + } else if (Directory.Exists (toolchainsDir)) { IsNdk64Bit = Directory.EnumerateDirectories (toolchainsDir, "arm-linux-androideabi-*") .Any (dir => Directory.Exists (Path.Combine (dir, "prebuilt", NdkHostPlatform64Bit))); } From 743f263bdeb4f1f05093c013c6f371f74fcbeb7c Mon Sep 17 00:00:00 2001 From: Dean Ellis Date: Mon, 19 Jul 2021 14:06:38 +0100 Subject: [PATCH 117/308] [Microsoft.Android.Build.BaseTasks] add StrongName (#127) * Bump to LibZipSharp 2.0.0-alpha8 * Fix weird error about LibZipSharp, by multi-targeting --- .../MSBuildReferences.projitems | 4 ++-- .../Microsoft.Android.Build.BaseTasks.csproj | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems index 7d7301294cd..951c79b045a 100644 --- a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems +++ b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems @@ -5,7 +5,7 @@ 16.10.0 - 2.0.0-alpha7 + 2.0.0-alpha8 7.0.0-alpha8.21302.6 @@ -15,7 +15,7 @@ - + diff --git a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Microsoft.Android.Build.BaseTasks.csproj b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Microsoft.Android.Build.BaseTasks.csproj index 1542aadbd91..0a431c94fa0 100644 --- a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Microsoft.Android.Build.BaseTasks.csproj +++ b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Microsoft.Android.Build.BaseTasks.csproj @@ -4,11 +4,13 @@ - netstandard2.0 + netstandard2.0;netcoreapp3.1 Microsoft.Android.Build.Tasks true true true + true + ..\..\product.snk From b2791fae20275d473b411081d69ab904c8945dd5 Mon Sep 17 00:00:00 2001 From: Dean Ellis Date: Tue, 20 Jul 2021 09:28:44 +0100 Subject: [PATCH 118/308] Bump LibZipSharp and Mono.Unix to latest stable versions, --- .../MSBuildReferences.projitems | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems index 951c79b045a..b4bcaec8f67 100644 --- a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems +++ b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems @@ -5,8 +5,8 @@ 16.10.0 - 2.0.0-alpha8 - 7.0.0-alpha8.21302.6 + 2.0.0 + 7.0.0-final.1.21369.2 From 7ee5960b5746b390b83163285ecd67323a5e2dcf Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Thu, 29 Jul 2021 09:24:24 -0500 Subject: [PATCH 119/308] [BaseTasks] improve Task settings in AsyncTaskExtensions (#129) * [BaseTasks] improve Task settings in AsyncTaskExtensions Context: https://devblogs.microsoft.com/premier-developer/limiting-concurrency-for-faster-and-more-responsive-apps/ A work item came in from VS where AOT compilation is causing responsiveness issues. The culprit came down to some of the `TaskScheduler` and `TaskCreationOptions` settings in `AsyncTaskExtensions`. @davkean's recommendations are: > This code needs to do two things: > 1) It needs to pass `TaskCreationOptions.LongRunning`, so that we > don't burn queues meant for short lived tasks. > 2) It needs to limit how many tasks it starts at once, to avoid CPU > contention and reduce the number of threads that we end up > burning. Looking at `AsyncTaskExtensions` usage, it would happen during regular builds as well, because aapt2-related tasks use these. To improve these, I'm changing: * New overloads to pass in `int maxConcurrencyLevel` and `TaskCreationOptions`. * These default to `Environment.ProcessorCount * 2` and `LongRunning`. When this change lands, we should potentially pass in `$(Aapt2DaemonMaxInstanceCount)` where appropriate for `maxConcurrencyLevel`. I wrote a few tests to just check general sanity of `AsyncTaskExtensions`. We didn't have any. * Update src/Microsoft.Android.Build.BaseTasks/AsyncTaskExtensions.cs --- .../AsyncTaskExtensions.cs | 85 ++++++++++++++----- .../AsyncTaskExtensionsTests.cs | 68 +++++++++++++++ 2 files changed, 134 insertions(+), 19 deletions(-) create mode 100644 external/xamarin-android-tools/tests/Microsoft.Android.Build.BaseTasks-Tests/AsyncTaskExtensionsTests.cs diff --git a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/AsyncTaskExtensions.cs b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/AsyncTaskExtensions.cs index 3911cc36890..fce4bc64e4d 100644 --- a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/AsyncTaskExtensions.cs +++ b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/AsyncTaskExtensions.cs @@ -1,4 +1,4 @@ -// https://github.com/xamarin/xamarin-android/blob/83854738b8e01747f9536f426fe17ad784cc2081/src/Xamarin.Android.Build.Tasks/Utilities/AsyncTaskExtensions.cs +// https://github.com/xamarin/xamarin-android/blob/83854738b8e01747f9536f426fe17ad784cc2081/src/Xamarin.Android.Build.Tasks/Utilities/AsyncTaskExtensions.cs using System; using System.Collections.Generic; @@ -12,17 +12,24 @@ public static class AsyncTaskExtensions /// /// Creates a collection of Task with proper CancellationToken and error handling and waits via Task.WhenAll /// - public static Task WhenAll(this AsyncTask asyncTask, IEnumerable source, Action body) + public static Task WhenAll (this AsyncTask asyncTask, IEnumerable source, Action body) => + asyncTask.WhenAll (source, body, maxConcurrencyLevel: DefaultMaxConcurrencyLevel); + + /// + /// Creates a collection of Task with proper CancellationToken and error handling and waits via Task.WhenAll + /// + public static Task WhenAll(this AsyncTask asyncTask, IEnumerable source, Action body, int maxConcurrencyLevel, TaskCreationOptions creationOptions = TaskCreationOptions.LongRunning) { + var scheduler = GetTaskScheduler (maxConcurrencyLevel); var tasks = new List (); foreach (var s in source) { - tasks.Add (Task.Run (() => { + tasks.Add (Task.Factory.StartNew (() => { try { body (s); } catch (Exception exc) { LogErrorAndCancel (asyncTask, exc); } - }, asyncTask.CancellationToken)); + }, asyncTask.CancellationToken, creationOptions, scheduler)); } return Task.WhenAll (tasks); } @@ -31,18 +38,26 @@ public static Task WhenAll(this AsyncTask asyncTask, IEnumerable - public static Task WhenAllWithLock (this AsyncTask asyncTask, IEnumerable source, Action body) + public static Task WhenAllWithLock (this AsyncTask asyncTask, IEnumerable source, Action body) => + asyncTask.WhenAllWithLock (source, body, maxConcurrencyLevel: DefaultMaxConcurrencyLevel); + + /// + /// Creates a collection of Task with proper CancellationToken and error handling and waits via Task.WhenAll + /// Passes an object the inner method can use for locking. The callback is of the form: (T item, object lockObject) + /// + public static Task WhenAllWithLock (this AsyncTask asyncTask, IEnumerable source, Action body, int maxConcurrencyLevel, TaskCreationOptions creationOptions = TaskCreationOptions.LongRunning) { + var scheduler = GetTaskScheduler (maxConcurrencyLevel); var lockObject = new object (); var tasks = new List (); foreach (var s in source) { - tasks.Add (Task.Run (() => { + tasks.Add (Task.Factory.StartNew (() => { try { body (s, lockObject); } catch (Exception exc) { LogErrorAndCancel (asyncTask, exc); } - }, asyncTask.CancellationToken)); + }, asyncTask.CancellationToken, creationOptions, scheduler)); } return Task.WhenAll (tasks); } @@ -50,9 +65,15 @@ public static Task WhenAllWithLock (this AsyncTask asyncTask, IEnumerab /// /// Calls Parallel.ForEach() with appropriate ParallelOptions and exception handling. /// - public static ParallelLoopResult ParallelForEach (this AsyncTask asyncTask, IEnumerable source, Action body) + public static ParallelLoopResult ParallelForEach (this AsyncTask asyncTask, IEnumerable source, Action body) => + asyncTask.ParallelForEach (source, body, maxConcurrencyLevel: DefaultMaxConcurrencyLevel); + + /// + /// Calls Parallel.ForEach() with appropriate ParallelOptions and exception handling. + /// + public static ParallelLoopResult ParallelForEach (this AsyncTask asyncTask, IEnumerable source, Action body, int maxConcurrencyLevel) { - var options = ParallelOptions (asyncTask); + var options = ParallelOptions (asyncTask, maxConcurrencyLevel); return Parallel.ForEach (source, options, s => { try { body (s); @@ -66,9 +87,16 @@ public static ParallelLoopResult ParallelForEach (this AsyncTask asyncT /// Calls Parallel.ForEach() with appropriate ParallelOptions and exception handling. /// Passes an object the inner method can use for locking. The callback is of the form: (T item, object lockObject) /// - public static ParallelLoopResult ParallelForEachWithLock (this AsyncTask asyncTask, IEnumerable source, Action body) + public static ParallelLoopResult ParallelForEachWithLock (this AsyncTask asyncTask, IEnumerable source, Action body) => + asyncTask.ParallelForEachWithLock (source, body, maxConcurrencyLevel: DefaultMaxConcurrencyLevel); + + /// + /// Calls Parallel.ForEach() with appropriate ParallelOptions and exception handling. + /// Passes an object the inner method can use for locking. The callback is of the form: (T item, object lockObject) + /// + public static ParallelLoopResult ParallelForEachWithLock (this AsyncTask asyncTask, IEnumerable source, Action body, int maxConcurrencyLevel) { - var options = ParallelOptions (asyncTask); + var options = ParallelOptions (asyncTask, maxConcurrencyLevel); var lockObject = new object (); return Parallel.ForEach (source, options, s => { try { @@ -79,11 +107,19 @@ public static ParallelLoopResult ParallelForEachWithLock (this AsyncTas }); } - static ParallelOptions ParallelOptions (AsyncTask asyncTask) => new ParallelOptions { + static ParallelOptions ParallelOptions (AsyncTask asyncTask, int maxConcurrencyLevel) => new ParallelOptions { CancellationToken = asyncTask.CancellationToken, - TaskScheduler = TaskScheduler.Default, + TaskScheduler = GetTaskScheduler (maxConcurrencyLevel), }; + static TaskScheduler GetTaskScheduler (int maxConcurrencyLevel) + { + var pair = new ConcurrentExclusiveSchedulerPair (TaskScheduler.Default, maxConcurrencyLevel); + return pair.ConcurrentScheduler; + } + + static int DefaultMaxConcurrencyLevel => Math.Max (1, Environment.ProcessorCount - 1); + static void LogErrorAndCancel (AsyncTask asyncTask, Exception exc) { asyncTask.LogCodedError ("XA0000", Properties.Resources.XA0000_Exception, exc); @@ -91,16 +127,27 @@ static void LogErrorAndCancel (AsyncTask asyncTask, Exception exc) } /// - /// Calls Task.Run() with a proper CancellationToken. + /// Calls Task.Factory.StartNew() with a proper CancellationToken, TaskScheduler, and TaskCreationOptions.LongRunning. + /// + public static Task RunTask (this AsyncTask asyncTask, Action body) => + asyncTask.RunTask (body, maxConcurrencyLevel: DefaultMaxConcurrencyLevel); + + /// + /// Calls Task.Factory.StartNew() with a proper CancellationToken /// - public static Task RunTask (this AsyncTask asyncTask, Action body) => - Task.Run (body, asyncTask.CancellationToken); + public static Task RunTask (this AsyncTask asyncTask, Action body, int maxConcurrencyLevel, TaskCreationOptions creationOptions = TaskCreationOptions.LongRunning) => + Task.Factory.StartNew (body, asyncTask.CancellationToken, creationOptions, GetTaskScheduler (maxConcurrencyLevel)); + /// + /// Calls Task.Factory.StartNew() with a proper CancellationToken, TaskScheduler, and TaskCreationOptions.LongRunning. + /// + public static Task RunTask (this AsyncTask asyncTask, Func body) => + asyncTask.RunTask (body, maxConcurrencyLevel: DefaultMaxConcurrencyLevel); /// - /// Calls Task.Run() with a proper CancellationToken. + /// Calls Task.Factory.StartNew() with a proper CancellationToken. /// - public static Task RunTask (this AsyncTask asyncTask, Func body) => - Task.Run (body, asyncTask.CancellationToken); + public static Task RunTask (this AsyncTask asyncTask, Func body, int maxConcurrencyLevel, TaskCreationOptions creationOptions = TaskCreationOptions.LongRunning) => + Task.Factory.StartNew (body, asyncTask.CancellationToken, creationOptions, GetTaskScheduler (maxConcurrencyLevel)); } } diff --git a/external/xamarin-android-tools/tests/Microsoft.Android.Build.BaseTasks-Tests/AsyncTaskExtensionsTests.cs b/external/xamarin-android-tools/tests/Microsoft.Android.Build.BaseTasks-Tests/AsyncTaskExtensionsTests.cs new file mode 100644 index 00000000000..ecfc8667162 --- /dev/null +++ b/external/xamarin-android-tools/tests/Microsoft.Android.Build.BaseTasks-Tests/AsyncTaskExtensionsTests.cs @@ -0,0 +1,68 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.Android.Build.Tasks; +using NUnit.Framework; +using Xamarin.Build; + +namespace Microsoft.Android.Build.BaseTasks.Tests +{ + [TestFixture] + public class AsyncTaskExtensionsTests + { + const int Iterations = 32; + + [Test] + public async Task RunTask () + { + bool set = false; + await new AsyncTask ().RunTask (delegate { set = true; }); // delegate { } has void return type + Assert.IsTrue (set); + } + + [Test] + public async Task RunTaskOfT () + { + bool set = false; + Assert.IsTrue (await new AsyncTask ().RunTask (() => set = true), "RunTask should return true"); + Assert.IsTrue (set); + } + + [Test] + public async Task WhenAll () + { + bool set = false; + await new AsyncTask ().WhenAll (new [] { 0 }, _ => set = true); + Assert.IsTrue (set); + } + + [Test] + public async Task WhenAllWithLock () + { + var input = new int [Iterations]; + var output = new List (); + await new AsyncTask ().WhenAllWithLock (input, (i, l) => { + lock (l) output.Add (i); + }); + Assert.AreEqual (Iterations, output.Count); + } + + [Test] + public void ParallelForEach () + { + bool set = false; + new AsyncTask ().ParallelForEach (new [] { 0 }, _ => set = true); + Assert.IsTrue (set); + } + + [Test] + public void ParallelForEachWithLock () + { + var input = new int [Iterations]; + var output = new List (); + new AsyncTask ().ParallelForEachWithLock (input, (i, l) => { + lock (l) output.Add (i); + }); + Assert.AreEqual (Iterations, output.Count); + } + } +} From 4b7c961b543ccc4b15ba2c5e4d90d8d63d49c234 Mon Sep 17 00:00:00 2001 From: Jonathan Pryor Date: Wed, 11 Aug 2021 16:59:01 -0400 Subject: [PATCH 120/308] [Xamarin.Android.Tools.AndroidSdk] More Microsoft Dist JDK Support (#130) Fixes: https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1314263 Remember `AndroidSdkInfo.DetectAndSetPreferredJavaSdkPathToLatest()` (commit 5fd3df80)? It's supposed to "detect" the latest installed "preferred" JDK installation location, and set configuration values so that subsequent `new AndroidSdkInfo()` expressions will use that JDK location, instead of some other JDK location. The "preferred" JDK was a JDK that was "known good" for Xamarin.Android use, i.e. one that Visual Studio installed. In 5fd3df80, this was the `microsoft_dist_openjdk_*` version. When we (prematurely) removed support for the `microsoft_dist_openjdk_` build in commits 5dcd2875 and 229ceddd, we updated `AndroidSdkInfo.DetectAndSetPreferredJavaSdkPathToLatest()` to instead prefer the [Microsoft OpenJDK][0] build, but this migration needed to be delayed until Visual Studio 17.0. Which means that the "current world order" is *wrong*: `AndroidSdkInfo.DetectAndSetPreferredJavaSdkPathToLatest()` tries to detect a JDK that is not currently installed by anything. Futhermore, Visual Studio for Mac will call `AndroidSdkInfo.DetectAndSetPreferredJavaSdkPathToLatest()` if no JDK is currently configured. The result is that on "clean" systems, Visual Studio for Mac can crash with a `NotSupportedException` because it's looking for a Microsoft OpenJDK installation, but only a `microsoft_dist_openjdk_` installation is available. Thus, no preferred JDK is found, and the IDE crashes. Fix this by adding a new (`internal`) `JdkInfo.GetPreferredJdkInfos()` method, which appropriately checks *both* Microsoft OpenJDK *and* `microsoft_dist_openjdk_` installation locations, and update `AndroidSdkInfo.DetectAndSetPreferredJavaSdkPathToLatest()` to now use `JdkInfo.GetPreferredJdkInfos()`. (We add the method to `JdkInfo` so that it is easier to keep `GetPreferredJdkInfos()` and `GetKnownSystemJdkInfos()` consistent with each other.) [0]: https://www.microsoft.com/openjdk --- .../Xamarin.Android.Tools.AndroidSdk/AndroidSdkInfo.cs | 2 +- .../src/Xamarin.Android.Tools.AndroidSdk/JdkInfo.cs | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidSdkInfo.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidSdkInfo.cs index feb5865c64b..b7b594db8a2 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidSdkInfo.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidSdkInfo.cs @@ -185,7 +185,7 @@ public static void DetectAndSetPreferredJavaSdkPathToLatest (Action> GetJavaProperties (string java) return props; } + // Keep ordering in sync w/ GetPreferredJdkInfos public static IEnumerable GetKnownSystemJdkInfos (Action? logger = null) { logger = logger ?? AndroidSdkInfo.DefaultConsoleLogger; @@ -300,6 +301,14 @@ public static IEnumerable GetKnownSystemJdkInfos (Action GetPreferredJdkInfos (Action logger) + { + return MicrosoftOpenJdkLocations.GetMicrosoftOpenJdks (logger) + .Concat (MicrosoftDistJdkLocations.GetMicrosoftDistJdks (logger)) + ; + } + internal static JdkInfo? TryGetJdkInfo (string path, Action logger, string locator) { JdkInfo? jdk = null; From e7508a29b2ef09d0e6ab69c49e227e585bb3ceb4 Mon Sep 17 00:00:00 2001 From: Jonathan Pryor Date: Tue, 17 Aug 2021 17:25:07 -0400 Subject: [PATCH 121/308] [Xamarin.Android.Tools.AndroidSdk] Eclipse Adoptium support (#132) Context: https://blog.adoptopenjdk.net/2021/03/transition-to-eclipse-an-update/ Context: https://projects.eclipse.org/projects/adoptium AdoptOpenJDK has moved to the Eclipse foundation, and made three name changes in the process: 1. The project is now known as Eclipse Adoptium. 2. The macOS installation directory now matches the file glob `temurin-*.jdk`, not `adoptopenjdk-*.jdk`. 3. The Windows installation directory now installs into e.g. `%ProgramFiles%\Eclipse Foundation\jdk-*`, not e.g. `%ProgramFiles%\AdoptOpenJDK\jdk-*`. Rename `AdoptOpenJdkLocations.cs` to `EclipseAdoptiumJdkLocations.cs`, and check for *both* the "legacy" AdoptOpenJDK and new Eclipse Adoptium paths. --- .../JdkInfo.cs | 3 ++- .../Jdks/AdoptOpenJdkLocations.cs | 20 ---------------- .../Jdks/EclipseAdoptiumJdkLocations.cs | 23 +++++++++++++++++++ 3 files changed, 25 insertions(+), 21 deletions(-) delete mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Jdks/AdoptOpenJdkLocations.cs create mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Jdks/EclipseAdoptiumJdkLocations.cs diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/JdkInfo.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/JdkInfo.cs index 00a0f9fdabb..19c325353ad 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/JdkInfo.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/JdkInfo.cs @@ -289,7 +289,7 @@ public static IEnumerable GetKnownSystemJdkInfos (Action GetKnownSystemJdkInfos (Action GetPreferredJdkInfos (Action logger) { return MicrosoftOpenJdkLocations.GetMicrosoftOpenJdks (logger) + .Concat (EclipseAdoptiumJdkLocations.GetEclipseAdoptiumJdks (logger)) .Concat (MicrosoftDistJdkLocations.GetMicrosoftDistJdks (logger)) ; } diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Jdks/AdoptOpenJdkLocations.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Jdks/AdoptOpenJdkLocations.cs deleted file mode 100644 index 581033da8a4..00000000000 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Jdks/AdoptOpenJdkLocations.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Text; - -namespace Xamarin.Android.Tools { - - class AdoptOpenJdkLocations : JdkLocations { - - internal static IEnumerable GetAdoptOpenJdks (Action logger) - { - return GetMacOSSystemJdks ("adoptopenjdk-*.jdk", logger) - .Concat (GetWindowsFileSystemJdks (Path.Combine ("AdoptOpenJDK", "jdk-*"), logger)) - .Concat (GetWindowsRegistryJdks (logger, @"SOFTWARE\AdoptOpenJDK\JDK", "*", @"hotspot\MSI", "Path")) - .OrderByDescending (jdk => jdk, JdkInfoVersionComparer.Default); - } - } -} diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Jdks/EclipseAdoptiumJdkLocations.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Jdks/EclipseAdoptiumJdkLocations.cs new file mode 100644 index 00000000000..d9d9b75c814 --- /dev/null +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Jdks/EclipseAdoptiumJdkLocations.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Text; + +namespace Xamarin.Android.Tools { + + class EclipseAdoptiumJdkLocations : JdkLocations { + + internal static IEnumerable GetEclipseAdoptiumJdks (Action logger) + { + return GetMacOSSystemJdks ("temurin-*.jdk", logger) + .Concat (GetMacOSSystemJdks ("adoptopenjdk-*.jdk", logger)) + .Concat (GetWindowsFileSystemJdks (Path.Combine ("AdoptOpenJDK", "jdk-*"), logger)) + .Concat (GetWindowsRegistryJdks (logger, @"SOFTWARE\AdoptOpenJDK\JDK", "*", @"hotspot\MSI", "Path")) + .Concat (GetWindowsFileSystemJdks (Path.Combine ("Eclipse Foundation", "jdk-*"), logger)) + .Concat (GetWindowsRegistryJdks (logger, @"SOFTWARE\Eclipse Foundation\JDK", "*", @"hotspot\MSI", "Path")) + .OrderByDescending (jdk => jdk, JdkInfoVersionComparer.Default); + } + } +} From 0ae5aa2dd2543307c22468bd74aaf26d1d7a2d70 Mon Sep 17 00:00:00 2001 From: Dean Ellis Date: Thu, 19 Aug 2021 11:39:30 +0100 Subject: [PATCH 122/308] [build] Use GitInfo to generate $(Version) (#131) Context: http://github.com/xamarin/xamarin-android/commit/2d81740cc1c708cd0474c9eac8936cd7abfc72e3 Context: https://github.com/xamarin/xamarin-android/issues/5749 In order to better support building solutions which reference both .NET 6 and legacy projects within Visual Studio, we have begun strong-naming all the MSBuild-related assemblies; see also 743f263b. This allows loading two different version of the "same" assembly, e.g. `Microsoft.Android.Build.BaseTasks.dll`, from two different locations within the same AppDomain/AssemblyLoadContext. However, strong-naming is only part of the solution. The other part is that, for sanity and reliability, the "two different versions of the 'same' assembly" should *also* have different assembly *versions*. If they have the same assembly version, are they truly different? Update the build system by adding a `Directory.Build.targets` and `GitInfo.txt` file so that the the [`GitInfo`][0] [NuGet Package][1] will be used to "fully" set the `$(Version)` MSBuild property, consisting of the major and minor version numbers contained within `GitInfo.txt`, along with a "computed" `$(GitSemVerPatch)` value based on the "patch version" in `GitInfo.txt`, if present, added to the number of commits which have occurred since `GitInfo.txt` changed. Additionally, set the `$(InformationalVersion)` MSBuild property so that it mirrors the behavior in [`Mono.Android.dll`][2]: [assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.1; git-rev-head:e7508a2; git-branch:main") [0]: https://github.com/devlooped/GitInfo [1]: https://www.nuget.org/packages/GitInfo/2.1.2 [2]: https://github.com/xamarin/xamarin-android/commit/b620689ddf0214b1e38d5feab9e2ea7a653614ff --- .../Directory.Build.targets | 16 ++++++++++++++++ external/xamarin-android-tools/GitInfo.txt | 1 + 2 files changed, 17 insertions(+) create mode 100644 external/xamarin-android-tools/Directory.Build.targets create mode 100644 external/xamarin-android-tools/GitInfo.txt diff --git a/external/xamarin-android-tools/Directory.Build.targets b/external/xamarin-android-tools/Directory.Build.targets new file mode 100644 index 00000000000..ec55b385ee9 --- /dev/null +++ b/external/xamarin-android-tools/Directory.Build.targets @@ -0,0 +1,16 @@ + + + + + + false + main + + + + + $(GitSemVerMajor).$(GitSemVerMinor).$(GitSemVerPatch) + $(Version); git-rev-head:$(GitCommit); git-branch:$(GitBranch) + + + diff --git a/external/xamarin-android-tools/GitInfo.txt b/external/xamarin-android-tools/GitInfo.txt new file mode 100644 index 00000000000..9f8e9b69a33 --- /dev/null +++ b/external/xamarin-android-tools/GitInfo.txt @@ -0,0 +1 @@ +1.0 \ No newline at end of file From 38230e9b0b2accce58bc556086df846f7514b525 Mon Sep 17 00:00:00 2001 From: Jonathan Pryor Date: Wed, 15 Sep 2021 15:23:42 -0400 Subject: [PATCH 123/308] [Xamarin.Android.Tools.AndroidSdk] Update SDK component for API-31 (#134) Context: https://dl-ssl.google.com/android/repository/repository2-1.xml Update the preferred Android SDK component versions to the current latest versions listed in the [Android Repository file][0]: * `$(AndroidSdkBuildToolsVersion)`/build-tools to 31.0.0 * `$(AndroidCommandLineToolsVersion)`/cmdline-tools to 5.0 * `$(AndroidSdkPlatformToolsVersion)`/platform-tools to 31.0.3 * `$(AndroidSdkPlatformVersion)`/platform to android-31 * `$(AndroidNdkVersion)`/ndk-bundle to 22.1.7171670 Note: there is an NDK r23 package in `ndk;23.0.7599858`, and xamarin-android supports NDK r23 as of [xamarin/xamarin-android@f361d997][2], but that is installed into a *versioned* directory, a'la `$ANDROID_SDK_ROOT/ndk/23.0.7599858`, which (1) may not be fully supported, and (2) *isn't* supported by the Android SDK Manager. The latest `ndk-bundle` package is 22.1.7171670. Additionally, the Android SDK `tools` component has been [deprecated since 2017-Sep][1]. Add a comment noting that `$(AndroidSdkToolsVersion)` should be considered obsolete, and that we should eventually remove it. [0]: https://dl-ssl.google.com/android/repository/repository2-1.xml [1]: https://developer.android.com/studio/releases/sdk-tools [2]: http://github.com/xamarin/xamarin-android/commit/f361d997807504a69c29163811f362da701410b6 --- .../Xamarin.Android.Tools.Versions.props | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.Versions.props b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.Versions.props index 1a304909536..2cfe8e6206a 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.Versions.props +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.Versions.props @@ -9,12 +9,14 @@ If this file is changed the submodule for androidtools should be updated, along with any other repo which references androidtools. --> - 30.0.2 - 2.1 - 30.0.4 - 26.1.1 + 31.0.0 + 5.0 + 31.0.3 - android-30 - 21.3.6528147 + android-31 + 22.1.7171670 + + + 26.1.1 \ No newline at end of file From b90d2337ccee802c081322e2b753fb8920f1ec05 Mon Sep 17 00:00:00 2001 From: Jonathan Pryor Date: Wed, 15 Sep 2021 19:17:15 -0400 Subject: [PATCH 124/308] [Xamarin.Android.Tools.AndroidSdk] Downgrade build-tools to API-30 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Context: https://developer.android.com/studio/releases/build-tools Context: https://dl-ssl.google.com/android/repository/repository2-1.xml Commit 38230e9b bumped `$(AndroidSdkBuildToolsVersion)` to 31.0.0, which seemed reasonable at the time…until we tried to actually build xamarin-android against build-tools 31.0.0, which promptly failed: COMPILETODALVIK : error : Unable to access jarfile /Users/runner/Library/Android/sdk/build-tools/31.0.0/lib/dx.jar Turns Out™ that the Android SDK Build-tools package dropped `dx.jar`: % curl -o repo.xml https://dl-ssl.google.com/android/repository/repository2-1.xml % xpath -q -e '//remotePackage[@path="build-tools;31.0.0"]/archives/archive[host-os="macosx"]/complete/url' repo.xml d32e21a8aa8492ef8b86a489f601da425842b5da.build-tools_r31-macosx.zip % curl -o x.zip https://dl-ssl.google.com/android/repository/d32e21a8aa8492ef8b86a489f601da425842b5da.build-tools_r31-macosx.zip % unzip -l x.zip | grep dx.jar # no match There's (currently) no mention of this removal at the Android [SDK Build Tools release notes][0] page, nor is there an explicit mention that `dx` is *obsolete*… Regardless, Xamarin.Android still supports the use of `dx`, and doesn't generate any warning messages about it being obsolete. Thus, in the meantime, continue to use/prefer `$(AndroidSdkBuildToolsVersion)`=30.0.3, which *does* contain `dx.jar`. [0]: https://web.archive.org/web/20210608191407/https://developer.android.com/studio/releases/build-tools --- .../Xamarin.Android.Tools.Versions.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.Versions.props b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.Versions.props index 2cfe8e6206a..592b0c6a4c3 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.Versions.props +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.Versions.props @@ -9,7 +9,7 @@ If this file is changed the submodule for androidtools should be updated, along with any other repo which references androidtools. --> - 31.0.0 + 30.0.3 5.0 31.0.3 From 9bc9119bae359826b9eb2812f739099b2670d844 Mon Sep 17 00:00:00 2001 From: Bret Johnson Date: Fri, 24 Sep 2021 11:52:21 -0400 Subject: [PATCH 125/308] [build] Import "parent directory.override.targets" (#135) Context: 0ae5aa2dd2543307c22468bd74aaf26d1d7a2d70 Context: https://github.com/xamarin/XamarinVS/pull/12550 The XamarinVS team would like to *not* use `GitInfo` to set the `$(Version)` MSBuild property (commit 0ae5aa2d), and would like to instead version *all* of their dependencies with the same version. Support this behavior by by updating `Directory.Build.targets` to import "parent directory.override.targets", a'la [xamarin/java.interop@8898bc][0] and [xamarin/java.interop@9a878f][1]. For example, if `xamarin-android-tools` is checked out into: external/xamarin-android-tools then we'll import `external/xamarin-android-tools.override.targets` from `Directory.Build.targets`. The `` of the parent-directory `.targets` file *must* be *last* within `Directory.Build.targets`, so that it can override any targets in our default `Directory.Build.targets`, e.g. the `SetVersion` target. [0]: https://github.com/xamarin/Java.Interop/commit/8898bc1402953fdf3a1e1066dd2542fc1818aadf [1]: https://github.com/xamarin/Java.Interop/commit/9a878f211c86bdd37cb71c5c461f85a0c1a1480d --- external/xamarin-android-tools/Directory.Build.targets | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/external/xamarin-android-tools/Directory.Build.targets b/external/xamarin-android-tools/Directory.Build.targets index ec55b385ee9..ac1c9840b3c 100644 --- a/external/xamarin-android-tools/Directory.Build.targets +++ b/external/xamarin-android-tools/Directory.Build.targets @@ -13,4 +13,9 @@ $(Version); git-rev-head:$(GitCommit); git-branch:$(GitBranch) + + From 53a3a88f76705d0abc0f6e924c9802a2dc977659 Mon Sep 17 00:00:00 2001 From: Jonathan Pryor Date: Mon, 27 Sep 2021 18:06:26 -0400 Subject: [PATCH 126/308] [build] Allow Assembly "vendorization" (#136) Context: 0ae5aa2dd2543307c22468bd74aaf26d1d7a2d70 Context: 9bc9119bae359826b9eb2812f739099b2670d844 Context: https://github.com/xamarin/XamarinVS/pull/12550 Changing `$(Version)` with every commit is fun and all, but doesn't solve all problems. Commit 0ae5aa2d works "nicely" for MSBuild tasks via [``][0]. It doesn't work as well for "normal" assembly references in a "normal" AppDomain context, because assemblies are [normally resolved][1] via "assembly base name", *not* the full path name including directory. Thus, given `Example.dll`: csc /out:Example.dll /r:Path/To/Xamarin.Android.Tools.AndroidSdk.dll then when `Example.dll` is loaded, it will try to load `Xamarin.Android.Tools.AndroidSdk` via a `Load-by-name` method, and will load the first `Xamarin.Android.Tools.AndroidSdk.dll` loaded into the `AppDomain`/`AssemblyLoadContext`, regardless of version. There may not be a good way to control what that assembly *is*. This causes grief with our peer IDE teams, as assembly versions are still checked, but on mismatch an exception is thrown (!). Commit 9bc9119b was an attempt to address this, but proved to be incomplete. Attempt to improve matters by introducing a "vendorization" protocol: 1. Update `Directory.Build.props` to import "parent directory.override.props", so that a "parent checkout" can easily override MSBuild properties. 2. Update the `*.csproj` files so that `$(AssemblyName)` is forced to start with `$(VendorPrefix)`, and end with `$(VendorSuffix)`. This allows a parent checkout to set the `$(VendorPrefix)` and `$(VendorSuffix)` properties, which will impact the assembly filenames of all assemblies built in xamarin-android-tools. [0]: https://docs.microsoft.com/en-us/visualstudio/msbuild/usingtask-element-msbuild?view=vs-2019 [1]: https://docs.microsoft.com/en-us/dotnet/core/dependency-loading/loading-managed --- external/xamarin-android-tools/Directory.Build.props | 8 ++++++++ .../Microsoft.Android.Build.BaseTasks.csproj | 1 + .../Xamarin.Android.Tools.AndroidSdk.csproj | 3 ++- 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/external/xamarin-android-tools/Directory.Build.props b/external/xamarin-android-tools/Directory.Build.props index 74fe6cfa3a3..d03b71f2e0d 100644 --- a/external/xamarin-android-tools/Directory.Build.props +++ b/external/xamarin-android-tools/Directory.Build.props @@ -4,6 +4,14 @@ Debug + + obj\ diff --git a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Microsoft.Android.Build.BaseTasks.csproj b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Microsoft.Android.Build.BaseTasks.csproj index 0a431c94fa0..cc25bb5978a 100644 --- a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Microsoft.Android.Build.BaseTasks.csproj +++ b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Microsoft.Android.Build.BaseTasks.csproj @@ -11,6 +11,7 @@ true true ..\..\product.snk + $(VendorPrefix)Microsoft.Android.Build.BaseTasks$(VendorSuffix) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj index 3d1fee51f95..9500ed553b3 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj @@ -14,6 +14,7 @@ Xamarin tools for interacting with the Android SDK. Copyright © Xamarin 2011-2016 Xamarin;Xamarin.Android + $(VendorPrefix)Xamarin.Android.Tools.AndroidSdk$(VendorSuffix) @@ -38,7 +39,7 @@ - + Microsoft400 From 3affb89a5b739f146f83490e8db9464e0a877469 Mon Sep 17 00:00:00 2001 From: Juan Marcelo Tondato Date: Wed, 27 Oct 2021 13:05:32 -0300 Subject: [PATCH 127/308] [Xamarin.Android.Tools.AndroidSdk] Add JdkInfo.GetSupportedJdkInfos() (#142) Context: https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1415418 Visual Studio needs to know the list of supported JDKs, so that it can appropriately compare and display them to the user as part of a "reset to defaults" experience. The list of "supported" JDKs was formerly provided by the *`internal`* `JdkInfo.GetPreferredJdkInfos()` method. Rename `JdkInfo.GetPreferredJdkInfos()` to `JdkInfo.GetSupportedJdkInfos()`, and make `public`. The rename is done to reduce ambiguity over what the "preferred" *means*, so that in public APIs it only means "whatever the user has previously selected, as per the Windows Registry/`monodroid-config.xml`/etc. --- .../Xamarin.Android.Tools.AndroidSdk/AndroidSdkInfo.cs | 2 +- .../src/Xamarin.Android.Tools.AndroidSdk/JdkInfo.cs | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidSdkInfo.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidSdkInfo.cs index b7b594db8a2..0b9cf605b93 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidSdkInfo.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidSdkInfo.cs @@ -185,7 +185,7 @@ public static void DetectAndSetPreferredJavaSdkPathToLatest (Action> GetJavaProperties (string java) return props; } - // Keep ordering in sync w/ GetPreferredJdkInfos + // Keep ordering in sync w/ GetSupportedJdkInfos public static IEnumerable GetKnownSystemJdkInfos (Action? logger = null) { logger = logger ?? AndroidSdkInfo.DefaultConsoleLogger; @@ -302,8 +302,10 @@ public static IEnumerable GetKnownSystemJdkInfos (Action GetPreferredJdkInfos (Action logger) + public static IEnumerable GetSupportedJdkInfos (Action? logger = null) { + logger = logger ?? AndroidSdkInfo.DefaultConsoleLogger; + return MicrosoftOpenJdkLocations.GetMicrosoftOpenJdks (logger) .Concat (EclipseAdoptiumJdkLocations.GetEclipseAdoptiumJdks (logger)) .Concat (MicrosoftDistJdkLocations.GetMicrosoftDistJdks (logger)) From 8eda1b15475a733bba6a85551ff3737f4a00e618 Mon Sep 17 00:00:00 2001 From: Peter Collins Date: Wed, 27 Oct 2021 15:27:10 -0400 Subject: [PATCH 128/308] [Xamarin.Android.Tools.AndroidSdk] Add API-31 to KnownVersions (#141) --- .../src/Xamarin.Android.Tools.AndroidSdk/AndroidVersions.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidVersions.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidVersions.cs index b258127d28d..8a819ecf23e 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidVersions.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidVersions.cs @@ -182,6 +182,9 @@ static bool MatchesId (AndroidVersion version, string id) new AndroidVersion (30, "11.0") { AlternateIds = new[]{ "R" }, }, + new AndroidVersion (31, "12.0") { + AlternateIds = new[]{ "S" }, + }, }; } } From 4eae2e3cb6da10d2a5414f8e2ba373440ada4e6a Mon Sep 17 00:00:00 2001 From: Jonathan Pryor Date: Thu, 4 Nov 2021 12:40:26 -0400 Subject: [PATCH 129/308] [Xamarin.Android.Tools.AndroidSdk] Parse Properties after header (#143) Fixes: https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1397171 Context: https://www.4e00.com/blog/java/2016/12/16/java-command-line-options.html We have a Watson report of an `InvalidOperationException` being thrown from `JdkInfo.GetJavaProperties()`: Xamarin.Android.Tools.AndroidSdk!Xamarin.Android.Tools.JdkInfo ... clr!IL_Throw Xamarin.Android.Tools.AndroidSdk!Xamarin.Android.Tools.JdkInfo.__c__DisplayClass46_0._GetJavaProperties_b__0 The dump provides an additional glimmer of information: the `InvalidOperationException` message text: Unknown property key for value (to execute a class)! `JdkInfo.GetJavaProperties()` only contains one `throw`, for when we think we're processing a multi-line value, but we don't have a key for the value encountered. Which brings us to [java-command-line-options.html][0], which isn't English, and isn't recent, but *does* show `java` output which contains our "offending" string of "(to execute a class)": $ java -showversion -help java version "1.8.0_66" Java(TM) SE Runtime Environment (build 1.8.0_66-b17) Java HotSpot(TM) 64-Bit Server VM (build 25.66-b17, mixed mode) Usage: java [-options] class [args...] (to execute a class) ... What appears to be happening is that `JdkInfo` is encountering a JDK for which `java -XshowSettings:properties -version` shows something resembling the above `java -showversion -help` output, and the " (to execute a class)" is treated as part of a multi-line value, which it isn't. Update `JdkInfo.GetJavaProperties()` so that we *require* that we see "Property settings:" *before* we start looking for keys & values. This should ensure that we appropriately ignore " (to execute a class)". Additionally, add a `JdkInfo` constructor overload which takes an `Action? logger` parameter, a'la `AndroidSdkInfo`, so that `JdkInfo` can provide additional "contextual" logging without requiring the use of exceptions. Turn the lack of a key for a multi- line value into a warning, instead of an exception. Finally, update `tools/ls-jdks` so that it will accept a path to a JDK to look at, and dump out the parsed properties. [0]: https://www.4e00.com/blog/java/2016/12/16/java-command-line-options.html --- .../JdkInfo.cs | 52 ++++++++++++++----- .../JdkInfoTests.cs | 2 +- .../tools/ls-jdks/App.cs | 25 +++++++++ 3 files changed, 66 insertions(+), 13 deletions(-) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/JdkInfo.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/JdkInfo.cs index 433e2ff23f8..f1ba525900f 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/JdkInfo.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/JdkInfo.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics; @@ -42,7 +42,14 @@ public string? Vendor { Lazy>> javaProperties; Lazy javaVersion; + Action logger; + public JdkInfo (string homePath) + : this (homePath, null, null) + { + } + + public JdkInfo (string homePath, string? locator = null, Action? logger = null) { if (homePath == null) throw new ArgumentNullException (nameof (homePath)); @@ -50,6 +57,8 @@ public JdkInfo (string homePath) throw new ArgumentException ("Not a directory", nameof (homePath)); HomePath = homePath; + Locator = locator; + this.logger = logger ?? AndroidSdkInfo.DefaultConsoleLogger; var binPath = Path.Combine (HomePath, "bin"); JarPath = ProcessUtils.FindExecutablesInDirectory (binPath, "jar").FirstOrDefault (); @@ -78,14 +87,15 @@ public JdkInfo (string homePath) IncludePath = new ReadOnlyCollection (includes); - javaProperties = new Lazy>> (GetJavaProperties, LazyThreadSafetyMode.ExecutionAndPublication); + javaProperties = new Lazy>> ( + () => GetJavaProperties (this.logger), + LazyThreadSafetyMode.ExecutionAndPublication); javaVersion = new Lazy (GetJavaVersion, LazyThreadSafetyMode.ExecutionAndPublication); } public JdkInfo (string homePath, string locator) - : this (homePath) + : this (homePath, locator, null) { - Locator = locator; } public override string ToString() @@ -217,9 +227,11 @@ ReadOnlyDictionary GetReleaseProperties () return new ReadOnlyDictionary(props); } - Dictionary> GetJavaProperties () + Dictionary> GetJavaProperties (Action logger) { - return GetJavaProperties (ProcessUtils.FindExecutablesInDirectory (Path.Combine (HomePath, "bin"), "java").First ()); + return GetJavaProperties ( + logger, + ProcessUtils.FindExecutablesInDirectory (Path.Combine (HomePath, "bin"), "java").First ()); } static bool AnySystemJavasInstalled () @@ -239,7 +251,7 @@ static bool AnySystemJavasInstalled () return true; } - static Dictionary> GetJavaProperties (string java) + static Dictionary> GetJavaProperties (Action logger, string java) { var javaProps = new ProcessStartInfo { FileName = java, @@ -248,19 +260,32 @@ static Dictionary> GetJavaProperties (string java) var props = new Dictionary> (); string? curKey = null; + bool foundPS = false; + var output = new StringBuilder (); if (!AnySystemJavasInstalled () && (java == "/usr/bin/java" || java == "java")) return props; + const string PropertySettings = "Property settings:"; + ProcessUtils.Exec (javaProps, (o, e) => { const string ContinuedValuePrefix = " "; const string NewValuePrefix = " "; const string NameValueDelim = " = "; + output.AppendLine (e.Data); if (string.IsNullOrEmpty (e.Data)) return; + if (e.Data.StartsWith (PropertySettings, StringComparison.Ordinal)) { + foundPS = true; + return; + } + if (!foundPS) { + return; + } if (e.Data.StartsWith (ContinuedValuePrefix, StringComparison.Ordinal)) { - if (curKey == null) - throw new InvalidOperationException ($"Unknown property key for value {e.Data}!"); + if (curKey == null) { + logger (TraceLevel.Error, $"No Java property previously seen for continued value `{e.Data}`."); + } props [curKey].Add (e.Data.Substring (ContinuedValuePrefix.Length)); return; } @@ -276,6 +301,9 @@ static Dictionary> GetJavaProperties (string java) values.Add (value); } }); + if (!foundPS) { + logger (TraceLevel.Warning, $"No Java properties found; did not find `{PropertySettings}` in `{java} -XshowSettings:properties -version` output: ```{output.ToString ()}```"); + } return props; } @@ -443,16 +471,16 @@ static IEnumerable GetLibJvmJdkPaths () // Last-ditch fallback! static IEnumerable GetPathEnvironmentJdks (Action logger) { - return GetPathEnvironmentJdkPaths () + return GetPathEnvironmentJdkPaths (logger) .Select (p => TryGetJdkInfo (p, logger, "$PATH")) .Where (jdk => jdk != null) .Select (jdk => jdk!); } - static IEnumerable GetPathEnvironmentJdkPaths () + static IEnumerable GetPathEnvironmentJdkPaths (Action logger) { foreach (var java in ProcessUtils.FindExecutablesInPath ("java")) { - var props = GetJavaProperties (java); + var props = GetJavaProperties (logger, java); if (props.TryGetValue ("java.home", out var java_homes)) { var java_home = java_homes [0]; // `java -XshowSettings:properties -version 2>&1 | grep java.home` ends with `/jre` on macOS. diff --git a/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/JdkInfoTests.cs b/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/JdkInfoTests.cs index 594c6d0efa7..9d6aa422f6b 100644 --- a/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/JdkInfoTests.cs +++ b/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/JdkInfoTests.cs @@ -79,7 +79,7 @@ internal static void CreateFauxJdk (string dir, string releaseVersion, string re string quote = OS.IsWindows ? "" : "\""; string java = - $"echo Property settings:{Environment.NewLine}" + + $"echo {quote}Property settings:{quote}{Environment.NewLine}" + $"echo {quote} java.home = {dir}{quote}{Environment.NewLine}" + $"echo {quote} java.vendor = Xamarin.Android Unit Tests{quote}{Environment.NewLine}" + $"echo {quote} java.version = {javaVersion}{quote}{Environment.NewLine}" + diff --git a/external/xamarin-android-tools/tools/ls-jdks/App.cs b/external/xamarin-android-tools/tools/ls-jdks/App.cs index 407dc4b657c..0eefbb1c580 100644 --- a/external/xamarin-android-tools/tools/ls-jdks/App.cs +++ b/external/xamarin-android-tools/tools/ls-jdks/App.cs @@ -6,9 +6,34 @@ class App { static void Main(string[] args) { + foreach (var path in args) { + PrintProperties (path); + } + if (args.Length != 0) + return; foreach (var jdk in JdkInfo.GetKnownSystemJdkInfos ()) { Console.WriteLine ($"Found JDK: {jdk.HomePath}"); Console.WriteLine ($" Locator: {jdk.Locator}"); + // Force parsing of java properties. + var keys = jdk.JavaSettingsPropertyKeys; + } + } + + static void PrintProperties (string jdkPath) + { + try { + var jdk = new JdkInfo (jdkPath, "ls-jdks"); + Console.WriteLine ($"Property settings for JDK Path: {jdk.HomePath}"); + foreach (var key in jdk.JavaSettingsPropertyKeys) { + if (!jdk.GetJavaSettingsPropertyValues (key, out var v)) { + Console.Error.WriteLine ($"ls-jdks: Could not retrieve value for key {key}."); + continue; + } + Console.WriteLine ($" {key} = {string.Join (Environment.NewLine + " ", v)}"); + } + } + catch (Exception e) { + Console.Error.WriteLine (e); } } } From 8f4f94072b7f5311352065b92c04b229acc857d1 Mon Sep 17 00:00:00 2001 From: Dean Ellis Date: Fri, 12 Nov 2021 10:25:57 +0000 Subject: [PATCH 130/308] Update MaximumCompatibleNDKMajorVersion to be 23 (#144) * Update MaximumCompatibleNDKMajorVersion to be 23 Fixes https://work.azdo.io/1433453 Users are reporting the following error when trying to use NDK 23 ``` MSB4044: The "MakeBundleNativeCodeExternal" task was not given a value for the required parameter "AndroidNdkDirectory". ``` Earlier in the logs we see this message ``` Skipping NDK in 'C:\ProgramData\Android\android-sdk dk\22.1.7171670': version 22.1.7171670 is out of the accepted range (major version must be between 16 and 21 ``` We need to update the `MaximumCompatibleNDKMajorVersion` to match our new upper limit. --- .../Sdks/AndroidSdkBase.cs | 2 +- .../AndroidSdkInfoTests.cs | 22 ++++++++++--------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkBase.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkBase.cs index c76fe9bbd56..2f03b6ed129 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkBase.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkBase.cs @@ -11,7 +11,7 @@ abstract class AndroidSdkBase { // When this changes, update the test: Xamarin.Android.Tools.Tests.AndroidSdkInfoTests.Ndk_MultipleNdkVersionsInSdk const int MinimumCompatibleNDKMajorVersion = 16; - const int MaximumCompatibleNDKMajorVersion = 21; + const int MaximumCompatibleNDKMajorVersion = 23; static readonly char[] SourcePropertiesKeyValueSplit = new char[] { '=' }; diff --git a/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/AndroidSdkInfoTests.cs b/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/AndroidSdkInfoTests.cs index d1950926c08..a3a3edea4e5 100644 --- a/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/AndroidSdkInfoTests.cs +++ b/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/AndroidSdkInfoTests.cs @@ -71,7 +71,7 @@ public void Ndk_MultipleNdkVersionsInSdk () { // Must match like-named constants in AndroidSdkBase const int MinimumCompatibleNDKMajorVersion = 16; - const int MaximumCompatibleNDKMajorVersion = 21; + const int MaximumCompatibleNDKMajorVersion = 23; CreateSdks(out string root, out string jdk, out string ndk, out string sdk); @@ -91,8 +91,10 @@ public void Ndk_MultipleNdkVersionsInSdk () "21.2.6472646", "21.3.6528147", "22.0.7026061", + "22.1.7171670", + "23.1.7779620", }; - string expectedVersion = "21.3.6528147"; + string expectedVersion = "23.1.7779620"; string expectedNdkPath = Path.Combine (sdk, "ndk", expectedVersion); try { @@ -137,7 +139,7 @@ public void Ndk_PathInSdk() File.WriteAllText(Path.Combine(ndkPath, $"ndk-stack{extension}"), ""); var info = new AndroidSdkInfo(logger, androidSdkPath: sdk, androidNdkPath: null, javaSdkPath: jdk); - + Assert.AreEqual(ndkPath, info.AndroidNdkPath, "AndroidNdkPath not found inside sdk!"); } finally @@ -308,7 +310,7 @@ public void Sdk_GetCommandLineToolsPaths () var latestToolsVersion = "latest"; var toolsVersion = "2.1"; var higherToolsVersion = "11.2"; - + void recreateCmdlineToolsDirectory () { Directory.Delete (cmdlineTools, recursive: true); Directory.CreateDirectory (cmdlineTools); @@ -324,7 +326,7 @@ void recreateCmdlineToolsDirectory () { Assert.AreEqual (toolsPaths.Count (), 1, "Incorrect number of elements"); Assert.AreEqual (toolsPaths.First (), Path.Combine (sdk, "cmdline-tools", toolsVersion), "Incorrect command line tools path"); - + // Test that cmdline-tools is preferred over tools recreateCmdlineToolsDirectory(); CreateFauxAndroidSdkToolsDirectory (sdk, createToolsDir: true, toolsVersion: latestToolsVersion, createOldToolsDir: true); @@ -347,7 +349,7 @@ void recreateCmdlineToolsDirectory () { && toolsPathsList [1].Equals (Path.Combine (sdk, "cmdline-tools", higherToolsVersion), StringComparison.Ordinal) && toolsPathsList [2].Equals (Path.Combine (sdk, "cmdline-tools", toolsVersion), StringComparison.Ordinal) && toolsPathsList [3].Equals (Path.Combine (sdk, "tools"), StringComparison.Ordinal); - + Assert.IsTrue (isOrderCorrect, "Tools order is not descending"); } finally { Directory.Delete (root, recursive: true); @@ -386,10 +388,10 @@ static void CreateFauxAndroidSdkToolsDirectory (string androidSdkDirectory, bool if (createToolsDir) { string androidSdkToolsPath = Path.Combine (androidSdkDirectory, "cmdline-tools", toolsVersion ?? "1.0"); string androidSdkToolsBinPath = Path.Combine (androidSdkToolsPath, "bin"); - + Directory.CreateDirectory (androidSdkToolsPath); Directory.CreateDirectory (androidSdkToolsBinPath); - + File.WriteAllText (Path.Combine (androidSdkToolsBinPath, IsWindows ? "lint.bat" : "lint"), ""); } @@ -399,7 +401,7 @@ static void CreateFauxAndroidSdkToolsDirectory (string androidSdkDirectory, bool Directory.CreateDirectory (androidSdkToolsPath); Directory.CreateDirectory (androidSdkToolsBinPath); - + File.WriteAllText (Path.Combine (androidSdkToolsBinPath, IsWindows ? "lint.bat" : "lint"), ""); } @@ -414,7 +416,7 @@ static void CreateFauxAndroidSdkDirectory ( ApiInfo[] apiLevels = null) { CreateFauxAndroidSdkToolsDirectory (androidSdkDirectory, createToolsDir, toolsVersion, createOldToolsDir); - + var androidSdkPlatformToolsPath = Path.Combine (androidSdkDirectory, "platform-tools"); var androidSdkPlatformsPath = Path.Combine (androidSdkDirectory, "platforms"); var androidSdkBuildToolsPath = Path.Combine (androidSdkDirectory, "build-tools", buildToolsVersion); From 63c898ea16d8f57f7dcab05f4ce0a8b725d52c14 Mon Sep 17 00:00:00 2001 From: Matt Norflus <91976437+mattnorflus@users.noreply.github.com> Date: Wed, 1 Dec 2021 14:08:24 -0500 Subject: [PATCH 131/308] Add resource dlls to MicroBuild signing. (#145) BaRS is conducting an effort to migrate signing in Xamarin pipelines off the [external Groovy pipeline][0]. The goal is to use MicroBuild to handle all signing within the build rather than hand off files for another pipeline to sign. The [Android.SDK.Manager][1] pipeline has been chosen as the first pipeline to migrate. Project files in `Android.SDK.Manager` reference the [xamarin-android-tools repo][2] as a submodule and [sign the resource dlls][3] that the `Xamarin.Android.Tools.AndroidSdk` project creates. This change will move the signing of these resource dlls to the build of `Xamarin.Android.Tools.AndroidSdk`. The added `` target enables the use of wildcards in MicroBuild. This change is not intended to impact the behavior of the `AndroidTools` build beyond the added signing. [0]: https://dev.azure.com/devdiv/DevDiv/_build?definitionId=13878&_a=summary [1]: https://dev.azure.com/devdiv/DevDiv/_build?definitionId=13502&_a=summary [2]: https://github.com/xamarin/android-sdk-installer/blob/684b7e1283706bef2e5c832a64ced0158abe5259/Xamarin.Installer.AndroidSDK.Manager/Xamarin.Installer.AndroidSDK.Manager.csproj#L154 [3]: https://dev.azure.com/devdiv/DevDiv/_build/results?buildId=5479174&view=logs&j=7082d35a-e46b-5a74-0b0f-e46ed113ca0e&t=9ebd6855-a6bc-5b9a-0813-711468e28365&l=91 --- .../Xamarin.Android.Tools.AndroidSdk.csproj | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj index 9500ed553b3..d3b5642edba 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj @@ -38,11 +38,16 @@ - - - Microsoft400 - - + + + + Microsoft400 + + + Microsoft400 + + + From ea476057f0ac7a06be2993632dc597dae0fac84f Mon Sep 17 00:00:00 2001 From: Jonathan Pryor Date: Thu, 16 Dec 2021 16:52:31 -0500 Subject: [PATCH 132/308] [build] Add d17-* as a branch trigger MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Needed to actually *build* the d17-0 branch… --- external/xamarin-android-tools/azure-pipelines.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/external/xamarin-android-tools/azure-pipelines.yaml b/external/xamarin-android-tools/azure-pipelines.yaml index 9259859c20d..fbb967f98f9 100644 --- a/external/xamarin-android-tools/azure-pipelines.yaml +++ b/external/xamarin-android-tools/azure-pipelines.yaml @@ -3,10 +3,12 @@ name: Xamarin.Android.Tools $(Rev:r) trigger: - main - d16-* + - d17-* pr: - main - d16-* + - d17-* # Global variables variables: From c7430c8899efac08302316f362e835e785655ef6 Mon Sep 17 00:00:00 2001 From: Dean Ellis Date: Mon, 24 Jan 2022 14:58:03 +0000 Subject: [PATCH 133/308] Bump LibZipSharp to 2.0.2 (#149) Changes - Use a different license tag for nuget generation - Fix corrupt data when Deleting entries from zip files. https://github.com/xamarin/LibZipSharp/compare/2.0.0...2.0.2 --- .../MSBuildReferences.projitems | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems index b4bcaec8f67..54422ea5487 100644 --- a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems +++ b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems @@ -5,7 +5,7 @@ 16.10.0 - 2.0.0 + 2.0.2 7.0.0-final.1.21369.2 From b43f65128d307e96f970adc8f29f2bc7fd8886cd Mon Sep 17 00:00:00 2001 From: Dean Ellis Date: Mon, 24 Jan 2022 15:42:29 +0000 Subject: [PATCH 134/308] Fix Typo in commit c7430c88 (#150) We should have used `==` not `!=` when doing the `LibZipSharpVersion` check. --- .../MSBuildReferences.projitems | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems index 54422ea5487..a5064c439b7 100644 --- a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems +++ b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems @@ -5,7 +5,7 @@ 16.10.0 - 2.0.2 + 2.0.2 7.0.0-final.1.21369.2 From 3d02e07f2cc693a354819e07c163bbb9cefb0b52 Mon Sep 17 00:00:00 2001 From: Dean Ellis Date: Wed, 26 Jan 2022 09:43:06 +0000 Subject: [PATCH 135/308] Bump LibZipSharp to 2.0.3 (#151) Bump LibZipSharp to 2.0.3 Context: https://github.com/xamarin/LibZipSharp/pull/104 Changes - Don't rewind the temporary stream on write to the main one https://github.com/xamarin/LibZipSharp/compare/2.0.2...2.0.3 --- .../MSBuildReferences.projitems | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems index a5064c439b7..c21a225a6d5 100644 --- a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems +++ b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems @@ -5,7 +5,7 @@ 16.10.0 - 2.0.2 + 2.0.3 7.0.0-final.1.21369.2 From 70d1830e13c06cfc4c4786ec20ac073005f7c761 Mon Sep 17 00:00:00 2001 From: Matt Norflus Date: Wed, 26 Jan 2022 12:27:20 -0500 Subject: [PATCH 136/308] Adding condition to GetFilesToSign to only include files if build configuration is Release --- .../Xamarin.Android.Tools.AndroidSdk.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj index d3b5642edba..74602958c89 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj @@ -38,7 +38,7 @@ - + Microsoft400 From b1e982577895dcde9073c7d32437ed851d29a991 Mon Sep 17 00:00:00 2001 From: Peter Collins Date: Thu, 10 Feb 2022 17:31:15 -0500 Subject: [PATCH 137/308] [ci] Mention new NuGet feed and release (#153) Context: https://github.com/xamarin/provisionator/pull/447 A [Release Pipeline][0] has been created to push new versions of `Xamarin.Android.Tools.AndroidSdk.nupkg` for projects that depend on it. [0]: https://dev.azure.com/xamarin/public/_release?_a=releases&view=mine&definitionId=12 --- external/xamarin-android-tools/README.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/external/xamarin-android-tools/README.md b/external/xamarin-android-tools/README.md index 22b57ca45b7..92720c90b99 100644 --- a/external/xamarin-android-tools/README.md +++ b/external/xamarin-android-tools/README.md @@ -87,20 +87,19 @@ to clarify expected behavior in our community. For more information, see the Xamarin.Android.Tools.AndroidSdk nupkg files are produced for every build which occurrs on [Azure Devops](https://dev.azure.com/xamarin/Xamarin/_build?definitionId=2&_a=summary). To download one of these packages, navigate to the build you are interested in and click on the `Artifacts` button. -Alternatively, "unofficial" releases are currently hosted on MyGet. +Alternatively, "unofficial" releases are currently hosted on the [Xamarin.Android](https://dev.azure.com/xamarin/public/_packaging?_a=feed&feed=Xamarin.Android) feed. Add the feed to your project's `NuGet.config` to reference these packages: ```xml - + ``` +An Azure Pipelines [Release ](https://dev.azure.com/xamarin/public/_release?view=mine&_a=releases&definitionId=12) can be manually triggered to push a new version to this feed. + # Mailing Lists To discuss this project, and participate in the design, we use the From d78d3f61305d0269c6c0a89cd5dfec4b0ef15f18 Mon Sep 17 00:00:00 2001 From: Jonathan Pryor Date: Fri, 18 Feb 2022 12:13:55 -0500 Subject: [PATCH 138/308] [Xamarin.Android.Tools.AndroidSdk] Update SDK component for API-32 (#156) Context: https://dl-ssl.google.com/android/repository/repository2-3.xml Context: https://github.com/xamarin/xamarin-android/commit/e4b3adb389f54bc3d23b38a260093ec3fb53d88e Update the preferred Android SDK component versions to the current latest versions listed in the [Android Repository file][0]: * `$(AndroidSdkPlatformToolsVersion)`/platform-tools to 32.0.0 * `$(AndroidSdkPlatformVersion)`/platform to android-32 * `$(AndroidNdkVersion)`/NDK to 23.1.7779620 `$(AndroidSdkBuildToolsVersion)` is left at 30.0.3, even though 32.0.0 exists, so that `dx` can continue to be used, and because the default Android SDK packages within Visual Studio only includes 30.0.3. (The package list needs to be updated.) [0]: https://dl-ssl.google.com/android/repository/repository2-3.xml --- .../Xamarin.Android.Tools.Versions.props | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.Versions.props b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.Versions.props index 592b0c6a4c3..6489e291a2b 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.Versions.props +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.Versions.props @@ -11,10 +11,10 @@ --> 30.0.3 5.0 - 31.0.3 + 32.0.0 - android-31 - 22.1.7171670 + android-32 + 23.1.7779620 26.1.1 From 1328f8dd3500c37dd77708ac847c24f796077ce4 Mon Sep 17 00:00:00 2001 From: Jonathan Pryor Date: Thu, 24 Feb 2022 12:26:56 -0500 Subject: [PATCH 139/308] Revert "[Xamarin.Android.Tools.AndroidSdk] Update SDK component for API-32 (#156)" This reverts commit d78d3f61305d0269c6c0a89cd5dfec4b0ef15f18. We do not yet have all of our proverbial ducks in a row to bump these Android SDK versions. Revert until we're fully ready to bump. --- .../Xamarin.Android.Tools.Versions.props | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.Versions.props b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.Versions.props index 6489e291a2b..592b0c6a4c3 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.Versions.props +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.Versions.props @@ -11,10 +11,10 @@ --> 30.0.3 5.0 - 32.0.0 + 31.0.3 - android-32 - 23.1.7779620 + android-31 + 22.1.7171670 26.1.1 From f8203568535b4906aa6cedcc8b15c9c5ff5fabd1 Mon Sep 17 00:00:00 2001 From: Jonathan Pryor Date: Fri, 25 Feb 2022 13:04:12 -0500 Subject: [PATCH 140/308] [Xamarin.Android.Tools.AndroidSdk] Attributes can be null! (#158) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Context: https://github.com/xamarin/xamarin-android-tools/pull/157 Context: b7b03d483efbbaf3b7ea2f212a243206012d6611 Commit b7b03d48 added Nullable Reference Type support to `Xamarin.Android.Tools.AndroidSdk.dll`. Unfortunately, it was largely "surface level", updating *public* API, but not all method implementations were appropriately updated. Case in point: if `$HOME/.config/xbuild/monodroid-config.xml` contains *no* value in ``, e.g. then Visual Studio for Mac may report a first-chance exception (only reported when debugging Visual Studio for Mac, as the exception is caught internally): {System.ArgumentNullException: Value cannot be null. (Parameter 'homePath') at Xamarin.Android.Tools.JdkInfo..ctor(String homePath, String locator, Action`2 logger)} at Xamarin.Android.Tools.AndroidSdk.Ide.dll!Xamarin.Android.Tools.JdkInfo.JdkInfo Line 55 at Xamarin.Android.Tools.AndroidSdk.Ide.dll!Xamarin.Android.Tools.JdkInfo.JdkInfo Line 99 at Xamarin.Android.Tools.AndroidSdk.Ide.dll!Xamarin.Android.Tools.JdkInfo.TryGetJdkInfo Line 347 at Xamarin.Android.Tools.AndroidSdk.Ide.dll!Xamarin.Android.Tools.JdkLocations.GetUnixPreferredJdks.AnonymousMethod__0 Line 14 at System.Linq.dll!System.Linq.Enumerable.SelectEnumerableIterator.MoveNext at System.Linq.dll!System.Linq.Enumerable.WhereSelectEnumerableIterator.ToArray at System.Linq.dll!System.Linq.Buffer.Buffer at System.Linq.dll!System.Linq.OrderedEnumerable.GetEnumerator at Xamarin.Android.Tools.AndroidSdk.Ide.dll!Xamarin.Android.Tools.JdkLocations.GetPreferredJdks Line 19 at System.Linq.dll!System.Linq.Enumerable.ConcatIterator.MoveNext at System.Linq.dll!System.Linq.Enumerable.SelectEnumerableIterator.MoveNext at Xamarin.Android.Tools.AndroidSdk.Ide.dll!Xamarin.Android.Tools.AndroidSdkBase.GetValidPath Line 112 at Xamarin.Android.Tools.AndroidSdk.Ide.dll!Xamarin.Android.Tools.AndroidSdkBase.Initialize Line 69 at Xamarin.Android.Tools.AndroidSdk.Ide.dll!Xamarin.Android.Tools.AndroidSdkInfo.AndroidSdkInfo at Xamarin.AndroidTools.Ide.dll!Xamarin.AndroidTools.AndroidSdk.Refresh Line 53 … A major reason to adopt Nullable Reference Types is to *prevent* the occurrence of `NullReferenceException`s, so what went wrong? What went wrong with b7b03d48 is that when XML attributes don't exist, [`XElement.Attribute()`][0] will return `null`, and most of our `XElement.Attribute()` invocations cast the `XAttribute` return value to `string`, "asserting" that a *non-`null`* is returned. Review the codebase for all `XElement.Attribute()` invocations, and update all casts from `(string)` to instead cast to `(string?)`. This ensures that we don't circumvent the C# compilers Nullable Reference Type checks, catches the circumvention which was present in `JdkLocations.GetUnixConfiguredJdkPaths()`, and thus avoids the first-chance exception that VSMac could see. [0]: https://docs.microsoft.com/en-us/dotnet/api/system.xml.linq.xelement.attribute?view=net-6.0 Co-Authored by: @KirillOsenkov --- .../AndroidAppManifest.cs | 43 +++++++++++-------- .../JdkInfo.cs | 1 + .../Jdks/JdkLocations.MacOS.cs | 6 ++- .../Sdks/AndroidSdkUnix.cs | 6 +-- 4 files changed, 32 insertions(+), 24 deletions(-) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidAppManifest.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidAppManifest.cs index 5a9e5a75f4f..952834d26ea 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidAppManifest.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidAppManifest.cs @@ -136,37 +136,37 @@ public void WriteToFile (string fileName) } public string? PackageName { - get { return (string) manifest.Attribute ("package"); } + get { return (string?) manifest.Attribute ("package"); } set { manifest.SetAttributeValue ("package", NullIfEmpty (value)); } } public string? ApplicationLabel { - get { return (string) application.Attribute (aNS + "label"); } + get { return (string?) application.Attribute (aNS + "label"); } set { application.SetAttributeValue (aNS + "label", NullIfEmpty (value)); } } public string? ApplicationIcon { - get { return (string) application.Attribute (aNS + "icon"); } + get { return (string?) application.Attribute (aNS + "icon"); } set { application.SetAttributeValue (aNS + "icon", NullIfEmpty (value)); } } public string? ApplicationTheme { - get { return (string) application.Attribute (aNS + "theme"); } + get { return (string?) application.Attribute (aNS + "theme"); } set { application.SetAttributeValue (aNS + "theme", NullIfEmpty (value)); } } public string? VersionName { - get { return (string) manifest.Attribute (aNS + "versionName"); } + get { return (string?) manifest.Attribute (aNS + "versionName"); } set { manifest.SetAttributeValue (aNS + "versionName", NullIfEmpty (value)); } } public string? VersionCode { - get { return (string) manifest.Attribute (aNS + "versionCode"); } + get { return (string?) manifest.Attribute (aNS + "versionCode"); } set { manifest.SetAttributeValue (aNS + "versionCode", NullIfEmpty (value)); } } public string? InstallLocation { - get { return (string) manifest.Attribute (aNS + "installLocation"); } + get { return (string?) manifest.Attribute (aNS + "installLocation"); } set { manifest.SetAttributeValue (aNS + "installLocation", NullIfEmpty (value)); } } @@ -182,8 +182,8 @@ public int? TargetSdkVersion { int? ParseSdkVersion (XAttribute attribute) { - var version = (string)attribute; - if (string.IsNullOrEmpty (version)) + var version = (string?) attribute; + if (version == null || string.IsNullOrEmpty (version)) return null; int vn; if (!int.TryParse (version, out vn)) { @@ -198,7 +198,7 @@ public int? TargetSdkVersion { public IEnumerable AndroidPermissions { get { foreach (var el in manifest.Elements ("uses-permission")) { - var name = (string) el.Attribute (aName); + var name = (string?) el.Attribute (aName); if (name == null) continue; var lastDot = name.LastIndexOf ('.'); @@ -211,7 +211,7 @@ public IEnumerable AndroidPermissions { public IEnumerable AndroidPermissionsQualified { get { foreach (var el in manifest.Elements ("uses-permission")) { - var name = (string) el.Attribute (aName); + var name = (string?) el.Attribute (aName); if (name != null) yield return name; } @@ -267,7 +267,11 @@ void RemoveAndroidPermissions (IEnumerable permissions) { var perms = new HashSet (permissions); var list = manifest.Elements ("uses-permission") - .Where (el => perms.Contains ((string)el.Attribute (aName))).ToList (); + .Where (el => { + var name = (string?) el.Attribute (aName); + return name != null && perms.Contains (name); + }) + .ToList (); foreach (var el in list) el.Remove (); } @@ -284,7 +288,7 @@ void RemoveAndroidPermissions (IEnumerable permissions) { string? first = null; foreach (var a in GetLaunchableActivities ()) { - var name = (string) a.Attribute (aName); + var name = (string?) a.Attribute (aName); //prefer the fastdev launcher, it's quicker if (name == "mono.android.__FastDevLauncher") { return name; @@ -303,7 +307,7 @@ void RemoveAndroidPermissions (IEnumerable permissions) public string? GetLaunchableUserActivityName () { return GetLaunchableActivities () - .Select (a => (string) a.Attribute (aName)) + .Select (a => (string?) a.Attribute (aName)) .FirstOrDefault (name => !string.IsNullOrEmpty (name) && name != "mono.android.__FastDevLauncher"); } @@ -313,7 +317,7 @@ IEnumerable GetLaunchableActivities () var filter = activity.Element ("intent-filter"); if (filter != null) { foreach (var category in filter.Elements ("category")) - if (category != null && (string)category.Attribute (aName) == "android.intent.category.LAUNCHER") + if (category != null && (string?)category.Attribute (aName) == "android.intent.category.LAUNCHER") yield return activity; } } @@ -322,8 +326,8 @@ IEnumerable GetLaunchableActivities () public IEnumerable GetAllActivityNames () { foreach (var activity in application.Elements ("activity")) { - var activityName = (string) activity.Attribute (aName); - if (activityName != "mono.android.__FastDevLauncher") + var activityName = (string?) activity.Attribute (aName); + if (activityName != null && activityName != "mono.android.__FastDevLauncher") yield return activityName; } } @@ -331,8 +335,9 @@ public IEnumerable GetAllActivityNames () public IEnumerable GetLaunchableActivityNames () { return GetLaunchableActivities () - .Select (a => (string) a.Attribute (aName)) - .Where (name => !string.IsNullOrEmpty (name) && name != "mono.android.__FastDevLauncher"); + .Select (a => (string?) a.Attribute (aName)) + .Where (name => !string.IsNullOrEmpty (name) && name != "mono.android.__FastDevLauncher") + .Select (name => name!); } } } diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/JdkInfo.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/JdkInfo.cs index f1ba525900f..a6c8666092e 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/JdkInfo.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/JdkInfo.cs @@ -285,6 +285,7 @@ static Dictionary> GetJavaProperties (Action GetUnixConfiguredJdkPaths (Action { var config = AndroidSdkUnix.GetUnixConfigFile (logger); foreach (var java_sdk in config.Root.Elements ("java-sdk")) { - var path = (string) java_sdk.Attribute ("path"); - yield return path; + var path = (string?) java_sdk.Attribute ("path"); + if (path != null && !string.IsNullOrEmpty (path)) { + yield return path; + } } } diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkUnix.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkUnix.cs index 2f3b2f9cf75..e3b8abac47c 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkUnix.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkUnix.cs @@ -49,7 +49,7 @@ public override string? PreferedAndroidSdkPath { var androidEl = config_file.Root.Element ("android-sdk"); if (androidEl != null) { - var path = (string)androidEl.Attribute ("path"); + var path = (string?)androidEl.Attribute ("path"); if (ValidateAndroidSdkLocation (path)) return path; @@ -64,7 +64,7 @@ public override string? PreferedAndroidNdkPath { var androidEl = config_file.Root.Element ("android-ndk"); if (androidEl != null) { - var path = (string)androidEl.Attribute ("path"); + var path = (string?)androidEl.Attribute ("path"); if (ValidateAndroidNdkLocation (path)) return path; @@ -79,7 +79,7 @@ public override string? PreferedJavaSdkPath { var javaEl = config_file.Root.Element ("java-sdk"); if (javaEl != null) { - var path = (string)javaEl.Attribute ("path"); + var path = (string?)javaEl.Attribute ("path"); if (ValidateJavaSdkLocation (path)) return path; From 4797850344ec7ec3af1d41345a37130fe7f77ed3 Mon Sep 17 00:00:00 2001 From: "Michael Cummings (MSFT)" Date: Tue, 15 Mar 2022 15:56:32 -0400 Subject: [PATCH 141/308] Bump to Microsoft.VisualStudioEng.MicroBuild.Core 1.0.0 (#159) Context: https://www.nuget.org/packages/Microsoft.VisualStudioEng.MicroBuild.Core/1.0.0 --- .../Xamarin.Android.Tools.AndroidSdk.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj index 74602958c89..0f35c23c572 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj @@ -31,7 +31,7 @@ - + all runtime; build; native; contentfiles; analyzers From a2fcb1e8d570192b65d08ef33131b992a6b2dc48 Mon Sep 17 00:00:00 2001 From: James Parsons Date: Mon, 21 Mar 2022 14:02:15 -0700 Subject: [PATCH 142/308] Add support for writing android:roundIcon to Android manifest (#162) Context: https://github.com/xamarin/XamarinVS/pull/12895 This change is necessary for enabling adaptive icon support in the Android Manifest property page. It introduces a new attribute `android:roundIcon`. Co-authored-by: Jonathan Peppers --- .../Xamarin.Android.Tools.AndroidSdk/AndroidAppManifest.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidAppManifest.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidAppManifest.cs index 952834d26ea..a8024c443bf 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidAppManifest.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidAppManifest.cs @@ -150,6 +150,11 @@ public string? ApplicationIcon { set { application.SetAttributeValue (aNS + "icon", NullIfEmpty (value)); } } + public string? ApplicationRoundIcon { + get { return (string?) application.Attribute (aNS + "roundIcon"); } + set { application.SetAttributeValue (aNS + "roundIcon", NullIfEmpty (value)); } + } + public string? ApplicationTheme { get { return (string?) application.Attribute (aNS + "theme"); } set { application.SetAttributeValue (aNS + "theme", NullIfEmpty (value)); } From 278d52503c5be0f0ec78dd08bf55822c45dc9437 Mon Sep 17 00:00:00 2001 From: Dean Ellis Date: Tue, 3 May 2022 16:57:20 +0100 Subject: [PATCH 143/308] Bump LibZipSharp to 2.0.4 (#166) Changes - Add a new more complex unit test - [ci] Use new EO compliant build pools - Bump vcpkg to latest version - Use RuntimeInformation to determine the Processor architecture https://github.com/xamarin/LibZipSharp/compare/2.0.3...2.0.4 --- .../MSBuildReferences.projitems | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems index c21a225a6d5..50f867c6074 100644 --- a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems +++ b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems @@ -5,7 +5,7 @@ 16.10.0 - 2.0.3 + 2.0.4 7.0.0-final.1.21369.2 From 200a2863673f217aade20f5f6552deb5960ecbee Mon Sep 17 00:00:00 2001 From: Jonathan Pryor Date: Mon, 13 Jun 2022 06:15:13 -0400 Subject: [PATCH 144/308] [Xamarin.Android.Tools.AndroidSdk] AndroidSdkInfo validation locator (#170) Context: https://github.com/xamarin/xamarin-android/pull/7073 Context: https://github.com/xamarin/xamarin-android/blob/fdfc4c44ba65fcff9caf809bcf2d1f1a6837b1e3/src/Xamarin.Android.Build.Tasks/Tests/Xamarin.Android.Build.Tests/AndroidDependenciesTests.cs#L19-L50 I was trying to figure out how xamarin-android's `AndroidDependenciesTests.InstallAndroidDependenciesTest()` *passes*; the test creates an empty "SDK" directory, then builds with the `InstallAndroidDependencies` target, then builds the `Build` target, then asserts that the used SDK directory matches the "temp" directory. The cause of the confusion was twofold: 1. Two targets were run, but they both wrote to the same log file, and thus any output from the `InstallAndroidDependencies` target was *lost*, which meant 2. When reviewing the output of the `Build` target -- the *only* output for quite some time -- I started searching for "other possibilities" for why it would work, e.g. "it's not using the constructor parameter, but rather `monodroid-config.xml`", which needed to be separately investigated and discarded. The investigation is done -- the problems were that the log file needed to understand what was going wrong didn't exist, and that the `platform-tools` 32.0.0 package didn't exist in the GoogleV2 manifest, and thus `platform-tools` wasn't installed, and thus `adb` wasn't found, causing `ValidateAndroidSdkLocation()` to skip it -- but the additional contextual log information could be useful for future investigations. Expand the log messages provided by `AndroidSdkBase` & co. so that we also log "where" the `loc` parameter is coming from, via a new `locator` parameter (similar to the `locator` parameter in `JdkInfo`), and update the "file check" logic so that we log the path of the detected files. --- .../Sdks/AndroidSdkBase.cs | 50 ++++++++++++------- .../Sdks/AndroidSdkUnix.cs | 6 +-- .../Sdks/AndroidSdkWindows.cs | 2 +- 3 files changed, 37 insertions(+), 21 deletions(-) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkBase.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkBase.cs index 2f03b6ed129..04e98c6d5b8 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkBase.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkBase.cs @@ -26,7 +26,7 @@ public string[] AllAndroidSdks { var dirs = new List (); dirs.Add (AndroidSdkPath); dirs.AddRange (GetAllAvailableAndroidSdks ()); - allAndroidSdks = dirs.Where (d => ValidateAndroidSdkLocation (d)) + allAndroidSdks = dirs.Where (d => ValidateAndroidSdkLocation ("AllAndroidSdks", d)) .Select (d => d!) .Distinct () .ToArray (); @@ -102,15 +102,15 @@ public virtual void Initialize (string? androidSdkPath = null, string? androidNd NdkStack = GetExecutablePath (AndroidNdkPath, NdkStack); } - static string? GetValidPath (Func pathValidator, string? ctorParam, Func getPreferredPath, Func> getAllPaths) + static string? GetValidPath (Func pathValidator, string? ctorParam, Func getPreferredPath, Func> getAllPaths) { - if (pathValidator (ctorParam)) + if (pathValidator ("constructor param", ctorParam)) return ctorParam; ctorParam = getPreferredPath (); - if (pathValidator (ctorParam)) + if (pathValidator ("preferred path", ctorParam)) return ctorParam; foreach (var path in getAllPaths ()) { - if (pathValidator (path)) { + if (pathValidator ("all paths", path)) { return path; } } @@ -119,18 +119,18 @@ public virtual void Initialize (string? androidSdkPath = null, string? androidNd string? GetValidNdkPath (string? ctorParam) { - if (ValidateAndroidNdkLocation (ctorParam)) + if (ValidateAndroidNdkLocation ("constructor param", ctorParam)) return ctorParam; if (AndroidSdkPath != null) { string bundle = FindBestNDK (AndroidSdkPath); - if (Directory.Exists (bundle) && ValidateAndroidNdkLocation (bundle)) + if (Directory.Exists (bundle) && ValidateAndroidNdkLocation ("within Android SDK", bundle)) return bundle; } ctorParam = PreferedAndroidNdkPath; - if (ValidateAndroidNdkLocation (ctorParam)) + if (ValidateAndroidNdkLocation ("preferred path", ctorParam)) return ctorParam; foreach (var path in GetAllAvailableAndroidNdks ()) { - if (ValidateAndroidNdkLocation (path)) + if (ValidateAndroidNdkLocation ("all paths", path)) return path; } return null; @@ -255,31 +255,47 @@ IEnumerable GetJavaSdkPaths () /// /// Checks that a value is the location of an Android SDK. /// - public bool ValidateAndroidSdkLocation ([NotNullWhen (true)] string? loc) + public bool ValidateAndroidSdkLocation (string locator, [NotNullWhen (true)] string? loc) { - bool result = !string.IsNullOrEmpty (loc) && ProcessUtils.FindExecutablesInDirectory (Path.Combine (loc, "platform-tools"), Adb).Any (); - Logger (TraceLevel.Verbose, $"{nameof (ValidateAndroidSdkLocation)}: `{loc}`, result={result}"); + bool result = !string.IsNullOrEmpty (loc); + if (result) { + bool foundAdb = false; + foreach (var p in ProcessUtils.FindExecutablesInDirectory (Path.Combine (loc!, "platform-tools"), Adb)) { + Logger (TraceLevel.Verbose, $"{nameof (ValidateAndroidSdkLocation)}: for locator={locator}, path=`{loc}`, found adb `{p}`"); + foundAdb = true; + } + result = foundAdb; + } + Logger (TraceLevel.Verbose, $"{nameof (ValidateAndroidSdkLocation)}: for locator={locator}, path=`{loc}`, result={result}"); return result; } /// /// Checks that a value is the location of a Java SDK. /// - public virtual bool ValidateJavaSdkLocation ([NotNullWhen (true)] string? loc) + public virtual bool ValidateJavaSdkLocation (string locator, [NotNullWhen (true)] string? loc) { - bool result = !string.IsNullOrEmpty (loc) && ProcessUtils.FindExecutablesInDirectory (Path.Combine (loc, "bin"), JarSigner).Any (); - Logger (TraceLevel.Verbose, $"{nameof (ValidateJavaSdkLocation)}: `{loc}`, result={result}"); + bool result = !string.IsNullOrEmpty (loc); + if (result) { + bool foundSigner = false; + foreach (var p in ProcessUtils.FindExecutablesInDirectory (Path.Combine (loc!, "bin"), JarSigner)) { + Logger (TraceLevel.Verbose, $"{nameof (ValidateJavaSdkLocation)}: for locator={locator}, path=`{loc}`, found jarsigner `{p}`"); + foundSigner = true; + } + result = foundSigner; + } + Logger (TraceLevel.Verbose, $"{nameof (ValidateJavaSdkLocation)}: locator={locator}, path=`{loc}`, result={result}"); return result; } /// /// Checks that a value is the location of an Android SDK. /// - public bool ValidateAndroidNdkLocation ([NotNullWhen (true)] string? loc) + public bool ValidateAndroidNdkLocation (string locator, [NotNullWhen (true)] string? loc) { bool result = !string.IsNullOrEmpty (loc) && ProcessUtils.FindExecutablesInDirectory (loc!, NdkStack).Any (); - Logger (TraceLevel.Verbose, $"{nameof (ValidateAndroidNdkLocation)}: `{loc}`, result={result}"); + Logger (TraceLevel.Verbose, $"{nameof (ValidateAndroidNdkLocation)}: for locator={locator}, path=`{loc}`, result={result}"); return result; } diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkUnix.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkUnix.cs index e3b8abac47c..f21bc65d069 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkUnix.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkUnix.cs @@ -51,7 +51,7 @@ public override string? PreferedAndroidSdkPath { if (androidEl != null) { var path = (string?)androidEl.Attribute ("path"); - if (ValidateAndroidSdkLocation (path)) + if (ValidateAndroidSdkLocation ("preferred path", path)) return path; } return null; @@ -66,7 +66,7 @@ public override string? PreferedAndroidNdkPath { if (androidEl != null) { var path = (string?)androidEl.Attribute ("path"); - if (ValidateAndroidNdkLocation (path)) + if (ValidateAndroidNdkLocation ("preferred path", path)) return path; } return null; @@ -81,7 +81,7 @@ public override string? PreferedJavaSdkPath { if (javaEl != null) { var path = (string?)javaEl.Attribute ("path"); - if (ValidateJavaSdkLocation (path)) + if (ValidateJavaSdkLocation ("preferred path", path)) return path; } return null; diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkWindows.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkWindows.cs index c3309847d97..e1d940438e2 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkWindows.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkWindows.cs @@ -145,7 +145,7 @@ protected override IEnumerable GetAllAvailableAndroidNdks () foreach (var basePath in new string [] {xamarin_private, android_default, vs_default, vs_default32bit, vs_2017_default, cdrive_default}) if (Directory.Exists (basePath)) foreach (var dir in Directory.GetDirectories (basePath, "android-ndk-r*")) - if (ValidateAndroidNdkLocation (dir)) + if (ValidateAndroidNdkLocation ("Windows known NDK path", dir)) yield return dir; foreach (var dir in base.GetAllAvailableAndroidNdks ()) { From 933f2fb552fdba6ccfd227f4ed5bb922eb4479cc Mon Sep 17 00:00:00 2001 From: Jonathan Pryor Date: Tue, 14 Jun 2022 13:28:08 -0400 Subject: [PATCH 145/308] [Xamarin.Android.Tools.AndroidSdk] Permit NDK r24 (#171) Update `AndroidSdkBase.MaximumCompatibleNDKMajorVersion` to 24, so that NDK r24 is considered as a valid version. --- .../src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkBase.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkBase.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkBase.cs index 04e98c6d5b8..4b04a8be77c 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkBase.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkBase.cs @@ -11,7 +11,7 @@ abstract class AndroidSdkBase { // When this changes, update the test: Xamarin.Android.Tools.Tests.AndroidSdkInfoTests.Ndk_MultipleNdkVersionsInSdk const int MinimumCompatibleNDKMajorVersion = 16; - const int MaximumCompatibleNDKMajorVersion = 23; + const int MaximumCompatibleNDKMajorVersion = 24; static readonly char[] SourcePropertiesKeyValueSplit = new char[] { '=' }; From d44917eb0a2962de1f7c5c4281beaf3e77d81276 Mon Sep 17 00:00:00 2001 From: Jonathan Pryor Date: Wed, 15 Jun 2022 17:18:19 -0400 Subject: [PATCH 146/308] [Xamarin.Android.Tools.AndroidSdk] Update SDK component for API-32 (#169) Context: https://dl-ssl.google.com/android/repository/repository2-3.xml Context: https://github.com/xamarin/xamarin-android/commit/e4b3adb389f54bc3d23b38a260093ec3fb53d88e Update the preferred Android SDK component versions to the current latest versions listed in the [Android Repository file][0] which *also* pass the xamarin-android unit tests. * `$(AndroidCommandLineToolsVersion)`/cmdline-tools to 7.0 * `$(AndroidNdkVersion)`/NDK to 24.0.8215888 * `$(AndroidSdkBuildToolsVersion)`/build-tools to 32.0.0 * `$(AndroidSdkPlatformToolsVersion)`/platform-tools to 33.0.2 * `$(AndroidSdkPlatformVersion)`/platform to android-32 NDK 24.0.8215888 is the current latest non-preview version, and is currently available on [GitHub Actions Virtual Environments][1] as `$(ANDROID_NDK_LATEST_HOME)`. Finally, commit 933f2fb5 forgot to update the unit tests for NDK r24; update `AndroidSdkInfoTests.Ndk_MultipleNdkVersionsInSdk()` to ensure that NDK r24 is preferred. [0]: https://dl-ssl.google.com/android/repository/repository2-3.xml [1]: https://github.com/actions/virtual-environments/blob/9cf1ebd754807fba137c8ce9fa2140311609fdd6/images/win/Windows2022-Readme.md --- .../Xamarin.Android.Tools.Versions.props | 10 +++++----- .../AndroidSdkInfoTests.cs | 6 ++++-- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.Versions.props b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.Versions.props index 592b0c6a4c3..bf37bf0b83e 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.Versions.props +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.Versions.props @@ -9,12 +9,12 @@ If this file is changed the submodule for androidtools should be updated, along with any other repo which references androidtools. --> - 30.0.3 - 5.0 - 31.0.3 + 32.0.0 + 7.0 + 33.0.2 - android-31 - 22.1.7171670 + android-32 + 24.0.8215888 26.1.1 diff --git a/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/AndroidSdkInfoTests.cs b/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/AndroidSdkInfoTests.cs index a3a3edea4e5..047ba105c9b 100644 --- a/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/AndroidSdkInfoTests.cs +++ b/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/AndroidSdkInfoTests.cs @@ -71,7 +71,7 @@ public void Ndk_MultipleNdkVersionsInSdk () { // Must match like-named constants in AndroidSdkBase const int MinimumCompatibleNDKMajorVersion = 16; - const int MaximumCompatibleNDKMajorVersion = 23; + const int MaximumCompatibleNDKMajorVersion = 24; CreateSdks(out string root, out string jdk, out string ndk, out string sdk); @@ -93,8 +93,10 @@ public void Ndk_MultipleNdkVersionsInSdk () "22.0.7026061", "22.1.7171670", "23.1.7779620", + "24.0.8215888", + "25.0.3735928559", // 0xdeadbeef }; - string expectedVersion = "23.1.7779620"; + string expectedVersion = "24.0.8215888"; string expectedNdkPath = Path.Combine (sdk, "ndk", expectedVersion); try { From 26fa79b991d67a76db6d276a23ea87269a9c1069 Mon Sep 17 00:00:00 2001 From: Juan Marcelo Tondato Date: Thu, 23 Jun 2022 13:04:48 -0300 Subject: [PATCH 147/308] [Xamarin.Android.Tools.AndroidSdk] Add API-32 to KnownVersions --- .../src/Xamarin.Android.Tools.AndroidSdk/AndroidVersions.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidVersions.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidVersions.cs index 8a819ecf23e..3ca977ef3cf 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidVersions.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidVersions.cs @@ -185,6 +185,9 @@ static bool MatchesId (AndroidVersion version, string id) new AndroidVersion (31, "12.0") { AlternateIds = new[]{ "S" }, }, + new AndroidVersion (32, "12.1") { + AlternateIds = new[]{ "Sv2" }, + }, }; } } From cfa13e5a88444f1f3994e1f0dc63959854b84bcf Mon Sep 17 00:00:00 2001 From: Peter Collins Date: Mon, 18 Jul 2022 16:36:51 -0400 Subject: [PATCH 148/308] [Localization] Add OneLocBuild job (#175) Context: https://aka.ms/OneLocBuild A new pipeline has been added in the DevDiv project which will run the `OneLocBuild@2` task on every commit to main. This task produces files needed by the localization team. For more details on this process, see the [OneLocBuild documentation][0] Onboarding for this repo is still pending, and a workflow for the "handback" of localized files will need to be established in a future PR once onboarding is complete. The new pipeline that runs the OneLocBuild task can be found at . [0]: https://aka.ms/OneLocBuild --- .../Localize/LocProject.json | 19 ++++++++ .../Localize/onelocbuild.yaml | 46 +++++++++++++++++++ 2 files changed, 65 insertions(+) create mode 100644 external/xamarin-android-tools/Localize/LocProject.json create mode 100644 external/xamarin-android-tools/Localize/onelocbuild.yaml diff --git a/external/xamarin-android-tools/Localize/LocProject.json b/external/xamarin-android-tools/Localize/LocProject.json new file mode 100644 index 00000000000..688ef4e4de2 --- /dev/null +++ b/external/xamarin-android-tools/Localize/LocProject.json @@ -0,0 +1,19 @@ +{ + "Projects": [ + { + "LanguageSet": "VS_Main_Languages", + "LocItems": [ + { + "CopyOption": "LangIDOnName", + "SourceFile": ".\\src\\Microsoft.Android.Build.BaseTasks\\Properties\\Resources.resx", + "OutputPath": ".\\src\\Microsoft.Android.Build.BaseTasks\\Properties" + }, + { + "CopyOption": "LangIDOnName", + "SourceFile": ".\\src\\Xamarin.Android.Tools.AndroidSdk\\Properties\\Resources.resx", + "OutputPath": ".\\src\\Xamarin.Android.Tools.AndroidSdk\\Properties" + } + ] + } + ] +} diff --git a/external/xamarin-android-tools/Localize/onelocbuild.yaml b/external/xamarin-android-tools/Localize/onelocbuild.yaml new file mode 100644 index 00000000000..2bd90ca3109 --- /dev/null +++ b/external/xamarin-android-tools/Localize/onelocbuild.yaml @@ -0,0 +1,46 @@ +# xamarin-android-tools - OneLocBuild + +name: $(Build.SourceBranchName)-$(Build.SourceVersion)-$(Rev:r) + +trigger: +- main + +pr: none + +jobs: +- job: OneLocBuild + displayName: OneLocBuild + condition: and(eq(variables['System.TeamProject'], 'DevDiv'), eq(variables['Build.SourceBranch'], 'refs/heads/main')) + pool: VSEngSS-MicroBuild2022-1ES + timeoutInMinutes: 30 + variables: + - group: Xamarin-Secrets + workspace: + clean: all + steps: + - checkout: self + clean: true + + - task: OneLocBuild@2 + displayName: OneLocBuild + env: + SYSTEM_ACCESSTOKEN: $(System.AccessToken) + inputs: + locProj: Localize/LocProject.json + outDir: $(Build.StagingDirectory) + packageSourceAuth: patAuth + patVariable: $(OneLocBuild--PAT) + isCreatePrSelected: true + repoType: gitHub + gitHubPatVariable: $(github--pat--vs-mobiletools-engineering-service2) + prSourceBranchPrefix: locpr + isShouldReusePrSelected: true + isAutoCompletePrSelected: false + isUseLfLineEndingsSelected: true + + - task: PublishBuildArtifacts@1 + displayName: Publish Localization Files + inputs: + PathtoPublish: $(Build.StagingDirectory)/loc + ArtifactName: Loc + condition: succeededOrFailed() From 1c7c0084a908f4167f284e4ad6295575f72483d7 Mon Sep 17 00:00:00 2001 From: Alex Hsu Date: Tue, 19 Jul 2022 10:35:50 -0700 Subject: [PATCH 149/308] LEGO: Merge pull request 182 LEGO: Merge pull request 182 --- .../Properties/Resources.resx.lcl | 23 +++++++++++ .../Properties/Resources.resx.lcl | 38 +++++++++++++++++++ .../Properties/Resources.resx.lcl | 23 +++++++++++ .../Properties/Resources.resx.lcl | 38 +++++++++++++++++++ .../Properties/Resources.resx.lcl | 23 +++++++++++ .../Properties/Resources.resx.lcl | 38 +++++++++++++++++++ .../Properties/Resources.resx.lcl | 23 +++++++++++ .../Properties/Resources.resx.lcl | 38 +++++++++++++++++++ .../Properties/Resources.resx.lcl | 23 +++++++++++ .../Properties/Resources.resx.lcl | 38 +++++++++++++++++++ .../Properties/Resources.resx.lcl | 23 +++++++++++ .../Properties/Resources.resx.lcl | 38 +++++++++++++++++++ .../Properties/Resources.resx.lcl | 23 +++++++++++ .../Properties/Resources.resx.lcl | 38 +++++++++++++++++++ .../Properties/Resources.resx.lcl | 23 +++++++++++ .../Properties/Resources.resx.lcl | 38 +++++++++++++++++++ .../Properties/Resources.resx.lcl | 23 +++++++++++ .../Properties/Resources.resx.lcl | 38 +++++++++++++++++++ .../Properties/Resources.resx.lcl | 23 +++++++++++ .../Properties/Resources.resx.lcl | 38 +++++++++++++++++++ .../Properties/Resources.resx.lcl | 23 +++++++++++ .../Properties/Resources.resx.lcl | 38 +++++++++++++++++++ .../Properties/Resources.resx.lcl | 23 +++++++++++ .../Properties/Resources.resx.lcl | 38 +++++++++++++++++++ .../Properties/Resources.resx.lcl | 23 +++++++++++ .../Properties/Resources.resx.lcl | 38 +++++++++++++++++++ 26 files changed, 793 insertions(+) create mode 100644 external/xamarin-android-tools/Localize/loc/cs/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.resx.lcl create mode 100644 external/xamarin-android-tools/Localize/loc/cs/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.resx.lcl create mode 100644 external/xamarin-android-tools/Localize/loc/de/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.resx.lcl create mode 100644 external/xamarin-android-tools/Localize/loc/de/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.resx.lcl create mode 100644 external/xamarin-android-tools/Localize/loc/es/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.resx.lcl create mode 100644 external/xamarin-android-tools/Localize/loc/es/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.resx.lcl create mode 100644 external/xamarin-android-tools/Localize/loc/fr/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.resx.lcl create mode 100644 external/xamarin-android-tools/Localize/loc/fr/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.resx.lcl create mode 100644 external/xamarin-android-tools/Localize/loc/it/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.resx.lcl create mode 100644 external/xamarin-android-tools/Localize/loc/it/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.resx.lcl create mode 100644 external/xamarin-android-tools/Localize/loc/ja/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.resx.lcl create mode 100644 external/xamarin-android-tools/Localize/loc/ja/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.resx.lcl create mode 100644 external/xamarin-android-tools/Localize/loc/ko/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.resx.lcl create mode 100644 external/xamarin-android-tools/Localize/loc/ko/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.resx.lcl create mode 100644 external/xamarin-android-tools/Localize/loc/pl/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.resx.lcl create mode 100644 external/xamarin-android-tools/Localize/loc/pl/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.resx.lcl create mode 100644 external/xamarin-android-tools/Localize/loc/pt-BR/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.resx.lcl create mode 100644 external/xamarin-android-tools/Localize/loc/pt-BR/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.resx.lcl create mode 100644 external/xamarin-android-tools/Localize/loc/ru/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.resx.lcl create mode 100644 external/xamarin-android-tools/Localize/loc/ru/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.resx.lcl create mode 100644 external/xamarin-android-tools/Localize/loc/tr/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.resx.lcl create mode 100644 external/xamarin-android-tools/Localize/loc/tr/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.resx.lcl create mode 100644 external/xamarin-android-tools/Localize/loc/zh-Hans/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.resx.lcl create mode 100644 external/xamarin-android-tools/Localize/loc/zh-Hans/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.resx.lcl create mode 100644 external/xamarin-android-tools/Localize/loc/zh-Hant/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.resx.lcl create mode 100644 external/xamarin-android-tools/Localize/loc/zh-Hant/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.resx.lcl diff --git a/external/xamarin-android-tools/Localize/loc/cs/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.resx.lcl b/external/xamarin-android-tools/Localize/loc/cs/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.resx.lcl new file mode 100644 index 00000000000..f79508865d0 --- /dev/null +++ b/external/xamarin-android-tools/Localize/loc/cs/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.resx.lcl @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/external/xamarin-android-tools/Localize/loc/cs/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.resx.lcl b/external/xamarin-android-tools/Localize/loc/cs/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.resx.lcl new file mode 100644 index 00000000000..c81379dc0fa --- /dev/null +++ b/external/xamarin-android-tools/Localize/loc/cs/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.resx.lcl @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/external/xamarin-android-tools/Localize/loc/de/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.resx.lcl b/external/xamarin-android-tools/Localize/loc/de/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.resx.lcl new file mode 100644 index 00000000000..204b19b8628 --- /dev/null +++ b/external/xamarin-android-tools/Localize/loc/de/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.resx.lcl @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/external/xamarin-android-tools/Localize/loc/de/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.resx.lcl b/external/xamarin-android-tools/Localize/loc/de/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.resx.lcl new file mode 100644 index 00000000000..0f9a224900e --- /dev/null +++ b/external/xamarin-android-tools/Localize/loc/de/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.resx.lcl @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/external/xamarin-android-tools/Localize/loc/es/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.resx.lcl b/external/xamarin-android-tools/Localize/loc/es/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.resx.lcl new file mode 100644 index 00000000000..d2feaef149b --- /dev/null +++ b/external/xamarin-android-tools/Localize/loc/es/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.resx.lcl @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/external/xamarin-android-tools/Localize/loc/es/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.resx.lcl b/external/xamarin-android-tools/Localize/loc/es/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.resx.lcl new file mode 100644 index 00000000000..e0a61c96929 --- /dev/null +++ b/external/xamarin-android-tools/Localize/loc/es/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.resx.lcl @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/external/xamarin-android-tools/Localize/loc/fr/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.resx.lcl b/external/xamarin-android-tools/Localize/loc/fr/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.resx.lcl new file mode 100644 index 00000000000..af39917fd1e --- /dev/null +++ b/external/xamarin-android-tools/Localize/loc/fr/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.resx.lcl @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/external/xamarin-android-tools/Localize/loc/fr/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.resx.lcl b/external/xamarin-android-tools/Localize/loc/fr/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.resx.lcl new file mode 100644 index 00000000000..68ba2ee127f --- /dev/null +++ b/external/xamarin-android-tools/Localize/loc/fr/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.resx.lcl @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/external/xamarin-android-tools/Localize/loc/it/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.resx.lcl b/external/xamarin-android-tools/Localize/loc/it/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.resx.lcl new file mode 100644 index 00000000000..793f05a1610 --- /dev/null +++ b/external/xamarin-android-tools/Localize/loc/it/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.resx.lcl @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/external/xamarin-android-tools/Localize/loc/it/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.resx.lcl b/external/xamarin-android-tools/Localize/loc/it/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.resx.lcl new file mode 100644 index 00000000000..1e870a1f4a8 --- /dev/null +++ b/external/xamarin-android-tools/Localize/loc/it/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.resx.lcl @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/external/xamarin-android-tools/Localize/loc/ja/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.resx.lcl b/external/xamarin-android-tools/Localize/loc/ja/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.resx.lcl new file mode 100644 index 00000000000..576e90de776 --- /dev/null +++ b/external/xamarin-android-tools/Localize/loc/ja/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.resx.lcl @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/external/xamarin-android-tools/Localize/loc/ja/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.resx.lcl b/external/xamarin-android-tools/Localize/loc/ja/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.resx.lcl new file mode 100644 index 00000000000..85dfe14aef1 --- /dev/null +++ b/external/xamarin-android-tools/Localize/loc/ja/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.resx.lcl @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/external/xamarin-android-tools/Localize/loc/ko/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.resx.lcl b/external/xamarin-android-tools/Localize/loc/ko/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.resx.lcl new file mode 100644 index 00000000000..2b90aacadb7 --- /dev/null +++ b/external/xamarin-android-tools/Localize/loc/ko/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.resx.lcl @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/external/xamarin-android-tools/Localize/loc/ko/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.resx.lcl b/external/xamarin-android-tools/Localize/loc/ko/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.resx.lcl new file mode 100644 index 00000000000..bdd6d67d56f --- /dev/null +++ b/external/xamarin-android-tools/Localize/loc/ko/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.resx.lcl @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/external/xamarin-android-tools/Localize/loc/pl/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.resx.lcl b/external/xamarin-android-tools/Localize/loc/pl/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.resx.lcl new file mode 100644 index 00000000000..0f766aa54de --- /dev/null +++ b/external/xamarin-android-tools/Localize/loc/pl/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.resx.lcl @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/external/xamarin-android-tools/Localize/loc/pl/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.resx.lcl b/external/xamarin-android-tools/Localize/loc/pl/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.resx.lcl new file mode 100644 index 00000000000..a6ca4505ead --- /dev/null +++ b/external/xamarin-android-tools/Localize/loc/pl/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.resx.lcl @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/external/xamarin-android-tools/Localize/loc/pt-BR/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.resx.lcl b/external/xamarin-android-tools/Localize/loc/pt-BR/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.resx.lcl new file mode 100644 index 00000000000..f8fad737ea0 --- /dev/null +++ b/external/xamarin-android-tools/Localize/loc/pt-BR/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.resx.lcl @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/external/xamarin-android-tools/Localize/loc/pt-BR/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.resx.lcl b/external/xamarin-android-tools/Localize/loc/pt-BR/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.resx.lcl new file mode 100644 index 00000000000..774dc8da352 --- /dev/null +++ b/external/xamarin-android-tools/Localize/loc/pt-BR/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.resx.lcl @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/external/xamarin-android-tools/Localize/loc/ru/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.resx.lcl b/external/xamarin-android-tools/Localize/loc/ru/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.resx.lcl new file mode 100644 index 00000000000..7c99ba6d054 --- /dev/null +++ b/external/xamarin-android-tools/Localize/loc/ru/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.resx.lcl @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/external/xamarin-android-tools/Localize/loc/ru/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.resx.lcl b/external/xamarin-android-tools/Localize/loc/ru/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.resx.lcl new file mode 100644 index 00000000000..bbfd7cea33c --- /dev/null +++ b/external/xamarin-android-tools/Localize/loc/ru/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.resx.lcl @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/external/xamarin-android-tools/Localize/loc/tr/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.resx.lcl b/external/xamarin-android-tools/Localize/loc/tr/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.resx.lcl new file mode 100644 index 00000000000..9d84a2331ba --- /dev/null +++ b/external/xamarin-android-tools/Localize/loc/tr/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.resx.lcl @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/external/xamarin-android-tools/Localize/loc/tr/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.resx.lcl b/external/xamarin-android-tools/Localize/loc/tr/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.resx.lcl new file mode 100644 index 00000000000..6795b1ac27c --- /dev/null +++ b/external/xamarin-android-tools/Localize/loc/tr/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.resx.lcl @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/external/xamarin-android-tools/Localize/loc/zh-Hans/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.resx.lcl b/external/xamarin-android-tools/Localize/loc/zh-Hans/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.resx.lcl new file mode 100644 index 00000000000..843aa00a775 --- /dev/null +++ b/external/xamarin-android-tools/Localize/loc/zh-Hans/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.resx.lcl @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/external/xamarin-android-tools/Localize/loc/zh-Hans/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.resx.lcl b/external/xamarin-android-tools/Localize/loc/zh-Hans/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.resx.lcl new file mode 100644 index 00000000000..da969e6e3e6 --- /dev/null +++ b/external/xamarin-android-tools/Localize/loc/zh-Hans/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.resx.lcl @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/external/xamarin-android-tools/Localize/loc/zh-Hant/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.resx.lcl b/external/xamarin-android-tools/Localize/loc/zh-Hant/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.resx.lcl new file mode 100644 index 00000000000..e900c037f80 --- /dev/null +++ b/external/xamarin-android-tools/Localize/loc/zh-Hant/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.resx.lcl @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/external/xamarin-android-tools/Localize/loc/zh-Hant/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.resx.lcl b/external/xamarin-android-tools/Localize/loc/zh-Hant/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.resx.lcl new file mode 100644 index 00000000000..0c7bd145ffc --- /dev/null +++ b/external/xamarin-android-tools/Localize/loc/zh-Hant/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.resx.lcl @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From e0be7f9eb59b3dae1d6b956448237bd085e5615e Mon Sep 17 00:00:00 2001 From: Peter Collins Date: Tue, 19 Jul 2022 19:02:24 -0400 Subject: [PATCH 150/308] [ci] Use latest macOS and Windows images (#181) Updates the build to run on the latest macOS and Windows VM images, as the macOS-10.15 Azure Pipelines agent pool is now deprecated. --- external/xamarin-android-tools/azure-pipelines.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/external/xamarin-android-tools/azure-pipelines.yaml b/external/xamarin-android-tools/azure-pipelines.yaml index fbb967f98f9..1d40960db36 100644 --- a/external/xamarin-android-tools/azure-pipelines.yaml +++ b/external/xamarin-android-tools/azure-pipelines.yaml @@ -23,9 +23,9 @@ jobs: strategy: matrix: macOS: - vmImage: macOS-10.15 - win2019: - vmImage: windows-2019 + vmImage: macOS-12 + windows: + vmImage: windows-2022 pool: vmImage: $(vmImage) From 7ae4182eb71eb8b4e4d8ac8d8802285fcaef24d2 Mon Sep 17 00:00:00 2001 From: Peter Collins Date: Wed, 20 Jul 2022 09:43:20 -0400 Subject: [PATCH 151/308] [ci] Run OneLocBuild on a schedule (#180) The xamarin-android-tools repo now builds in both the xamarin/public and devdiv/DevDiv Azure DevOps orgs, and unfortunately CI triggers only work for [one organization at a time][0]: "However, if you create pipelines for a single repository in multiple Azure DevOps organizations, only the first organization's pipelines can be automatically triggered by GitHub commits or pull requests." Since the OneLocBuild job can't run against every commit automatically, the job has been updated to run on a daily schedule so that we can still get continuous builds. [0]: https://docs.microsoft.com/en-us/azure/devops/pipelines/repos/github?view=azure-devops&tabs=yaml#create-pipelines-in-multiple-azure-devops-organizations-and-projects --- .../xamarin-android-tools/Localize/onelocbuild.yaml | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/external/xamarin-android-tools/Localize/onelocbuild.yaml b/external/xamarin-android-tools/Localize/onelocbuild.yaml index 2bd90ca3109..d458975b39b 100644 --- a/external/xamarin-android-tools/Localize/onelocbuild.yaml +++ b/external/xamarin-android-tools/Localize/onelocbuild.yaml @@ -2,11 +2,17 @@ name: $(Build.SourceBranchName)-$(Build.SourceVersion)-$(Rev:r) -trigger: -- main +trigger: none pr: none +schedules: +- cron: "0 6 * * *" + displayName: Run daily at 6:00 UTC + branches: + include: + - main + jobs: - job: OneLocBuild displayName: OneLocBuild From ae0229eb216ce49b2470b9304e3468e674bace25 Mon Sep 17 00:00:00 2001 From: tondat Date: Mon, 25 Jul 2022 14:17:08 -0300 Subject: [PATCH 152/308] [Xamarin.Android.Tools.AndroidSdk] Add API-33 to KnownVersions --- .../src/Xamarin.Android.Tools.AndroidSdk/AndroidVersions.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidVersions.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidVersions.cs index 3ca977ef3cf..c45fd175187 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidVersions.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidVersions.cs @@ -188,6 +188,9 @@ static bool MatchesId (AndroidVersion version, string id) new AndroidVersion (32, "12.1") { AlternateIds = new[]{ "Sv2" }, }, + new AndroidVersion (33, "13.0") { + AlternateIds = new[]{ "T" }, + }, }; } } From 95adbae364c4cef2e4e5d5931227cf05d9a04c9a Mon Sep 17 00:00:00 2001 From: Juan Marcelo Tondato Date: Mon, 25 Jul 2022 16:02:18 -0300 Subject: [PATCH 153/308] [Xamarin.Android.Tools.AndroidSdk] Update SDK component for API-33 (#184) Update `$(AndroidSdkPlatformVersion)` to android-33. API-33 has been declared stable by Google, and will be the default API level for .NET 7. --- .../Xamarin.Android.Tools.Versions.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.Versions.props b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.Versions.props index bf37bf0b83e..5d58bc7fcfb 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.Versions.props +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.Versions.props @@ -13,7 +13,7 @@ 7.0 33.0.2 - android-32 + android-33 24.0.8215888 From 8c54f41357fab0d4f78969e396078a5856bf7605 Mon Sep 17 00:00:00 2001 From: Alex Hsu Date: Fri, 29 Jul 2022 10:18:55 -0700 Subject: [PATCH 154/308] Juno: check in to juno/hb_befb220e-87ce-47e9-a9e6-10ea592b2337_20220729025332507. (#185) LEGO: Pull request from juno/hb_befb220e-87ce-47e9-a9e6-10ea592b2337_20220729025332507 to main with localized lcls --- .../Properties/Resources.resx.lcl | 8 ++++---- .../Properties/Resources.resx.lcl | 17 ++++++++++------- .../Properties/Resources.resx.lcl | 8 ++++---- .../Properties/Resources.resx.lcl | 17 ++++++++++------- .../Properties/Resources.resx.lcl | 8 ++++---- .../Properties/Resources.resx.lcl | 17 ++++++++++------- .../Properties/Resources.resx.lcl | 8 ++++---- .../Properties/Resources.resx.lcl | 17 ++++++++++------- .../Properties/Resources.resx.lcl | 8 ++++---- .../Properties/Resources.resx.lcl | 17 ++++++++++------- .../Properties/Resources.resx.lcl | 8 ++++---- .../Properties/Resources.resx.lcl | 17 ++++++++++------- .../Properties/Resources.resx.lcl | 8 ++++---- .../Properties/Resources.resx.lcl | 17 ++++++++++------- .../Properties/Resources.resx.lcl | 8 ++++---- .../Properties/Resources.resx.lcl | 17 ++++++++++------- .../Properties/Resources.resx.lcl | 8 ++++---- .../Properties/Resources.resx.lcl | 17 ++++++++++------- 18 files changed, 126 insertions(+), 99 deletions(-) diff --git a/external/xamarin-android-tools/Localize/loc/de/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.resx.lcl b/external/xamarin-android-tools/Localize/loc/de/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.resx.lcl index 204b19b8628..a5826587b77 100644 --- a/external/xamarin-android-tools/Localize/loc/de/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.resx.lcl +++ b/external/xamarin-android-tools/Localize/loc/de/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.resx.lcl @@ -1,5 +1,5 @@  - + @@ -12,11 +12,11 @@ + + + - - - diff --git a/external/xamarin-android-tools/Localize/loc/de/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.resx.lcl b/external/xamarin-android-tools/Localize/loc/de/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.resx.lcl index 0f9a224900e..b67ca37e0fd 100644 --- a/external/xamarin-android-tools/Localize/loc/de/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.resx.lcl +++ b/external/xamarin-android-tools/Localize/loc/de/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.resx.lcl @@ -1,5 +1,5 @@  - + @@ -12,24 +12,27 @@ + + + - - - + + + - - - + + + diff --git a/external/xamarin-android-tools/Localize/loc/es/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.resx.lcl b/external/xamarin-android-tools/Localize/loc/es/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.resx.lcl index d2feaef149b..0c034721451 100644 --- a/external/xamarin-android-tools/Localize/loc/es/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.resx.lcl +++ b/external/xamarin-android-tools/Localize/loc/es/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.resx.lcl @@ -1,5 +1,5 @@  - + @@ -12,11 +12,11 @@ + + + - - - diff --git a/external/xamarin-android-tools/Localize/loc/es/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.resx.lcl b/external/xamarin-android-tools/Localize/loc/es/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.resx.lcl index e0a61c96929..d6f46f207a7 100644 --- a/external/xamarin-android-tools/Localize/loc/es/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.resx.lcl +++ b/external/xamarin-android-tools/Localize/loc/es/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.resx.lcl @@ -1,5 +1,5 @@  - + @@ -12,24 +12,27 @@ + + + - - - + + + - - - + + + diff --git a/external/xamarin-android-tools/Localize/loc/fr/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.resx.lcl b/external/xamarin-android-tools/Localize/loc/fr/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.resx.lcl index af39917fd1e..27a2d9f4d92 100644 --- a/external/xamarin-android-tools/Localize/loc/fr/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.resx.lcl +++ b/external/xamarin-android-tools/Localize/loc/fr/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.resx.lcl @@ -1,5 +1,5 @@  - + @@ -12,11 +12,11 @@ + + + - - - diff --git a/external/xamarin-android-tools/Localize/loc/fr/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.resx.lcl b/external/xamarin-android-tools/Localize/loc/fr/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.resx.lcl index 68ba2ee127f..bb99c91de22 100644 --- a/external/xamarin-android-tools/Localize/loc/fr/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.resx.lcl +++ b/external/xamarin-android-tools/Localize/loc/fr/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.resx.lcl @@ -1,5 +1,5 @@  - + @@ -12,24 +12,27 @@ + + + - - - + + + - - - + + + diff --git a/external/xamarin-android-tools/Localize/loc/ko/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.resx.lcl b/external/xamarin-android-tools/Localize/loc/ko/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.resx.lcl index 2b90aacadb7..66ce2f97708 100644 --- a/external/xamarin-android-tools/Localize/loc/ko/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.resx.lcl +++ b/external/xamarin-android-tools/Localize/loc/ko/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.resx.lcl @@ -1,5 +1,5 @@  - + @@ -12,11 +12,11 @@ + + + - - - diff --git a/external/xamarin-android-tools/Localize/loc/ko/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.resx.lcl b/external/xamarin-android-tools/Localize/loc/ko/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.resx.lcl index bdd6d67d56f..9122014fba2 100644 --- a/external/xamarin-android-tools/Localize/loc/ko/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.resx.lcl +++ b/external/xamarin-android-tools/Localize/loc/ko/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.resx.lcl @@ -1,5 +1,5 @@  - + @@ -12,24 +12,27 @@ + + + - - - + + + - - - + + + diff --git a/external/xamarin-android-tools/Localize/loc/pl/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.resx.lcl b/external/xamarin-android-tools/Localize/loc/pl/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.resx.lcl index 0f766aa54de..6b74fae0f63 100644 --- a/external/xamarin-android-tools/Localize/loc/pl/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.resx.lcl +++ b/external/xamarin-android-tools/Localize/loc/pl/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.resx.lcl @@ -1,5 +1,5 @@  - + @@ -12,11 +12,11 @@ + + + - - - diff --git a/external/xamarin-android-tools/Localize/loc/pl/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.resx.lcl b/external/xamarin-android-tools/Localize/loc/pl/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.resx.lcl index a6ca4505ead..6f9623d4fc6 100644 --- a/external/xamarin-android-tools/Localize/loc/pl/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.resx.lcl +++ b/external/xamarin-android-tools/Localize/loc/pl/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.resx.lcl @@ -1,5 +1,5 @@  - + @@ -12,24 +12,27 @@ + + + - - - + + + - - - + + + diff --git a/external/xamarin-android-tools/Localize/loc/pt-BR/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.resx.lcl b/external/xamarin-android-tools/Localize/loc/pt-BR/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.resx.lcl index f8fad737ea0..d1d92b68236 100644 --- a/external/xamarin-android-tools/Localize/loc/pt-BR/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.resx.lcl +++ b/external/xamarin-android-tools/Localize/loc/pt-BR/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.resx.lcl @@ -1,5 +1,5 @@  - + @@ -12,11 +12,11 @@ + + + - - - diff --git a/external/xamarin-android-tools/Localize/loc/pt-BR/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.resx.lcl b/external/xamarin-android-tools/Localize/loc/pt-BR/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.resx.lcl index 774dc8da352..450eced92ce 100644 --- a/external/xamarin-android-tools/Localize/loc/pt-BR/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.resx.lcl +++ b/external/xamarin-android-tools/Localize/loc/pt-BR/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.resx.lcl @@ -1,5 +1,5 @@  - + @@ -12,24 +12,27 @@ + + + - - - + + + - - - + + + diff --git a/external/xamarin-android-tools/Localize/loc/ru/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.resx.lcl b/external/xamarin-android-tools/Localize/loc/ru/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.resx.lcl index 7c99ba6d054..5d08dd83572 100644 --- a/external/xamarin-android-tools/Localize/loc/ru/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.resx.lcl +++ b/external/xamarin-android-tools/Localize/loc/ru/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.resx.lcl @@ -1,5 +1,5 @@  - + @@ -12,11 +12,11 @@ + + + - - - diff --git a/external/xamarin-android-tools/Localize/loc/ru/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.resx.lcl b/external/xamarin-android-tools/Localize/loc/ru/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.resx.lcl index bbfd7cea33c..ff13dd21c69 100644 --- a/external/xamarin-android-tools/Localize/loc/ru/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.resx.lcl +++ b/external/xamarin-android-tools/Localize/loc/ru/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.resx.lcl @@ -1,5 +1,5 @@  - + @@ -12,24 +12,27 @@ + + + - - - + + + - - - + + + diff --git a/external/xamarin-android-tools/Localize/loc/zh-Hans/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.resx.lcl b/external/xamarin-android-tools/Localize/loc/zh-Hans/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.resx.lcl index 843aa00a775..9de1389fddf 100644 --- a/external/xamarin-android-tools/Localize/loc/zh-Hans/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.resx.lcl +++ b/external/xamarin-android-tools/Localize/loc/zh-Hans/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.resx.lcl @@ -1,5 +1,5 @@  - + @@ -12,11 +12,11 @@ + + + - - - diff --git a/external/xamarin-android-tools/Localize/loc/zh-Hans/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.resx.lcl b/external/xamarin-android-tools/Localize/loc/zh-Hans/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.resx.lcl index da969e6e3e6..ca010195273 100644 --- a/external/xamarin-android-tools/Localize/loc/zh-Hans/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.resx.lcl +++ b/external/xamarin-android-tools/Localize/loc/zh-Hans/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.resx.lcl @@ -1,5 +1,5 @@  - + @@ -12,24 +12,27 @@ + + + - - - + + + - - - + + + diff --git a/external/xamarin-android-tools/Localize/loc/zh-Hant/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.resx.lcl b/external/xamarin-android-tools/Localize/loc/zh-Hant/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.resx.lcl index e900c037f80..c6a55b9d67c 100644 --- a/external/xamarin-android-tools/Localize/loc/zh-Hant/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.resx.lcl +++ b/external/xamarin-android-tools/Localize/loc/zh-Hant/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.resx.lcl @@ -1,5 +1,5 @@  - + @@ -12,11 +12,11 @@ + + + - - - diff --git a/external/xamarin-android-tools/Localize/loc/zh-Hant/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.resx.lcl b/external/xamarin-android-tools/Localize/loc/zh-Hant/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.resx.lcl index 0c7bd145ffc..473420a0d31 100644 --- a/external/xamarin-android-tools/Localize/loc/zh-Hant/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.resx.lcl +++ b/external/xamarin-android-tools/Localize/loc/zh-Hant/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.resx.lcl @@ -1,5 +1,5 @@  - + @@ -12,24 +12,27 @@ + + + - - - + + + - - - + + + From 434b9230447a2ccbccc78f8eb061bc2452676c9d Mon Sep 17 00:00:00 2001 From: Alex Hsu Date: Fri, 29 Jul 2022 10:19:31 -0700 Subject: [PATCH 155/308] Juno: check in to juno/hb_befb220e-87ce-47e9-a9e6-10ea592b2337_20220729154833425. (#186) LEGO: Pull request from juno/hb_befb220e-87ce-47e9-a9e6-10ea592b2337_20220729154833425 to main with localized lcls --- .../Properties/Resources.resx.lcl | 8 ++++---- .../Properties/Resources.resx.lcl | 17 ++++++++++------- .../Properties/Resources.resx.lcl | 8 ++++---- .../Properties/Resources.resx.lcl | 17 ++++++++++------- .../Properties/Resources.resx.lcl | 8 ++++---- .../Properties/Resources.resx.lcl | 17 ++++++++++------- 6 files changed, 42 insertions(+), 33 deletions(-) diff --git a/external/xamarin-android-tools/Localize/loc/cs/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.resx.lcl b/external/xamarin-android-tools/Localize/loc/cs/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.resx.lcl index f79508865d0..28b0748c7c7 100644 --- a/external/xamarin-android-tools/Localize/loc/cs/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.resx.lcl +++ b/external/xamarin-android-tools/Localize/loc/cs/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.resx.lcl @@ -1,5 +1,5 @@  - + @@ -12,11 +12,11 @@ + + + - - - diff --git a/external/xamarin-android-tools/Localize/loc/cs/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.resx.lcl b/external/xamarin-android-tools/Localize/loc/cs/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.resx.lcl index c81379dc0fa..2009686d61b 100644 --- a/external/xamarin-android-tools/Localize/loc/cs/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.resx.lcl +++ b/external/xamarin-android-tools/Localize/loc/cs/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.resx.lcl @@ -1,5 +1,5 @@  - + @@ -12,24 +12,27 @@ + + + - - - + + + - - - + + + diff --git a/external/xamarin-android-tools/Localize/loc/ja/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.resx.lcl b/external/xamarin-android-tools/Localize/loc/ja/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.resx.lcl index 576e90de776..25f613fa090 100644 --- a/external/xamarin-android-tools/Localize/loc/ja/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.resx.lcl +++ b/external/xamarin-android-tools/Localize/loc/ja/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.resx.lcl @@ -1,5 +1,5 @@  - + @@ -12,11 +12,11 @@ + + + - - - diff --git a/external/xamarin-android-tools/Localize/loc/ja/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.resx.lcl b/external/xamarin-android-tools/Localize/loc/ja/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.resx.lcl index 85dfe14aef1..36cd2b97ea8 100644 --- a/external/xamarin-android-tools/Localize/loc/ja/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.resx.lcl +++ b/external/xamarin-android-tools/Localize/loc/ja/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.resx.lcl @@ -1,5 +1,5 @@  - + @@ -12,24 +12,27 @@ + + + - - - + + + - - - + + + diff --git a/external/xamarin-android-tools/Localize/loc/tr/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.resx.lcl b/external/xamarin-android-tools/Localize/loc/tr/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.resx.lcl index 9d84a2331ba..61a1f604f90 100644 --- a/external/xamarin-android-tools/Localize/loc/tr/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.resx.lcl +++ b/external/xamarin-android-tools/Localize/loc/tr/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.resx.lcl @@ -1,5 +1,5 @@  - + @@ -12,11 +12,11 @@ + + + - - - diff --git a/external/xamarin-android-tools/Localize/loc/tr/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.resx.lcl b/external/xamarin-android-tools/Localize/loc/tr/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.resx.lcl index 6795b1ac27c..623f1e866e8 100644 --- a/external/xamarin-android-tools/Localize/loc/tr/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.resx.lcl +++ b/external/xamarin-android-tools/Localize/loc/tr/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.resx.lcl @@ -1,5 +1,5 @@  - + @@ -12,24 +12,27 @@ + + + - - - + + + - - - + + + From 950b65f748971c14999ddec0dfcf4ed28773ed47 Mon Sep 17 00:00:00 2001 From: Alex Hsu Date: Sun, 31 Jul 2022 08:48:48 -0700 Subject: [PATCH 156/308] LEGO: Merge pull request 187 LEGO: Merge pull request 187 --- .../Properties/Resources.resx.lcl | 8 ++++---- .../Properties/Resources.resx.lcl | 17 ++++++++++------- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/external/xamarin-android-tools/Localize/loc/it/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.resx.lcl b/external/xamarin-android-tools/Localize/loc/it/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.resx.lcl index 793f05a1610..575b45b2e64 100644 --- a/external/xamarin-android-tools/Localize/loc/it/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.resx.lcl +++ b/external/xamarin-android-tools/Localize/loc/it/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.resx.lcl @@ -1,5 +1,5 @@  - + @@ -12,11 +12,11 @@ + + + - - - diff --git a/external/xamarin-android-tools/Localize/loc/it/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.resx.lcl b/external/xamarin-android-tools/Localize/loc/it/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.resx.lcl index 1e870a1f4a8..79552708bb5 100644 --- a/external/xamarin-android-tools/Localize/loc/it/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.resx.lcl +++ b/external/xamarin-android-tools/Localize/loc/it/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.resx.lcl @@ -1,5 +1,5 @@  - + @@ -12,24 +12,27 @@ + + + - - - + + + - - - + + + From 88622a725da4d44388af9f8cab2af18e5e3ff118 Mon Sep 17 00:00:00 2001 From: Rodrigo Moya Date: Tue, 2 Aug 2022 17:04:51 +0200 Subject: [PATCH 157/308] Avoid `Environment.SpecialFolder.ApplicationData` (#188) * Don't use ApplicationData for config file on UNIX When running on NET6 (VSMac installer), SpecialFolder.ApplicationData returns nothing, resulting in a relative path being used for the config file. So, just switch to use UserProfile, to which we append .config for the well known, and expected, place for the config file. Example testing with Mono's `csharp` command: $ csharp Mono C# Shell, type "help;" for help Enter statements below. csharp> Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) "/Users/jopepper/.config" csharp> Environment.GetFolderPath(Environment.SpecialFolder.UserProfile) "/Users/jopepper" Co-authored-by: Jonathan Peppers --- .../Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkUnix.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkUnix.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkUnix.cs index f21bc65d069..c43b8ea1866 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkUnix.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkUnix.cs @@ -223,8 +223,7 @@ private static string UnixConfigPath { get { var p = AppDomain.CurrentDomain.GetData (GetUnixConfigDirOverrideName)?.ToString (); if (string.IsNullOrEmpty (p)) { - p = Environment.GetFolderPath (Environment.SpecialFolder.ApplicationData); - p = Path.Combine (p, "xbuild"); + p = Path.Combine (Environment.GetFolderPath (Environment.SpecialFolder.UserProfile), ".config", "xbuild"); } return Path.Combine (p, "monodroid-config.xml"); } From 2a0f35f2da7b08ef18d52d95baf3f7b8d6e6a693 Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Thu, 4 Aug 2022 12:12:10 -0500 Subject: [PATCH 158/308] [Xamarin.Android.Tools.AndroidSdk] Permit NDK r25 (#190) Context: https://github.com/xamarin/xamarin-android/issues/7233 Update `AndroidSdkBase.MaximumCompatibleNDKMajorVersion` to 25, so that NDK r25 is considered as a valid version. --- .../Sdks/AndroidSdkBase.cs | 2 +- .../AndroidSdkInfoTests.cs | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkBase.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkBase.cs index 4b04a8be77c..f00b1a1bb20 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkBase.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkBase.cs @@ -11,7 +11,7 @@ abstract class AndroidSdkBase { // When this changes, update the test: Xamarin.Android.Tools.Tests.AndroidSdkInfoTests.Ndk_MultipleNdkVersionsInSdk const int MinimumCompatibleNDKMajorVersion = 16; - const int MaximumCompatibleNDKMajorVersion = 24; + const int MaximumCompatibleNDKMajorVersion = 25; static readonly char[] SourcePropertiesKeyValueSplit = new char[] { '=' }; diff --git a/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/AndroidSdkInfoTests.cs b/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/AndroidSdkInfoTests.cs index 047ba105c9b..043e89c6369 100644 --- a/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/AndroidSdkInfoTests.cs +++ b/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/AndroidSdkInfoTests.cs @@ -71,7 +71,7 @@ public void Ndk_MultipleNdkVersionsInSdk () { // Must match like-named constants in AndroidSdkBase const int MinimumCompatibleNDKMajorVersion = 16; - const int MaximumCompatibleNDKMajorVersion = 24; + const int MaximumCompatibleNDKMajorVersion = 25; CreateSdks(out string root, out string jdk, out string ndk, out string sdk); @@ -94,9 +94,10 @@ public void Ndk_MultipleNdkVersionsInSdk () "22.1.7171670", "23.1.7779620", "24.0.8215888", - "25.0.3735928559", // 0xdeadbeef + "25.0.8775105", + "26.0.3735928559", // 0xdeadbeef }; - string expectedVersion = "24.0.8215888"; + string expectedVersion = "25.0.8775105"; string expectedNdkPath = Path.Combine (sdk, "ndk", expectedVersion); try { From 76d8e7ca22c1a990096ba44d1f2cece2a3136b50 Mon Sep 17 00:00:00 2001 From: VS MobileTools Engineering Service 2 Date: Fri, 5 Aug 2022 11:42:54 -0700 Subject: [PATCH 159/308] [Localization] Import translated resx files (#189) The xamarin-android-tools repo has been fully onboarded into the OneLoc system, and the translations for Microsoft.Android.Build.BaseTasks and Xamarin.Android.Tools.AndroidSdk are now complete. XLF files and the XliffTasks NuGet will no longer be used. They have been replaced by the OneLocBuild translation process which produces translated RESX files. Future changes to the `Resources.resx` files in this repo will trigger a new OneLocBuild handoff for translation. Shortly after that, the LCL files in the Localize folder will be automatically updated with newly translated strings. This update will cause the OneLocBuild task to open another PR which updates the RESX files used to produce localized satellite assemblies. ----- This is the pull request automatically created by the OneLocBuild task in the build process to check-in localized files generated based upon translation source files (.lcl files) handed-back from the downstream localization pipeline. If there are issues in translations, visit https://aka.ms/ceLocBug and log bugs for fixes. The OneLocBuild wiki is https://aka.ms/onelocbuild and the localization process in general is documented at https://aka.ms/AllAboutLoc. Co-authored-by: Peter Collins --- .../Microsoft.Android.Build.BaseTasks.csproj | 15 +- .../Properties/Resources.Designer.cs | 72 ++++++++++ .../Properties/Resources.cs.resx | 104 ++++++++++++++ .../Properties/Resources.de.resx | 104 ++++++++++++++ .../Properties/Resources.es.resx | 104 ++++++++++++++ .../Properties/Resources.fr.resx | 104 ++++++++++++++ .../Properties/Resources.it.resx | 104 ++++++++++++++ .../Properties/Resources.ja.resx | 104 ++++++++++++++ .../Properties/Resources.ko.resx | 104 ++++++++++++++ .../Properties/Resources.pl.resx | 104 ++++++++++++++ .../Properties/Resources.pt-BR.resx | 104 ++++++++++++++ .../Properties/Resources.ru.resx | 104 ++++++++++++++ .../Properties/Resources.tr.resx | 104 ++++++++++++++ .../Properties/Resources.zh-Hans.resx | 104 ++++++++++++++ .../Properties/Resources.zh-Hant.resx | 104 ++++++++++++++ .../Properties/xlf/Resources.cs.xlf | 12 -- .../Properties/xlf/Resources.de.xlf | 12 -- .../Properties/xlf/Resources.es.xlf | 12 -- .../Properties/xlf/Resources.fr.xlf | 12 -- .../Properties/xlf/Resources.it.xlf | 12 -- .../Properties/xlf/Resources.ja.xlf | 12 -- .../Properties/xlf/Resources.ko.xlf | 12 -- .../Properties/xlf/Resources.pl.xlf | 12 -- .../Properties/xlf/Resources.pt-BR.xlf | 12 -- .../Properties/xlf/Resources.ru.xlf | 12 -- .../Properties/xlf/Resources.tr.xlf | 12 -- .../Properties/xlf/Resources.zh-Hans.xlf | 12 -- .../Properties/xlf/Resources.zh-Hant.xlf | 12 -- .../Properties/Resources.cs.resx | 136 ++++++++++++++++++ .../Properties/Resources.de.resx | 136 ++++++++++++++++++ .../Properties/Resources.es.resx | 136 ++++++++++++++++++ .../Properties/Resources.fr.resx | 136 ++++++++++++++++++ .../Properties/Resources.it.resx | 136 ++++++++++++++++++ .../Properties/Resources.ja.resx | 136 ++++++++++++++++++ .../Properties/Resources.ko.resx | 136 ++++++++++++++++++ .../Properties/Resources.pl.resx | 136 ++++++++++++++++++ .../Properties/Resources.pt-BR.resx | 136 ++++++++++++++++++ .../Properties/Resources.ru.resx | 136 ++++++++++++++++++ .../Properties/Resources.tr.resx | 136 ++++++++++++++++++ .../Properties/Resources.zh-Hans.resx | 136 ++++++++++++++++++ .../Properties/Resources.zh-Hant.resx | 136 ++++++++++++++++++ .../Properties/xlf/Resources.cs.xlf | 27 ---- .../Properties/xlf/Resources.de.xlf | 27 ---- .../Properties/xlf/Resources.es.xlf | 27 ---- .../Properties/xlf/Resources.fr.xlf | 27 ---- .../Properties/xlf/Resources.it.xlf | 27 ---- .../Properties/xlf/Resources.ja.xlf | 27 ---- .../Properties/xlf/Resources.ko.xlf | 27 ---- .../Properties/xlf/Resources.pl.xlf | 27 ---- .../Properties/xlf/Resources.pt-BR.xlf | 27 ---- .../Properties/xlf/Resources.ru.xlf | 27 ---- .../Properties/xlf/Resources.tr.xlf | 27 ---- .../Properties/xlf/Resources.zh-Hans.xlf | 27 ---- .../Properties/xlf/Resources.zh-Hant.xlf | 27 ---- .../Xamarin.Android.Tools.AndroidSdk.csproj | 6 - 55 files changed, 3201 insertions(+), 519 deletions(-) create mode 100644 external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.Designer.cs create mode 100644 external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.cs.resx create mode 100644 external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.de.resx create mode 100644 external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.es.resx create mode 100644 external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.fr.resx create mode 100644 external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.it.resx create mode 100644 external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.ja.resx create mode 100644 external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.ko.resx create mode 100644 external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.pl.resx create mode 100644 external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.pt-BR.resx create mode 100644 external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.ru.resx create mode 100644 external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.tr.resx create mode 100644 external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.zh-Hans.resx create mode 100644 external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.zh-Hant.resx delete mode 100644 external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/xlf/Resources.cs.xlf delete mode 100644 external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/xlf/Resources.de.xlf delete mode 100644 external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/xlf/Resources.es.xlf delete mode 100644 external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/xlf/Resources.fr.xlf delete mode 100644 external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/xlf/Resources.it.xlf delete mode 100644 external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/xlf/Resources.ja.xlf delete mode 100644 external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/xlf/Resources.ko.xlf delete mode 100644 external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/xlf/Resources.pl.xlf delete mode 100644 external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/xlf/Resources.pt-BR.xlf delete mode 100644 external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/xlf/Resources.ru.xlf delete mode 100644 external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/xlf/Resources.tr.xlf delete mode 100644 external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/xlf/Resources.zh-Hans.xlf delete mode 100644 external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/xlf/Resources.zh-Hant.xlf create mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.cs.resx create mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.de.resx create mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.es.resx create mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.fr.resx create mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.it.resx create mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.ja.resx create mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.ko.resx create mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.pl.resx create mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.pt-BR.resx create mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.ru.resx create mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.tr.resx create mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.zh-Hans.resx create mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.zh-Hant.resx delete mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.cs.xlf delete mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.de.xlf delete mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.es.xlf delete mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.fr.xlf delete mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.it.xlf delete mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.ja.xlf delete mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.ko.xlf delete mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.pl.xlf delete mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.pt-BR.xlf delete mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.ru.xlf delete mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.tr.xlf delete mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.zh-Hans.xlf delete mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.zh-Hant.xlf diff --git a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Microsoft.Android.Build.BaseTasks.csproj b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Microsoft.Android.Build.BaseTasks.csproj index cc25bb5978a..5a3b2f51ded 100644 --- a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Microsoft.Android.Build.BaseTasks.csproj +++ b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Microsoft.Android.Build.BaseTasks.csproj @@ -1,23 +1,26 @@ - netstandard2.0;netcoreapp3.1 Microsoft.Android.Build.Tasks true - true - true true ..\..\product.snk $(VendorPrefix)Microsoft.Android.Build.BaseTasks$(VendorSuffix) - + + True + True + Resources.resx + + + ResXFileCodeGenerator + Resources.Designer.cs + - - diff --git a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.Designer.cs b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.Designer.cs new file mode 100644 index 00000000000..1795a033042 --- /dev/null +++ b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.Designer.cs @@ -0,0 +1,72 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Microsoft.Android.Build.Tasks.Properties { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.Android.Build.Tasks.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to Unhandled exception: {0}. + /// + internal static string XA0000_Exception { + get { + return ResourceManager.GetString("XA0000_Exception", resourceCulture); + } + } + } +} diff --git a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.cs.resx b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.cs.resx new file mode 100644 index 00000000000..59e52f6cd5f --- /dev/null +++ b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.cs.resx @@ -0,0 +1,104 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 1.3 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Neošetřená výjimka: {0} + {0} - The exception message of the associated exception + + \ No newline at end of file diff --git a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.de.resx b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.de.resx new file mode 100644 index 00000000000..bebc92ad434 --- /dev/null +++ b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.de.resx @@ -0,0 +1,104 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 1.3 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Ausnahmefehler: {0} + {0} - The exception message of the associated exception + + \ No newline at end of file diff --git a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.es.resx b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.es.resx new file mode 100644 index 00000000000..846b2488fb9 --- /dev/null +++ b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.es.resx @@ -0,0 +1,104 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 1.3 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Excepción no controlada: {0} + {0} - The exception message of the associated exception + + \ No newline at end of file diff --git a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.fr.resx b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.fr.resx new file mode 100644 index 00000000000..0a514e6b51c --- /dev/null +++ b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.fr.resx @@ -0,0 +1,104 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 1.3 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Exception non prise en charge{0} + {0} - The exception message of the associated exception + + \ No newline at end of file diff --git a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.it.resx b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.it.resx new file mode 100644 index 00000000000..8a6d245903a --- /dev/null +++ b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.it.resx @@ -0,0 +1,104 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 1.3 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Eccezione non gestita: {0} + {0} - The exception message of the associated exception + + \ No newline at end of file diff --git a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.ja.resx b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.ja.resx new file mode 100644 index 00000000000..329f20e9980 --- /dev/null +++ b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.ja.resx @@ -0,0 +1,104 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 1.3 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + ハンドルされない例外: {0} + {0} - The exception message of the associated exception + + \ No newline at end of file diff --git a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.ko.resx b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.ko.resx new file mode 100644 index 00000000000..b0a8db624d4 --- /dev/null +++ b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.ko.resx @@ -0,0 +1,104 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 1.3 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 처리되지 않은 예외: {0} + {0} - The exception message of the associated exception + + \ No newline at end of file diff --git a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.pl.resx b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.pl.resx new file mode 100644 index 00000000000..a31add32af6 --- /dev/null +++ b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.pl.resx @@ -0,0 +1,104 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 1.3 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Nieobsługiwany wyjątek: {0} + {0} - The exception message of the associated exception + + \ No newline at end of file diff --git a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.pt-BR.resx b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.pt-BR.resx new file mode 100644 index 00000000000..79ad207a084 --- /dev/null +++ b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.pt-BR.resx @@ -0,0 +1,104 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 1.3 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Exceção não tratada: {0} + {0} - The exception message of the associated exception + + \ No newline at end of file diff --git a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.ru.resx b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.ru.resx new file mode 100644 index 00000000000..18b77ef1a2c --- /dev/null +++ b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.ru.resx @@ -0,0 +1,104 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 1.3 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Необработанное исключение: {0} + {0} - The exception message of the associated exception + + \ No newline at end of file diff --git a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.tr.resx b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.tr.resx new file mode 100644 index 00000000000..934bb43f582 --- /dev/null +++ b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.tr.resx @@ -0,0 +1,104 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 1.3 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + İşlenmeyen özel durum: {0} + {0} - The exception message of the associated exception + + \ No newline at end of file diff --git a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.zh-Hans.resx b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.zh-Hans.resx new file mode 100644 index 00000000000..ee9e25027e2 --- /dev/null +++ b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.zh-Hans.resx @@ -0,0 +1,104 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 1.3 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 未处理的异常: {0} + {0} - The exception message of the associated exception + + \ No newline at end of file diff --git a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.zh-Hant.resx b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.zh-Hant.resx new file mode 100644 index 00000000000..f7abee57c3b --- /dev/null +++ b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/Resources.zh-Hant.resx @@ -0,0 +1,104 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 1.3 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 未處理的例外狀況: {0} + {0} - The exception message of the associated exception + + \ No newline at end of file diff --git a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/xlf/Resources.cs.xlf b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/xlf/Resources.cs.xlf deleted file mode 100644 index 0492b122d5d..00000000000 --- a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/xlf/Resources.cs.xlf +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - Unhandled exception: {0} - Neošetřená výjimka: {0} - {0} - The exception message of the associated exception - - - - \ No newline at end of file diff --git a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/xlf/Resources.de.xlf b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/xlf/Resources.de.xlf deleted file mode 100644 index 1810bc2e15c..00000000000 --- a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/xlf/Resources.de.xlf +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - Unhandled exception: {0} - Ausnahmefehler: {0} - {0} - The exception message of the associated exception - - - - \ No newline at end of file diff --git a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/xlf/Resources.es.xlf b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/xlf/Resources.es.xlf deleted file mode 100644 index 5e1af08d70b..00000000000 --- a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/xlf/Resources.es.xlf +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - Unhandled exception: {0} - Excepción no controlada: {0} - {0} - The exception message of the associated exception - - - - \ No newline at end of file diff --git a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/xlf/Resources.fr.xlf b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/xlf/Resources.fr.xlf deleted file mode 100644 index b7f2e63566a..00000000000 --- a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/xlf/Resources.fr.xlf +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - Unhandled exception: {0} - Exception non prise en charge : {0} - {0} - The exception message of the associated exception - - - - \ No newline at end of file diff --git a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/xlf/Resources.it.xlf b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/xlf/Resources.it.xlf deleted file mode 100644 index b8b4d0a1a85..00000000000 --- a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/xlf/Resources.it.xlf +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - Unhandled exception: {0} - Eccezione non gestita: {0} - {0} - The exception message of the associated exception - - - - \ No newline at end of file diff --git a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/xlf/Resources.ja.xlf b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/xlf/Resources.ja.xlf deleted file mode 100644 index b63e2db5453..00000000000 --- a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/xlf/Resources.ja.xlf +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - Unhandled exception: {0} - ハンドルされない例外: {0} - {0} - The exception message of the associated exception - - - - \ No newline at end of file diff --git a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/xlf/Resources.ko.xlf b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/xlf/Resources.ko.xlf deleted file mode 100644 index 2f43d0380be..00000000000 --- a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/xlf/Resources.ko.xlf +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - Unhandled exception: {0} - 처리되지 않은 예외: {0} - {0} - The exception message of the associated exception - - - - \ No newline at end of file diff --git a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/xlf/Resources.pl.xlf b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/xlf/Resources.pl.xlf deleted file mode 100644 index 46fc0ca8649..00000000000 --- a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/xlf/Resources.pl.xlf +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - Unhandled exception: {0} - Nieobsłużony wyjątek: {0} - {0} - The exception message of the associated exception - - - - \ No newline at end of file diff --git a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/xlf/Resources.pt-BR.xlf b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/xlf/Resources.pt-BR.xlf deleted file mode 100644 index 951acf68266..00000000000 --- a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/xlf/Resources.pt-BR.xlf +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - Unhandled exception: {0} - Exceção sem tratamento: {0} - {0} - The exception message of the associated exception - - - - \ No newline at end of file diff --git a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/xlf/Resources.ru.xlf b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/xlf/Resources.ru.xlf deleted file mode 100644 index 7984f3c1c1c..00000000000 --- a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/xlf/Resources.ru.xlf +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - Unhandled exception: {0} - Необработанное исключение: {0} - {0} - The exception message of the associated exception - - - - \ No newline at end of file diff --git a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/xlf/Resources.tr.xlf b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/xlf/Resources.tr.xlf deleted file mode 100644 index 79944401b3b..00000000000 --- a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/xlf/Resources.tr.xlf +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - Unhandled exception: {0} - İşlenmeyen özel durum: {0} - {0} - The exception message of the associated exception - - - - \ No newline at end of file diff --git a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/xlf/Resources.zh-Hans.xlf b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/xlf/Resources.zh-Hans.xlf deleted file mode 100644 index 11a918c646d..00000000000 --- a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/xlf/Resources.zh-Hans.xlf +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - Unhandled exception: {0} - 未处理的异常: {0} - {0} - The exception message of the associated exception - - - - \ No newline at end of file diff --git a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/xlf/Resources.zh-Hant.xlf b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/xlf/Resources.zh-Hant.xlf deleted file mode 100644 index 6738c0c00a6..00000000000 --- a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Properties/xlf/Resources.zh-Hant.xlf +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - Unhandled exception: {0} - 未處理的例外狀況: {0} - {0} - The exception message of the associated exception - - - - \ No newline at end of file diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.cs.resx b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.cs.resx new file mode 100644 index 00000000000..42470c39056 --- /dev/null +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.cs.resx @@ -0,0 +1,136 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Při ověřování instalace sady Java SDK v {0}, která se našla při hledání cest z {1} došlo k výjimce. Ujistěte se, že v části Android v možnostech Visual Studio je nakonfigurovaný platný adresář sady Java SDK. Pokud chcete pro sestavení z příkazového řádku použít vlastní cestu sady SDK, nastavte vlastnost MSBuild JavaSdkDirectory na vlastní cestu. Výjimka: {2} + +{0} - The path of the invalid installation +{1} - a "contextual" name for where {0} came from: `Preferred Registry` (Windows Registry), `OpenJDK`, `$JAVA_HOME` (environment variable), etc. +{2} - The exception message of the associated exception. + + + Při čtení konfiguračního souboru {0} došlo k výjimce. Výjimka: {1} + +{0} - The path of the file being read. +{1} - The exception message of the associated exception. + + + Při čtení výstupu {0} {1} došlo k výjimce. Výjimka: {2} + + \ No newline at end of file diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.de.resx b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.de.resx new file mode 100644 index 00000000000..cc440d66307 --- /dev/null +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.de.resx @@ -0,0 +1,136 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Ausnahme beim Überprüfen der Java SDK-Installation in '{0}', die beim Durchsuchen der Pfade von '{1}' gefunden wurde. Stellen Sie sicher, dass für den Android-Abschnitt der Visual Studio-Optionen ein gültiges Java SDK-Verzeichnis konfiguriert ist. Um einen benutzerdefinierten SDK-Pfad für einen Befehlszeilenbuild zu verwenden, legen Sie die MSBuild-Eigenschaft "JavaSdkDirectory" auf den benutzerdefinierten Pfad fest. Ausnahme: {2} + +{0} - The path of the invalid installation +{1} - a "contextual" name for where {0} came from: `Preferred Registry` (Windows Registry), `OpenJDK`, `$JAVA_HOME` (environment variable), etc. +{2} - The exception message of the associated exception. + + + Ausnahme beim Lesen der Konfigurationsdatei '{0}'. Ausnahme: {1} + +{0} - The path of the file being read. +{1} - The exception message of the associated exception. + + + Ausnahme beim Lesen der Ausgabe von "{0} {1}". Ausnahme: {2} + + \ No newline at end of file diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.es.resx b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.es.resx new file mode 100644 index 00000000000..dea39840b2f --- /dev/null +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.es.resx @@ -0,0 +1,136 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Se ha producido una excepción al validar la instalación del SDK de Java en "{0}" que se ha encontrado al buscar las rutas desde "{1}". Asegúrese de que la sección de Android de las opciones de Visual Studio tiene configurado un directorio de Java SDK válido. Para utilizar una ruta de acceso al SDK personalizada para una compilación de línea de comandos, establezca la propiedad MSBuild 'JavaSdkDirectory' en la ruta personalizada. Excepción: {2} + +{0} - The path of the invalid installation +{1} - a "contextual" name for where {0} came from: `Preferred Registry` (Windows Registry), `OpenJDK`, `$JAVA_HOME` (environment variable), etc. +{2} - The exception message of the associated exception. + + + Se ha producido una excepción al leer el archivo de configuración "{0}". Excepción: {1} + +{0} - The path of the file being read. +{1} - The exception message of the associated exception. + + + Se ha producido una excepción al leer la salida de "{0} {1}". Excepción: {2} + + \ No newline at end of file diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.fr.resx b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.fr.resx new file mode 100644 index 00000000000..64106e94733 --- /dev/null +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.fr.resx @@ -0,0 +1,136 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Une exception s’est produite lors de la validation de l’installation du Kit de développement logiciel (SDK) Java dans '{0}' qui a été trouvée lors de la recherche dans les chemins d’accès à partir de '{1}'. Vérifiez que la section Android des options Visual Studio a un répertoire Java SDK valide configuré. Pour utiliser un chemin de kit SDK personnalisé pour une build de ligne de commande, définissez la propriété MSBuild 'JavaSdkDirectory' sur le chemin personnalisé. Exception : {2} + +{0} - The path of the invalid installation +{1} - a "contextual" name for where {0} came from: `Preferred Registry` (Windows Registry), `OpenJDK`, `$JAVA_HOME` (environment variable), etc. +{2} - The exception message of the associated exception. + + + Une exception s’est produite lors de la lecture du fichier de configuration '{0}'. Exception : {1} + +{0} - The path of the file being read. +{1} - The exception message of the associated exception. + + + Une exception s’est produite lors de la lecture de la sortie de '{0} {1}'. Exception : {2} + + \ No newline at end of file diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.it.resx b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.it.resx new file mode 100644 index 00000000000..82b70004437 --- /dev/null +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.it.resx @@ -0,0 +1,136 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Si è verificata un'eccezione durante la convalida dell'installazione di Java SDK in '{0}' trovata durante la ricerca nei percorsi da '{1}'. Assicurarsi che nella sezione Android delle opzioni di Visual Studio sia configurata una directory java SDK valida. Per usare un percorso SDK personalizzato per una compilazione della riga di comando, impostare la proprietà MSBuild 'JavaSdkDirectory' sul percorso personalizzato. Eccezione: {2} + +{0} - The path of the invalid installation +{1} - a "contextual" name for where {0} came from: `Preferred Registry` (Windows Registry), `OpenJDK`, `$JAVA_HOME` (environment variable), etc. +{2} - The exception message of the associated exception. + + + Si è verificata un'eccezione durante la lettura del file di configurazione '{0}'. Eccezione: {1} + +{0} - The path of the file being read. +{1} - The exception message of the associated exception. + + + Eccezione durante la lettura dell'output di '{0} {1}'. Eccezione: {2} + + \ No newline at end of file diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.ja.resx b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.ja.resx new file mode 100644 index 00000000000..caac05f6558 --- /dev/null +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.ja.resx @@ -0,0 +1,136 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + '{1}' からのパスの検索中に見つかった '{0}' の Java SDK インストールを検証中に例外が発生しました。Visual Studio オプションの Android セクションに、有効な Java SDK ディレクトリが構成されていることを確認してください。コマンド ライン ビルドにカスタム SDK パスを使用するには、'JavaSdkDirectory' MSBuild プロパティにそのカスタム パスを設定します。例外: {2} + +{0} - The path of the invalid installation +{1} - a "contextual" name for where {0} came from: `Preferred Registry` (Windows Registry), `OpenJDK`, `$JAVA_HOME` (environment variable), etc. +{2} - The exception message of the associated exception. + + + 構成ファイル '{0}' の読み取り中に例外が発生しました。例外: {1} + +{0} - The path of the file being read. +{1} - The exception message of the associated exception. + + + '{0} {1}' の出力の読み取り中に例外が発生しました。例外: {2} + + \ No newline at end of file diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.ko.resx b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.ko.resx new file mode 100644 index 00000000000..11d77439612 --- /dev/null +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.ko.resx @@ -0,0 +1,136 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + '{1}'에서 경로를 검색하는 동안 발견된 '{0}'의 Java SDK 설치의 유효성을 검사하는 동안 예외가 발생했습니다. Visual Studio 옵션의 Android 섹션에 유효한 Java SDK 디렉터리가 구성되어 있는지 확인하세요. 명령줄 빌드에 사용자 지정 SDK 경로를 사용하려면 'JavaSdkDirectory' MSBuild 속성을 사용자 지정 경로로 설정하세요. 예외: {2} + +{0} - The path of the invalid installation +{1} - a "contextual" name for where {0} came from: `Preferred Registry` (Windows Registry), `OpenJDK`, `$JAVA_HOME` (environment variable), etc. +{2} - The exception message of the associated exception. + + + 구성 파일 '{0}'을(를) 읽는 동안 예외가 발생했습니다. 예외: {1} + +{0} - The path of the file being read. +{1} - The exception message of the associated exception. + + + '{0} {1}'의 출력을 읽는 동안 예외가 발생했습니다. 예외: {2} + + \ No newline at end of file diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.pl.resx b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.pl.resx new file mode 100644 index 00000000000..9f9c5dacf45 --- /dev/null +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.pl.resx @@ -0,0 +1,136 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Wystąpił wyjątek podczas sprawdzania poprawności instalacji zestawu Java SDK w lokalizacji „{0}”, która została znaleziona podczas przeszukiwania ścieżek z lokalizacji „{1}”. Upewnij się, że sekcja opcji programu Visual Studio dla systemu Android ma skonfigurowany prawidłowy katalog zestawu Java SDK. Aby użyć niestandardowej ścieżki zestawu SDK dla kompilacji wiersza polecenia, ustaw ścieżkę niestandardową właściwości „JavaSdkDirectory” programu MSBuild. Wyjątek: {2} + +{0} - The path of the invalid installation +{1} - a "contextual" name for where {0} came from: `Preferred Registry` (Windows Registry), `OpenJDK`, `$JAVA_HOME` (environment variable), etc. +{2} - The exception message of the associated exception. + + + Wystąpił wyjątek podczas odczytywania pliku konfiguracji „{0}”. Wyjątek: {1} + +{0} - The path of the file being read. +{1} - The exception message of the associated exception. + + + Wystąpił wyjątek podczas odczytywania danych wyjściowych elementu „{0} {1}„. Wyjątek: {2} + + \ No newline at end of file diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.pt-BR.resx b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.pt-BR.resx new file mode 100644 index 00000000000..4a2b67a15e2 --- /dev/null +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.pt-BR.resx @@ -0,0 +1,136 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Ocorreu uma exceção ao validar a instalação do Java SDK em '{0}' que foi encontrada ao pesquisar os caminhos de '{1}'. Certifique-se de que a seção Android das opções do Visual Studio tenha um diretório Java SDK válido configurado. Para usar um caminho SDK personalizado para uma compilação de linha de comando, defina a propriedade MSBuild 'JavaSdkDirectory' para o caminho personalizado. Exceção: {2} + +{0} - The path of the invalid installation +{1} - a "contextual" name for where {0} came from: `Preferred Registry` (Windows Registry), `OpenJDK`, `$JAVA_HOME` (environment variable), etc. +{2} - The exception message of the associated exception. + + + Ocorreu uma exceção ao ler o arquivo de configuração '{0}'. Exceção: {1} + +{0} - The path of the file being read. +{1} - The exception message of the associated exception. + + + Ocorreu uma exceção ao ler a saída de '{0} {1}'. Exceção: {2} + + \ No newline at end of file diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.ru.resx b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.ru.resx new file mode 100644 index 00000000000..9be590b3588 --- /dev/null +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.ru.resx @@ -0,0 +1,136 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Возникло исключение при проверке установки Java SDK в "{0}", обнаруженном при поиске путей из "{1}". Настройте допустимый каталог Java SDK в разделе Android параметров Visual Studio. Чтобы использовать пользовательский путь SDK для сборки командной строки, задайте настраиваемый путь для свойства MSBuild "JavaSdkDirectory". Исключение: {2} + +{0} - The path of the invalid installation +{1} - a "contextual" name for where {0} came from: `Preferred Registry` (Windows Registry), `OpenJDK`, `$JAVA_HOME` (environment variable), etc. +{2} - The exception message of the associated exception. + + + Возникло исключение при чтении файла конфигурации "{0}". Исключение: {1} + +{0} - The path of the file being read. +{1} - The exception message of the associated exception. + + + Возникло исключение при чтении выходных данных "{0} {1}". Исключение: {2} + + \ No newline at end of file diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.tr.resx b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.tr.resx new file mode 100644 index 00000000000..8156b187b07 --- /dev/null +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.tr.resx @@ -0,0 +1,136 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + '{1}' yolu aranıp '{0}' içindeki Java SDK yüklemesi doğrulanırken bir istisna oluştu. Visual Studio seçeneklerinin Android bölümünün yapılandırılmış geçerli bir Java SDK dizinine sahip olduğundan emin olun. Komut satırı derlemesi için özel bir SDK yolu kullanmak üzere 'JavaSdkDirectory' MSBuild özelliğini özel yola ayarlayın. Özel durum: {2} + +{0} - The path of the invalid installation +{1} - a "contextual" name for where {0} came from: `Preferred Registry` (Windows Registry), `OpenJDK`, `$JAVA_HOME` (environment variable), etc. +{2} - The exception message of the associated exception. + + + ‘{0}’ Yapılandırma dosyası okunurken özel durum oluştu. Özel durum: {1} + +{0} - The path of the file being read. +{1} - The exception message of the associated exception. + + + '{0} {1}' çıktısı okunurken özel durum oluştu. Özel durum: {2} + + \ No newline at end of file diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.zh-Hans.resx b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.zh-Hans.resx new file mode 100644 index 00000000000..c452ceb9a05 --- /dev/null +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.zh-Hans.resx @@ -0,0 +1,136 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 验证 "{0}" 中的 Java SDK 安装时发生异常,该安装是在从 "{1}" 搜索路径时找到的。确保 Visual Studio 选项的 Android 部分配置了有效的 Java SDK 目录。若要对命令行生成使用自定义 SDK 路径,请将 "JavaSdkDirectory" MSBuild 属性设置为自定义路径。异常: {2} + +{0} - The path of the invalid installation +{1} - a "contextual" name for where {0} came from: `Preferred Registry` (Windows Registry), `OpenJDK`, `$JAVA_HOME` (environment variable), etc. +{2} - The exception message of the associated exception. + + + 读取配置文件 "{0}" 时发生异常。异常: {1} + +{0} - The path of the file being read. +{1} - The exception message of the associated exception. + + + 读取 "{0} {1}" 的输出时发生异常。异常: {2} + + \ No newline at end of file diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.zh-Hant.resx b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.zh-Hant.resx new file mode 100644 index 00000000000..c3a204d4434 --- /dev/null +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/Resources.zh-Hant.resx @@ -0,0 +1,136 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 驗證 '{0}' 中的 JAVA SDK 安裝時發生例外狀況,該安裝是在從 '{1}' 搜尋路徑時找到的。請確認 Visual Studio 選項的 Android 區段已設定有效的 JAVA SDK 目錄。若要針對命令列組建使用自訂 SDK 路徑,請將 'JAVASdkDirectory' MSBuild 屬性設定為自訂路徑。例外狀況: {2} + +{0} - The path of the invalid installation +{1} - a "contextual" name for where {0} came from: `Preferred Registry` (Windows Registry), `OpenJDK`, `$JAVA_HOME` (environment variable), etc. +{2} - The exception message of the associated exception. + + + 讀取設定檔 '{0}' 時發生例外狀況。例外狀況: {1} + +{0} - The path of the file being read. +{1} - The exception message of the associated exception. + + + 讀取 '{0} {1}' 的輸出時發生例外狀況。例外狀況: {2} + + \ No newline at end of file diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.cs.xlf b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.cs.xlf deleted file mode 100644 index bd8c0d8bad0..00000000000 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.cs.xlf +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - An exception occurred while validating the Java SDK installation in '{0}' that was found while searching the paths from '{1}'. Ensure that the Android section of the Visual Studio options has a valid Java SDK directory configured. To use a custom SDK path for a command line build, set the 'JavaSdkDirectory' MSBuild property to the custom path. Exception: {2} - An exception occurred while validating the Java SDK installation in '{0}' that was found while searching the paths from '{1}'. Ensure that the Android section of the Visual Studio options has a valid Java SDK directory configured. To use a custom SDK path for a command line build, set the 'JavaSdkDirectory' MSBuild property to the custom path. Exception: {2} - -{0} - The path of the invalid installation -{1} - a "contextual" name for where {0} came from: `Preferred Registry` (Windows Registry), `OpenJDK`, `$JAVA_HOME` (environment variable), etc. -{2} - The exception message of the associated exception. - - - An exception occurred while reading configuration file '{0}'. Exception: {1} - An exception occurred while reading configuration file '{0}'. Exception: {1} - -{0} - The path of the file being read. -{1} - The exception message of the associated exception. - - - An exception occurred while reading the output of '{0} {1}'. Exception: {2} - An exception occurred while reading the output of '{0} {1}'. Exception: {2} - - - - - \ No newline at end of file diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.de.xlf b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.de.xlf deleted file mode 100644 index f4bf5ddcd78..00000000000 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.de.xlf +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - An exception occurred while validating the Java SDK installation in '{0}' that was found while searching the paths from '{1}'. Ensure that the Android section of the Visual Studio options has a valid Java SDK directory configured. To use a custom SDK path for a command line build, set the 'JavaSdkDirectory' MSBuild property to the custom path. Exception: {2} - An exception occurred while validating the Java SDK installation in '{0}' that was found while searching the paths from '{1}'. Ensure that the Android section of the Visual Studio options has a valid Java SDK directory configured. To use a custom SDK path for a command line build, set the 'JavaSdkDirectory' MSBuild property to the custom path. Exception: {2} - -{0} - The path of the invalid installation -{1} - a "contextual" name for where {0} came from: `Preferred Registry` (Windows Registry), `OpenJDK`, `$JAVA_HOME` (environment variable), etc. -{2} - The exception message of the associated exception. - - - An exception occurred while reading configuration file '{0}'. Exception: {1} - An exception occurred while reading configuration file '{0}'. Exception: {1} - -{0} - The path of the file being read. -{1} - The exception message of the associated exception. - - - An exception occurred while reading the output of '{0} {1}'. Exception: {2} - An exception occurred while reading the output of '{0} {1}'. Exception: {2} - - - - - \ No newline at end of file diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.es.xlf b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.es.xlf deleted file mode 100644 index 0e1e9987593..00000000000 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.es.xlf +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - An exception occurred while validating the Java SDK installation in '{0}' that was found while searching the paths from '{1}'. Ensure that the Android section of the Visual Studio options has a valid Java SDK directory configured. To use a custom SDK path for a command line build, set the 'JavaSdkDirectory' MSBuild property to the custom path. Exception: {2} - An exception occurred while validating the Java SDK installation in '{0}' that was found while searching the paths from '{1}'. Ensure that the Android section of the Visual Studio options has a valid Java SDK directory configured. To use a custom SDK path for a command line build, set the 'JavaSdkDirectory' MSBuild property to the custom path. Exception: {2} - -{0} - The path of the invalid installation -{1} - a "contextual" name for where {0} came from: `Preferred Registry` (Windows Registry), `OpenJDK`, `$JAVA_HOME` (environment variable), etc. -{2} - The exception message of the associated exception. - - - An exception occurred while reading configuration file '{0}'. Exception: {1} - An exception occurred while reading configuration file '{0}'. Exception: {1} - -{0} - The path of the file being read. -{1} - The exception message of the associated exception. - - - An exception occurred while reading the output of '{0} {1}'. Exception: {2} - An exception occurred while reading the output of '{0} {1}'. Exception: {2} - - - - - \ No newline at end of file diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.fr.xlf b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.fr.xlf deleted file mode 100644 index 1a577af4912..00000000000 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.fr.xlf +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - An exception occurred while validating the Java SDK installation in '{0}' that was found while searching the paths from '{1}'. Ensure that the Android section of the Visual Studio options has a valid Java SDK directory configured. To use a custom SDK path for a command line build, set the 'JavaSdkDirectory' MSBuild property to the custom path. Exception: {2} - An exception occurred while validating the Java SDK installation in '{0}' that was found while searching the paths from '{1}'. Ensure that the Android section of the Visual Studio options has a valid Java SDK directory configured. To use a custom SDK path for a command line build, set the 'JavaSdkDirectory' MSBuild property to the custom path. Exception: {2} - -{0} - The path of the invalid installation -{1} - a "contextual" name for where {0} came from: `Preferred Registry` (Windows Registry), `OpenJDK`, `$JAVA_HOME` (environment variable), etc. -{2} - The exception message of the associated exception. - - - An exception occurred while reading configuration file '{0}'. Exception: {1} - An exception occurred while reading configuration file '{0}'. Exception: {1} - -{0} - The path of the file being read. -{1} - The exception message of the associated exception. - - - An exception occurred while reading the output of '{0} {1}'. Exception: {2} - An exception occurred while reading the output of '{0} {1}'. Exception: {2} - - - - - \ No newline at end of file diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.it.xlf b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.it.xlf deleted file mode 100644 index c94f2d39baf..00000000000 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.it.xlf +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - An exception occurred while validating the Java SDK installation in '{0}' that was found while searching the paths from '{1}'. Ensure that the Android section of the Visual Studio options has a valid Java SDK directory configured. To use a custom SDK path for a command line build, set the 'JavaSdkDirectory' MSBuild property to the custom path. Exception: {2} - An exception occurred while validating the Java SDK installation in '{0}' that was found while searching the paths from '{1}'. Ensure that the Android section of the Visual Studio options has a valid Java SDK directory configured. To use a custom SDK path for a command line build, set the 'JavaSdkDirectory' MSBuild property to the custom path. Exception: {2} - -{0} - The path of the invalid installation -{1} - a "contextual" name for where {0} came from: `Preferred Registry` (Windows Registry), `OpenJDK`, `$JAVA_HOME` (environment variable), etc. -{2} - The exception message of the associated exception. - - - An exception occurred while reading configuration file '{0}'. Exception: {1} - An exception occurred while reading configuration file '{0}'. Exception: {1} - -{0} - The path of the file being read. -{1} - The exception message of the associated exception. - - - An exception occurred while reading the output of '{0} {1}'. Exception: {2} - An exception occurred while reading the output of '{0} {1}'. Exception: {2} - - - - - \ No newline at end of file diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.ja.xlf b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.ja.xlf deleted file mode 100644 index 6de35c24a83..00000000000 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.ja.xlf +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - An exception occurred while validating the Java SDK installation in '{0}' that was found while searching the paths from '{1}'. Ensure that the Android section of the Visual Studio options has a valid Java SDK directory configured. To use a custom SDK path for a command line build, set the 'JavaSdkDirectory' MSBuild property to the custom path. Exception: {2} - An exception occurred while validating the Java SDK installation in '{0}' that was found while searching the paths from '{1}'. Ensure that the Android section of the Visual Studio options has a valid Java SDK directory configured. To use a custom SDK path for a command line build, set the 'JavaSdkDirectory' MSBuild property to the custom path. Exception: {2} - -{0} - The path of the invalid installation -{1} - a "contextual" name for where {0} came from: `Preferred Registry` (Windows Registry), `OpenJDK`, `$JAVA_HOME` (environment variable), etc. -{2} - The exception message of the associated exception. - - - An exception occurred while reading configuration file '{0}'. Exception: {1} - An exception occurred while reading configuration file '{0}'. Exception: {1} - -{0} - The path of the file being read. -{1} - The exception message of the associated exception. - - - An exception occurred while reading the output of '{0} {1}'. Exception: {2} - An exception occurred while reading the output of '{0} {1}'. Exception: {2} - - - - - \ No newline at end of file diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.ko.xlf b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.ko.xlf deleted file mode 100644 index c96a2ccf2f2..00000000000 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.ko.xlf +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - An exception occurred while validating the Java SDK installation in '{0}' that was found while searching the paths from '{1}'. Ensure that the Android section of the Visual Studio options has a valid Java SDK directory configured. To use a custom SDK path for a command line build, set the 'JavaSdkDirectory' MSBuild property to the custom path. Exception: {2} - An exception occurred while validating the Java SDK installation in '{0}' that was found while searching the paths from '{1}'. Ensure that the Android section of the Visual Studio options has a valid Java SDK directory configured. To use a custom SDK path for a command line build, set the 'JavaSdkDirectory' MSBuild property to the custom path. Exception: {2} - -{0} - The path of the invalid installation -{1} - a "contextual" name for where {0} came from: `Preferred Registry` (Windows Registry), `OpenJDK`, `$JAVA_HOME` (environment variable), etc. -{2} - The exception message of the associated exception. - - - An exception occurred while reading configuration file '{0}'. Exception: {1} - An exception occurred while reading configuration file '{0}'. Exception: {1} - -{0} - The path of the file being read. -{1} - The exception message of the associated exception. - - - An exception occurred while reading the output of '{0} {1}'. Exception: {2} - An exception occurred while reading the output of '{0} {1}'. Exception: {2} - - - - - \ No newline at end of file diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.pl.xlf b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.pl.xlf deleted file mode 100644 index 102d8036668..00000000000 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.pl.xlf +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - An exception occurred while validating the Java SDK installation in '{0}' that was found while searching the paths from '{1}'. Ensure that the Android section of the Visual Studio options has a valid Java SDK directory configured. To use a custom SDK path for a command line build, set the 'JavaSdkDirectory' MSBuild property to the custom path. Exception: {2} - An exception occurred while validating the Java SDK installation in '{0}' that was found while searching the paths from '{1}'. Ensure that the Android section of the Visual Studio options has a valid Java SDK directory configured. To use a custom SDK path for a command line build, set the 'JavaSdkDirectory' MSBuild property to the custom path. Exception: {2} - -{0} - The path of the invalid installation -{1} - a "contextual" name for where {0} came from: `Preferred Registry` (Windows Registry), `OpenJDK`, `$JAVA_HOME` (environment variable), etc. -{2} - The exception message of the associated exception. - - - An exception occurred while reading configuration file '{0}'. Exception: {1} - An exception occurred while reading configuration file '{0}'. Exception: {1} - -{0} - The path of the file being read. -{1} - The exception message of the associated exception. - - - An exception occurred while reading the output of '{0} {1}'. Exception: {2} - An exception occurred while reading the output of '{0} {1}'. Exception: {2} - - - - - \ No newline at end of file diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.pt-BR.xlf b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.pt-BR.xlf deleted file mode 100644 index 394aa0620be..00000000000 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.pt-BR.xlf +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - An exception occurred while validating the Java SDK installation in '{0}' that was found while searching the paths from '{1}'. Ensure that the Android section of the Visual Studio options has a valid Java SDK directory configured. To use a custom SDK path for a command line build, set the 'JavaSdkDirectory' MSBuild property to the custom path. Exception: {2} - An exception occurred while validating the Java SDK installation in '{0}' that was found while searching the paths from '{1}'. Ensure that the Android section of the Visual Studio options has a valid Java SDK directory configured. To use a custom SDK path for a command line build, set the 'JavaSdkDirectory' MSBuild property to the custom path. Exception: {2} - -{0} - The path of the invalid installation -{1} - a "contextual" name for where {0} came from: `Preferred Registry` (Windows Registry), `OpenJDK`, `$JAVA_HOME` (environment variable), etc. -{2} - The exception message of the associated exception. - - - An exception occurred while reading configuration file '{0}'. Exception: {1} - An exception occurred while reading configuration file '{0}'. Exception: {1} - -{0} - The path of the file being read. -{1} - The exception message of the associated exception. - - - An exception occurred while reading the output of '{0} {1}'. Exception: {2} - An exception occurred while reading the output of '{0} {1}'. Exception: {2} - - - - - \ No newline at end of file diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.ru.xlf b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.ru.xlf deleted file mode 100644 index b39553d1666..00000000000 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.ru.xlf +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - An exception occurred while validating the Java SDK installation in '{0}' that was found while searching the paths from '{1}'. Ensure that the Android section of the Visual Studio options has a valid Java SDK directory configured. To use a custom SDK path for a command line build, set the 'JavaSdkDirectory' MSBuild property to the custom path. Exception: {2} - An exception occurred while validating the Java SDK installation in '{0}' that was found while searching the paths from '{1}'. Ensure that the Android section of the Visual Studio options has a valid Java SDK directory configured. To use a custom SDK path for a command line build, set the 'JavaSdkDirectory' MSBuild property to the custom path. Exception: {2} - -{0} - The path of the invalid installation -{1} - a "contextual" name for where {0} came from: `Preferred Registry` (Windows Registry), `OpenJDK`, `$JAVA_HOME` (environment variable), etc. -{2} - The exception message of the associated exception. - - - An exception occurred while reading configuration file '{0}'. Exception: {1} - An exception occurred while reading configuration file '{0}'. Exception: {1} - -{0} - The path of the file being read. -{1} - The exception message of the associated exception. - - - An exception occurred while reading the output of '{0} {1}'. Exception: {2} - An exception occurred while reading the output of '{0} {1}'. Exception: {2} - - - - - \ No newline at end of file diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.tr.xlf b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.tr.xlf deleted file mode 100644 index 020759cf4c3..00000000000 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.tr.xlf +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - An exception occurred while validating the Java SDK installation in '{0}' that was found while searching the paths from '{1}'. Ensure that the Android section of the Visual Studio options has a valid Java SDK directory configured. To use a custom SDK path for a command line build, set the 'JavaSdkDirectory' MSBuild property to the custom path. Exception: {2} - An exception occurred while validating the Java SDK installation in '{0}' that was found while searching the paths from '{1}'. Ensure that the Android section of the Visual Studio options has a valid Java SDK directory configured. To use a custom SDK path for a command line build, set the 'JavaSdkDirectory' MSBuild property to the custom path. Exception: {2} - -{0} - The path of the invalid installation -{1} - a "contextual" name for where {0} came from: `Preferred Registry` (Windows Registry), `OpenJDK`, `$JAVA_HOME` (environment variable), etc. -{2} - The exception message of the associated exception. - - - An exception occurred while reading configuration file '{0}'. Exception: {1} - An exception occurred while reading configuration file '{0}'. Exception: {1} - -{0} - The path of the file being read. -{1} - The exception message of the associated exception. - - - An exception occurred while reading the output of '{0} {1}'. Exception: {2} - An exception occurred while reading the output of '{0} {1}'. Exception: {2} - - - - - \ No newline at end of file diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.zh-Hans.xlf b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.zh-Hans.xlf deleted file mode 100644 index 4a160a199dc..00000000000 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.zh-Hans.xlf +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - An exception occurred while validating the Java SDK installation in '{0}' that was found while searching the paths from '{1}'. Ensure that the Android section of the Visual Studio options has a valid Java SDK directory configured. To use a custom SDK path for a command line build, set the 'JavaSdkDirectory' MSBuild property to the custom path. Exception: {2} - An exception occurred while validating the Java SDK installation in '{0}' that was found while searching the paths from '{1}'. Ensure that the Android section of the Visual Studio options has a valid Java SDK directory configured. To use a custom SDK path for a command line build, set the 'JavaSdkDirectory' MSBuild property to the custom path. Exception: {2} - -{0} - The path of the invalid installation -{1} - a "contextual" name for where {0} came from: `Preferred Registry` (Windows Registry), `OpenJDK`, `$JAVA_HOME` (environment variable), etc. -{2} - The exception message of the associated exception. - - - An exception occurred while reading configuration file '{0}'. Exception: {1} - An exception occurred while reading configuration file '{0}'. Exception: {1} - -{0} - The path of the file being read. -{1} - The exception message of the associated exception. - - - An exception occurred while reading the output of '{0} {1}'. Exception: {2} - An exception occurred while reading the output of '{0} {1}'. Exception: {2} - - - - - \ No newline at end of file diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.zh-Hant.xlf b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.zh-Hant.xlf deleted file mode 100644 index 74d9a2e51c2..00000000000 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Properties/xlf/Resources.zh-Hant.xlf +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - An exception occurred while validating the Java SDK installation in '{0}' that was found while searching the paths from '{1}'. Ensure that the Android section of the Visual Studio options has a valid Java SDK directory configured. To use a custom SDK path for a command line build, set the 'JavaSdkDirectory' MSBuild property to the custom path. Exception: {2} - An exception occurred while validating the Java SDK installation in '{0}' that was found while searching the paths from '{1}'. Ensure that the Android section of the Visual Studio options has a valid Java SDK directory configured. To use a custom SDK path for a command line build, set the 'JavaSdkDirectory' MSBuild property to the custom path. Exception: {2} - -{0} - The path of the invalid installation -{1} - a "contextual" name for where {0} came from: `Preferred Registry` (Windows Registry), `OpenJDK`, `$JAVA_HOME` (environment variable), etc. -{2} - The exception message of the associated exception. - - - An exception occurred while reading configuration file '{0}'. Exception: {1} - An exception occurred while reading configuration file '{0}'. Exception: {1} - -{0} - The path of the file being read. -{1} - The exception message of the associated exception. - - - An exception occurred while reading the output of '{0} {1}'. Exception: {2} - An exception occurred while reading the output of '{0} {1}'. Exception: {2} - - - - - \ No newline at end of file diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj index 0f35c23c572..c1b258aaaa5 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj @@ -17,11 +17,6 @@ $(VendorPrefix)Xamarin.Android.Tools.AndroidSdk$(VendorSuffix) - - cs;de;es;fr;it;ja;ko;pl;pt-BR;ru;tr;zh-Hans;zh-Hant - true - - @@ -35,7 +30,6 @@ all runtime; build; native; contentfiles; analyzers - From 43cc004f7fc95069adc8863fd30ba3dca7cc32f9 Mon Sep 17 00:00:00 2001 From: Jonathan Pryor Date: Tue, 23 Aug 2022 14:46:14 -0400 Subject: [PATCH 160/308] [ci] Use Microsoft.SourceLink.GitHub (#192) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Context: https://github.com/xamarin/xamarin-android/commit/7b4d4b8c235b22b688db13e59771722da89f37cb Context: https://github.com/dotnet/sourcelink#githubcom-and-github-enterprise Context: https://www.hanselman.com/blog/exploring-net-cores-sourcelink-stepping-into-the-source-code-of-nuget-packages-you-dont-own Add a reference to the [Microsoft.SourceLink.GitHub NuGet package][0] so that `Microsoft.Android.Build.BaseTasks.pdb` and `Xamarin.Android.Tools.AndroidSdk.pdb` contain URLs to facilitate debugging into their corresponding assemblies without needing to checkout and build the `xamarin-android-tools` repo locally: % $HOME/.dotnet/tools/sourcelink print-urls src/Microsoft.Android.Build.BaseTasks/bin/Debug/netstandard2.0/Microsoft.Android.Build.BaseTasks.pdb ced7dccec3e466c9a37e0af78cccae49f3a5e50071988e03b90427ebc5ce8461 sha256 csharp …/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/AndroidAsyncTask.cs https://raw.githubusercontent.com/xamarin/xamarin-android-tools/76d8e7ca22c1a990096ba44d1f2cece2a3136b50/src/Microsoft.Android.Build.BaseTasks/AndroidAsyncTask.cs … % $HOME/.dotnet/tools/sourcelink test src/Microsoft.Android.Build.BaseTasks/bin/Debug/netstandard2.0/Microsoft.Android.Build.BaseTasks.pdb 2 Documents with errors: 74a35b2e0310ee692c4338531afedfe18376f44d57ebf83466e7e6a5b80e2d14 sha256 csharp /Volumes/Xamarin-Work/src/xamarin/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/obj/Debug/netstandard2.0/.NETStandard,Version=v2.0.AssemblyAttributes.cs https://raw.githubusercontent.com/xamarin/xamarin-android-tools/76d8e7ca22c1a990096ba44d1f2cece2a3136b50/src/Microsoft.Android.Build.BaseTasks/obj/Debug/netstandard2.0/.NETStandard,Version=v2.0.AssemblyAttributes.cs error: url failed NotFound: Not Found 79807509946ed38a1ca2faf491c7259a0fee80fab1cc86b3e6750608b4a6d065 sha256 csharp /Volumes/Xamarin-Work/src/xamarin/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/obj/Debug/netstandard2.0/Microsoft.Android.Build.BaseTasks.AssemblyInfo.cs https://raw.githubusercontent.com/xamarin/xamarin-android-tools/76d8e7ca22c1a990096ba44d1f2cece2a3136b50/src/Microsoft.Android.Build.BaseTasks/obj/Debug/netstandard2.0/Microsoft.Android.Build.BaseTasks.AssemblyInfo.cs error: url failed NotFound: Not Found sourcelink test failed These errors are expected, as they are generated files. [0]: https://www.nuget.org/packages/Microsoft.SourceLink.GitHub --- external/xamarin-android-tools/Directory.Build.targets | 5 +++++ .../Microsoft.Android.Build.BaseTasks.csproj | 4 ++++ .../Xamarin.Android.Tools.AndroidSdk.csproj | 1 + 3 files changed, 10 insertions(+) diff --git a/external/xamarin-android-tools/Directory.Build.targets b/external/xamarin-android-tools/Directory.Build.targets index ac1c9840b3c..635c2cdfd40 100644 --- a/external/xamarin-android-tools/Directory.Build.targets +++ b/external/xamarin-android-tools/Directory.Build.targets @@ -18,4 +18,9 @@ Project="$([System.IO.Path]::GetDirectoryName($(MSBuildThisFileDirectory))).override.targets" Condition=" Exists('$([System.IO.Path]::GetDirectoryName($(MSBuildThisFileDirectory))).override.targets') " /> + + + + + diff --git a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Microsoft.Android.Build.BaseTasks.csproj b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Microsoft.Android.Build.BaseTasks.csproj index 5a3b2f51ded..684707240d0 100644 --- a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Microsoft.Android.Build.BaseTasks.csproj +++ b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Microsoft.Android.Build.BaseTasks.csproj @@ -23,4 +23,8 @@ + + + + diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj index c1b258aaaa5..f304453a7af 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj @@ -30,6 +30,7 @@ all runtime; build; native; contentfiles; analyzers + From e4e6cdf15d7f059be147cdd699ddcaf5033f64db Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Wed, 24 Aug 2022 20:29:44 +0200 Subject: [PATCH 161/308] Bump to mono/mono.posix@d8994ca, xamarin/LibZipSharp@98e9173 (#193) Changes: https://github.com/mono/mono.posix/compare/e1269a564a022b143769785714968a52091c10a4...d8994caaf29f0a95022ee4d1d51ae0a55150ce92 * mono/mono.posix@d8994ca: Remove Windows support completely for now Fixes an issue in which Mono.Unix would try to resolve `libc` P/Invokes by looking for the `msvcrt` library on Unix machines. * mono/mono.posix@74d504f: Fix yaml template path * mono/mono.posix@127cf9e: [build] Don't rebuild managed code on packaging time on Windows Changes: https://github.com/xamarin/LibZipSharp/compare/2.0.4...2.0.7 * xamarin/LibZipSharp@98e9173: Bump version to 2.0.7 * xamarin/LibZipSharp@6e1e1b3: Localized file check-in by OneLocBuild Task: Build definition ID 11678: Build ID 6581869 (#119) * xamarin/LibZipSharp@1c05430: LEGO: Merge pull request 118 * xamarin/LibZipSharp@06d44d8: Localized file check-in by OneLocBuild Task: Build definition ID 11678: Build ID 6570668 (#117) * xamarin/LibZipSharp@37f3894: LEGO: Merge pull request 116 * xamarin/LibZipSharp@6c0edc5: Update libzip and zlib submodules (#115) * xamarin/LibZipSharp@acd9a54: [Localization] Switch from xlf to resx files (#112) * xamarin/LibZipSharp@3cece80: LEGO: Merge pull request 114 * xamarin/LibZipSharp@fe336b4: LEGO: Merge pull request 113 * xamarin/LibZipSharp@9aee99a: [Localization] Add OneLocBuild job (#111) * xamarin/LibZipSharp@bdfa9f8: Bump Mono.Unix to 7.1.0-final.1.21458.1 (#110) --- .../MSBuildReferences.projitems | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems index 50f867c6074..183fecd5c41 100644 --- a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems +++ b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems @@ -5,8 +5,8 @@ 16.10.0 - 2.0.4 - 7.0.0-final.1.21369.2 + 2.0.7 + 7.1.0-final.1.21458.1 From 221db21da638df6e0e00693e1aa24e87bccfcd16 Mon Sep 17 00:00:00 2001 From: Eric Erhardt Date: Thu, 15 Sep 2022 15:53:51 -0500 Subject: [PATCH 162/308] Use Environment.SpecialFolder.UserProfile, not SpecialFolder.Personal (#194) Context: https://github.com/dotnet/runtime/pull/68610 In Mono and .NET prior to .NET 8, the [`System.Environment.SpecialFolder`][0]`.Personal` enum value would refer to `$HOME` on Unix platforms. This will be changing in .NET 8, such that `Environment.SpecialFolder.Personal` will instead refer to `$XDG_DOCUMENTS_DIR` (if set) or `$HOME/Documents`. This is for "semantic compatibility" with .NET on Windows. Replace usage of `Environment.SpecialFolder.Personal` with `Environment.SpecialFolder.UserProfile`, so that our code continues to work as expected under .NET 8. [0]: https://docs.microsoft.com/en-us/dotnet/api/system.environment.specialfolder?view=net-6.0 --- .../Jdks/MicrosoftDistJdkLocations.cs | 2 +- .../src/Xamarin.Android.Tools.AndroidSdk/OS.cs | 6 +++--- .../Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkUnix.cs | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Jdks/MicrosoftDistJdkLocations.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Jdks/MicrosoftDistJdkLocations.cs index e463a5e7de4..00827c90ed2 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Jdks/MicrosoftDistJdkLocations.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Jdks/MicrosoftDistJdkLocations.cs @@ -21,7 +21,7 @@ static IEnumerable GetMacOSMicrosoftDistJdkPaths () var jdks = AppDomain.CurrentDomain.GetData ($"GetMacOSMicrosoftJdkPaths jdks override! {typeof (JdkInfo).AssemblyQualifiedName}") ?.ToString (); if (jdks == null) { - var home = Environment.GetFolderPath (Environment.SpecialFolder.Personal); + var home = Environment.GetFolderPath (Environment.SpecialFolder.UserProfile); jdks = Path.Combine (home, "Library", "Developer", "Xamarin", "jdk"); } if (!Directory.Exists (jdks)) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/OS.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/OS.cs index e0e07f7e807..b2247489753 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/OS.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/OS.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Runtime.InteropServices; using System.IO; @@ -69,15 +69,15 @@ static string GetProgramFilesX86 () internal static string GetXamarinAndroidCacheDir () { if (IsMac) { - var home = Environment.GetFolderPath (Environment.SpecialFolder.Personal); + var home = Environment.GetFolderPath (Environment.SpecialFolder.UserProfile); return Path.Combine (home, "Library", "Caches", "Xamarin.Android"); } else if (IsWindows) { var localAppData = Environment.GetFolderPath (Environment.SpecialFolder.LocalApplicationData); return Path.Combine (localAppData, "Xamarin.Android", "Cache"); } else { - var home = Environment.GetFolderPath (Environment.SpecialFolder.Personal); var xdgCacheHome = Environment.GetEnvironmentVariable ("XDG_CACHE_HOME"); if (string.IsNullOrEmpty (xdgCacheHome)) { + var home = Environment.GetFolderPath (Environment.SpecialFolder.UserProfile); xdgCacheHome = Path.Combine (home, ".cache"); } return Path.Combine (xdgCacheHome, "Xamarin.Android"); diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkUnix.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkUnix.cs index c43b8ea1866..82302a10281 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkUnix.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkUnix.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Linq; using System.Collections.Generic; using System.Diagnostics; @@ -111,7 +111,7 @@ protected override IEnumerable GetAllAvailableAndroidSdks () } // Check some hardcoded paths for good measure - var macSdkPath = Path.Combine (Environment.GetFolderPath (Environment.SpecialFolder.Personal), "Library", "Android", "sdk"); + var macSdkPath = Path.Combine (Environment.GetFolderPath (Environment.SpecialFolder.UserProfile), "Library", "Android", "sdk"); yield return macSdkPath; } From 9ac73511d2f7de006c0b72b286b57b8aeac07fba Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Tue, 11 Oct 2022 14:32:07 -0500 Subject: [PATCH 163/308] Move from `netcoreapp3.1` to `net6.0` (#195) I believe this is a good idea anyway, but this should assist with: https://github.com/xamarin/xamarin-android/pull/7451 Which is hitting the error: error NU1102: Unable to find package Microsoft.NETCore.App.Host.osx-x64 with version (= 3.1.31) .NET Core 3.1.31 is not released yet and is likely on a private feed. Also pass `6.0.x` to the [`UseDotNet`][0] yaml task. After these changes, the build failed with: error MSB3971: The reference assemblies for ".NETFramework,Version=v6.0" were not found. You might be using an older .NET SDK to target .NET 5.0 or higher. Update Visual Studio and/or your .NET SDK. Mono MSBuild probably can't build .NET 6 projects? So I fixed various things in the build: * Use `dotnet build` instead of MSBuild, remove NuGet commands * No tests were running: [warning] Project file(s) matching the specified pattern were not found. * Fixed the `$(XATBuildingForNetCoreApp)` property, so tests run. * Upload artifacts based on the pool name: `$(vmImage)` * Use `$(PackageOutputPath)` instead of `$(OutputPath)` * `PublishBuildArtifacts` is deprecated use `PublishPipelineArtifact` * Save `.binlog` files as artifacts [0]: https://learn.microsoft.com/azure/devops/pipelines/tasks/tool/dotnet-core-tool-installer --- .../Directory.Build.props | 2 +- .../azure-pipelines.yaml | 30 +++++-------------- .../Microsoft.Android.Build.BaseTasks.csproj | 2 +- .../Xamarin.Android.Tools.AndroidSdk.csproj | 2 +- ...osoft.Android.Build.BaseTasks-Tests.csproj | 2 +- ...arin.Android.Tools.AndroidSdk-Tests.csproj | 2 +- .../tools/ls-jdks/ls-jdks.csproj | 2 +- 7 files changed, 13 insertions(+), 29 deletions(-) diff --git a/external/xamarin-android-tools/Directory.Build.props b/external/xamarin-android-tools/Directory.Build.props index d03b71f2e0d..33d982fb3d9 100644 --- a/external/xamarin-android-tools/Directory.Build.props +++ b/external/xamarin-android-tools/Directory.Build.props @@ -15,7 +15,7 @@ obj\ - + True diff --git a/external/xamarin-android-tools/azure-pipelines.yaml b/external/xamarin-android-tools/azure-pipelines.yaml index 1d40960db36..3918569cabb 100644 --- a/external/xamarin-android-tools/azure-pipelines.yaml +++ b/external/xamarin-android-tools/azure-pipelines.yaml @@ -12,7 +12,7 @@ pr: # Global variables variables: - DotNetCoreVersion: 3.1.100 + DotNetCoreVersion: 6.0.x jobs: - job: build @@ -42,22 +42,8 @@ jobs: inputs: version: $(DotNetCoreVersion) - - task: NuGetToolInstaller@0 - displayName: 'Install NuGet' - inputs: - versionSpec: 5.x - - - task: NuGetCommand@2 - displayName: 'NuGet Restore' - inputs: - restoreSolution: Xamarin.Android.Tools.sln - feedsToUse: config - nugetConfigPath: NuGet.config - - - task: MSBuild@1 + - script: dotnet build Xamarin.Android.Tools.sln -bl:$(Build.ArtifactStagingDirectory)/build.binlog displayName: 'Build solution Xamarin.Android.Tools.sln' - inputs: - solution: Xamarin.Android.Tools.sln - task: DotNetCoreCLI@2 displayName: 'Run Tests' @@ -73,15 +59,13 @@ jobs: Write-Host "##vso[task.setvariable variable=xat.nuget.version]$version" condition: and(succeeded(), eq(variables['agent.os'], 'Windows_NT')) - - task: MSBuild@1 + - script: dotnet pack src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj -p:Version=$(xat.nuget.version) -p:PackageOutputPath=$(Build.ArtifactStagingDirectory) -bl:$(Build.ArtifactStagingDirectory)/pack.binlog displayName: 'Build NuGet' - inputs: - solution: 'src\Xamarin.Android.Tools.AndroidSdk\Xamarin.Android.Tools.AndroidSdk.csproj' - msbuildArguments: '/t:pack /p:Version=$(xat.nuget.version) /p:OutputPath=$(Build.ArtifactStagingDirectory)' condition: and(succeeded(), eq(variables['agent.os'], 'Windows_NT')) - - task: PublishBuildArtifacts@1 + - task: PublishPipelineArtifact@1 displayName: Upload Artifacts inputs: - pathtoPublish: $(Build.ArtifactStagingDirectory) - condition: and(succeeded(), eq(variables['agent.os'], 'Windows_NT')) + path: $(Build.ArtifactStagingDirectory) + artifactName: $(vmImage) + condition: always() diff --git a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Microsoft.Android.Build.BaseTasks.csproj b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Microsoft.Android.Build.BaseTasks.csproj index 684707240d0..f3602ce5e84 100644 --- a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Microsoft.Android.Build.BaseTasks.csproj +++ b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Microsoft.Android.Build.BaseTasks.csproj @@ -3,7 +3,7 @@ - netstandard2.0;netcoreapp3.1 + netstandard2.0;net6.0 Microsoft.Android.Build.Tasks true true diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj index f304453a7af..eeef803bc15 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj @@ -1,7 +1,7 @@ - netstandard2.0;netcoreapp3.1 + netstandard2.0;net6.0 8.0 enable INTERNAL_NULLABLE_ATTRIBUTES diff --git a/external/xamarin-android-tools/tests/Microsoft.Android.Build.BaseTasks-Tests/Microsoft.Android.Build.BaseTasks-Tests.csproj b/external/xamarin-android-tools/tests/Microsoft.Android.Build.BaseTasks-Tests/Microsoft.Android.Build.BaseTasks-Tests.csproj index 8e0a2a442c9..80ecc3d34c0 100644 --- a/external/xamarin-android-tools/tests/Microsoft.Android.Build.BaseTasks-Tests/Microsoft.Android.Build.BaseTasks-Tests.csproj +++ b/external/xamarin-android-tools/tests/Microsoft.Android.Build.BaseTasks-Tests/Microsoft.Android.Build.BaseTasks-Tests.csproj @@ -4,7 +4,7 @@ - netcoreapp3.1 + net6.0 Microsoft.Android.Build.BaseTasks.Tests false $(TestOutputFullPath) diff --git a/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/Xamarin.Android.Tools.AndroidSdk-Tests.csproj b/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/Xamarin.Android.Tools.AndroidSdk-Tests.csproj index 4100134c6e8..722a4910fab 100644 --- a/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/Xamarin.Android.Tools.AndroidSdk-Tests.csproj +++ b/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/Xamarin.Android.Tools.AndroidSdk-Tests.csproj @@ -4,7 +4,7 @@ - netcoreapp3.1 + net6.0 true ..\..\product.snk false diff --git a/external/xamarin-android-tools/tools/ls-jdks/ls-jdks.csproj b/external/xamarin-android-tools/tools/ls-jdks/ls-jdks.csproj index 751f34d2e36..af1eee9db06 100644 --- a/external/xamarin-android-tools/tools/ls-jdks/ls-jdks.csproj +++ b/external/xamarin-android-tools/tools/ls-jdks/ls-jdks.csproj @@ -2,7 +2,7 @@ Exe - net472;netcoreapp3.1 + net472;net6.0 Xamarin.Android.Tools false $(ToolOutputFullPath) From 7c4fabafeb85b57f8dd9b25a45073dfa6bbe4e2c Mon Sep 17 00:00:00 2001 From: Dean Ellis Date: Tue, 8 Nov 2022 21:24:22 +0000 Subject: [PATCH 164/308] Enable CodeQL (#197) --- external/xamarin-android-tools/azure-pipelines.yaml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/external/xamarin-android-tools/azure-pipelines.yaml b/external/xamarin-android-tools/azure-pipelines.yaml index 3918569cabb..d0f314fbb17 100644 --- a/external/xamarin-android-tools/azure-pipelines.yaml +++ b/external/xamarin-android-tools/azure-pipelines.yaml @@ -12,7 +12,10 @@ pr: # Global variables variables: - DotNetCoreVersion: 6.0.x + - name: DotNetCoreVersion + value: 6.0.x + - name: Codeql.Enabled + value: True jobs: - job: build From 828da705e9f2acc5eba0d4ef34b60babea9c9afa Mon Sep 17 00:00:00 2001 From: Jonathan Pryor Date: Thu, 17 Nov 2022 15:25:01 -0500 Subject: [PATCH 165/308] [build] Update NuGet package versions (#196) Context: https://dev.azure.com/xamarin/public/_componentGovernance/115226/alert/3150206?typeId=5477311 Context: https://dev.azure.com/xamarin/public/_componentGovernance/115226/alert/6875331?typeId=5477311 Context: https://github.com/xamarin/java.interop/commit/53182615707bd4181564c0be388d3b09c1d11a20 [Component Governance][0] is a Microsoft internal tool which checks for known security issues in product dependencies. It is currently reporting a defects in xamarin-android-tools due to use of older `System.Net.Http` packages ([CVE-2018-8292][0]) and older `System.Security.Cryptography.X509Certificates` packages ([CVE-2017-11770][1]): > **Location** > > * /s/packages/system.net.http/4.1.0/system.net.http.4.1.0.nupkg > * /s/packages/system.net.http/4.1.0/system.net.http.nuspec > * /s/packages/system.security.cryptography.x509certificates/4.1.0/system.security.cryptography.x509certificates.4.1.0.nupkg > * /s/packages/system.security.cryptography.x509certificates/4.1.0/system.security.cryptography.x509certificates.nuspec The "odd" thing is that xamarin-android-tools doesn't *use* either of these dependencies! They appear to be pulled in via package dependencies. Rework how we use `@(PackageReference)` so that `Directory.Build.targets` uses [the `Update` attribute][2] to centralize package version specification, except within `MSBuildReferences.projitems` as it's ``ed by xamarin-android. Update most NuGet package versions to the latest versions provided by `dotnet-public` or `dotnet-eng` (which may not be the latest versions on NuGet.org). While stable versions are generally preferred, we use Microsoft.NET.Test.Sdk version 17.5.0-preview-20221003-04 to ensure that we avoid Newtonsoft.Json 9.0.1 issues a'la xamarin/java.interop@53182615. NuGet Package Version Bumps: * Microsoft.Build : `16.10.0` -> `17.3.2` * Microsoft.Build.Framework : `16.10.0` -> `17.3.2` * Microsoft.Build.Tasks.Core : `16.10.0` -> `17.3.2` * Microsoft.Build.Utilities.Core : `16.10.0` -> `17.3.2` * Microsoft.NET.Test.Sdk : `16.5.0` -> `17.5.0-preview-20221003-04` * nunit : `3.12.0` -> `3.13.2` * NUnit3TestAdapter : `3.16.1` -> `4.0.0` [0]: https://nvd.nist.gov/vuln/detail/CVE-2018-8292 [1]: https://nvd.nist.gov/vuln/detail/CVE-2017-11770 [2]: https://learn.microsoft.com/en-us/visualstudio/msbuild/item-element-msbuild?view=vs-2022#attributes-and-elements --- external/xamarin-android-tools/Directory.Build.targets | 7 ++++++- .../MSBuildReferences.projitems | 2 +- .../Xamarin.Android.Tools.AndroidSdk.csproj | 2 +- .../Microsoft.Android.Build.BaseTasks-Tests.csproj | 6 +++--- .../Xamarin.Android.Tools.AndroidSdk-Tests.csproj | 6 +++--- 5 files changed, 14 insertions(+), 9 deletions(-) diff --git a/external/xamarin-android-tools/Directory.Build.targets b/external/xamarin-android-tools/Directory.Build.targets index 635c2cdfd40..984d5852c25 100644 --- a/external/xamarin-android-tools/Directory.Build.targets +++ b/external/xamarin-android-tools/Directory.Build.targets @@ -19,8 +19,13 @@ Condition=" Exists('$([System.IO.Path]::GetDirectoryName($(MSBuildThisFileDirectory))).override.targets') " /> + - + + + + + diff --git a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems index 183fecd5c41..a76b5a56c72 100644 --- a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems +++ b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems @@ -4,7 +4,7 @@ - 16.10.0 + 17.3.2 2.0.7 7.1.0-final.1.21458.1 diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj index eeef803bc15..d37e9cb6985 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj @@ -26,7 +26,7 @@ - + all runtime; build; native; contentfiles; analyzers diff --git a/external/xamarin-android-tools/tests/Microsoft.Android.Build.BaseTasks-Tests/Microsoft.Android.Build.BaseTasks-Tests.csproj b/external/xamarin-android-tools/tests/Microsoft.Android.Build.BaseTasks-Tests/Microsoft.Android.Build.BaseTasks-Tests.csproj index 80ecc3d34c0..52207653133 100644 --- a/external/xamarin-android-tools/tests/Microsoft.Android.Build.BaseTasks-Tests/Microsoft.Android.Build.BaseTasks-Tests.csproj +++ b/external/xamarin-android-tools/tests/Microsoft.Android.Build.BaseTasks-Tests/Microsoft.Android.Build.BaseTasks-Tests.csproj @@ -14,9 +14,9 @@ - - - + + + diff --git a/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/Xamarin.Android.Tools.AndroidSdk-Tests.csproj b/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/Xamarin.Android.Tools.AndroidSdk-Tests.csproj index 722a4910fab..19c4033d198 100644 --- a/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/Xamarin.Android.Tools.AndroidSdk-Tests.csproj +++ b/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/Xamarin.Android.Tools.AndroidSdk-Tests.csproj @@ -13,9 +13,9 @@ - - - + + + From 7cf9e6ef9e6874a44f824b580660ca9c93e9ce26 Mon Sep 17 00:00:00 2001 From: Peter Collins Date: Wed, 30 Nov 2022 11:20:39 -0500 Subject: [PATCH 166/308] Add reference to System.Security.Cryptography.Xml (#198) Context: https://dev.azure.com/xamarin/public/_componentGovernance/115226/alert/8008980?typeId=5585428&pipelinesTrackingFilter=1 We've receieved an alert about our usage of the 6.0.0 version of `System.Security.Cryptography.Xml`. This package is brought in through the [Microsoft.Build.Tasks.Core][0] package reference. An explicit reference to `System.Security.Cryptography.Xml` 6.0.1 should bring in the fix for CVE-2022-34716. [0]: https://www.nuget.org/packages/Microsoft.Build.Tasks.Core/17.3.2#dependencies-body-tab --- .../MSBuildReferences.projitems | 1 + 1 file changed, 1 insertion(+) diff --git a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems index a76b5a56c72..2ce698cc1e6 100644 --- a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems +++ b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems @@ -14,6 +14,7 @@ + From 00664afb58dd040a24d593f8d1ef5d2f64952cae Mon Sep 17 00:00:00 2001 From: Dean Ellis Date: Wed, 11 Jan 2023 13:27:45 +0000 Subject: [PATCH 167/308] Add support for Project Specific RegisterTaskObject. (#199) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Context: https://github.com/dotnet/maui/issues/11605 Context: https://github.com/dotnet/maui/pull/11387#issuecomment-1318738792 Context: http://github.com/xamarin/xamarin-android/commit/8bc7a3e84f95e70fe12790ac31ecd97957771cb2 In dotnet/maui#11605, when `$(AndroidEnableMarshalMethods)`=True (xamarin/xamarin-android@8bc7a3e8), the build may fail if the `.sln` contains more than one Android "App" project. We tracked this down to "undesired sharing" between project builds; the `obj` provided to [`IBuildEngine4.RegisterTaskObject()`][0] can be visible across project builds. Consider [``][1]: var marshalMethodsState = BuildEngine4.GetRegisteredTaskObjectAssemblyLocal (".:!MarshalMethods!:.", RegisteredTaskObjectLifetime.Build); Further consider a `.sln` with two "App" projects, as the "App" project build hits ``. The lifetime of `.Build` is *not* tied to the the `Build` target of a given `.csproj`; rather, it's for the *build process*. This can result in: 1. `dotnet build Project.sln` is run; `Project.sln` references `App1.csproj` and `App2.csproj`. 2. `App1.csproj` is built. 3. `App1.csproj` calls ``. 4. `App2.csproj` is later built as part in the process, and *also* calls ``. In particular note the key within ``: `".:!MarshalMethods!:."`. This value is unchanged, and means that that when `App2.csproj` is built, it will be using the same key as was used with `App1.csproj`, and thus could be inadvertently using data intended for `App1.csproj`! This would result build errors: …\Xamarin.Android.Common.targets(1717,3): error XAGPM7009: System.InvalidOperationException: Unable to translate assembly name 'Xamarin.AndroidX.Activity' to its index …\Xamarin.Android.Common.targets(1717,3): error XAGPM7009: at Xamarin.Android.Tasks.MarshalMethodsNativeAssemblyGenerator.WriteNativeMethods(LlvmIrGenerator generator, Dictionary`2 asmNameToIndex, LlvmIrVariableReference get_function_pointer_ref) …\Xamarin.Android.Common.targets(1717,3): error XAGPM7009: at Xamarin.Android.Tasks.MarshalMethodsNativeAssemblyGenerator.Write(LlvmIrGenerator generator) …\Xamarin.Android.Common.targets(1717,3): error XAGPM7009: at Xamarin.Android.Tasks.LLVMIR.LlvmIrComposer.Write(AndroidTargetArch arch, StreamWriter output, String fileName) …\Xamarin.Android.Common.targets(1717,3): error XAGPM7009: at Xamarin.Android.Tasks.GeneratePackageManagerJava.AddEnvironment() …\Xamarin.Android.Common.targets(1717,3): error XAGPM7009: at Xamarin.Android.Tasks.GeneratePackageManagerJava.RunTask() The sharing of `RegisterTaskObject` data across project builds is rarely desirable. There are a few instances where it is safe to share the registered objects between projects, e.g. `java` version information (keyed on `java` path). However, most of the time it is specific to the project that is being built. Historically we have probably got away with this because "most" users only have one project. Update the `MSBuildExtensions` extension methods to include an additional `RegisterTaskObjectKeyFlags` parameter: [Flags] public enum RegisterTaskObjectKeyFlags { None = 0, IncludeProjectFile = 1 << 0, } Allowing: var marshalMethodsState = BuildEngine4.GetRegisteredTaskObjectAssemblyLocal ( ".:!MarshalMethods!:.", RegisteredTaskObjectLifetime.Build, RegisterTaskObjectKeyFlags.IncludeProjectFile ); When `RegisterTaskObjectKeyFlags.IncludeProjectFile` is specified, then [`IBuildEngine.ProjectFileOfTaskNode`][2] is used as part of the key with `RegisterTaskObject()`. This helps ensure that builds in different `.csproj` files will result in different keys. The previous `MSBuildExtensions.GetRegisteredTaskObjectAssemblyLocal()` and related overloads have been updated so that `RegisterTaskObjectKeyFlags.IncludeProjectFile` is used by default. [0]: https://learn.microsoft.com/en-us/dotnet/api/microsoft.build.framework.ibuildengine4.registertaskobject?view=msbuild-17-netcore [1]: https://github.com/xamarin/xamarin-android/blob/c92ae5eb9fdcb3a2fd7c20f5b42dddf8b3ea781a/src/Xamarin.Android.Build.Tasks/Tasks/GeneratePackageManagerJava.cs#L407 [2]: https://learn.microsoft.com/en-us/dotnet/api/microsoft.build.framework.ibuildengine.projectfileoftasknode?view=msbuild-17-netcore --- .../MSBuildExtensions.cs | 64 +++++++++++++++++-- 1 file changed, 58 insertions(+), 6 deletions(-) diff --git a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildExtensions.cs b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildExtensions.cs index d50b3d9367d..757b7540d52 100644 --- a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildExtensions.cs +++ b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildExtensions.cs @@ -13,6 +13,12 @@ namespace Microsoft.Android.Build.Tasks { + [Flags] + public enum RegisterTaskObjectKeyFlags { + None = 0, + IncludeProjectFile = 1 << 0, + } + public static class MSBuildExtensions { public static void LogDebugMessage (this TaskLoggingHelper log, string message, params object[] messageArgs) @@ -252,34 +258,80 @@ public static void SetDestinationSubPath (this ITaskItem assembly) /// /// IBuildEngine4.RegisterTaskObject, but adds the current assembly path into the key /// + [Obsolete ("Use RegisterTaskObjectAssemblyLocal (engine, key, value, allowEarlyCollection, lifetime, flags) instead.")] public static void RegisterTaskObjectAssemblyLocal (this IBuildEngine4 engine, object key, object value, RegisteredTaskObjectLifetime lifetime, bool allowEarlyCollection = false) => - engine.RegisterTaskObject ((AssemblyLocation, key), value, lifetime, allowEarlyCollection); + RegisterTaskObjectAssemblyLocal (engine, key, value, lifetime, allowEarlyCollection: false, flags: RegisterTaskObjectKeyFlags.IncludeProjectFile); + + /// + /// IBuildEngine4.RegisterTaskObject, but adds the current assembly path into the key + /// + public static void RegisterTaskObjectAssemblyLocal (this IBuildEngine4 engine, object key, object value, RegisteredTaskObjectLifetime lifetime, bool allowEarlyCollection = false, RegisterTaskObjectKeyFlags flags = RegisterTaskObjectKeyFlags.IncludeProjectFile) => + engine.RegisterTaskObject (engine.GetKey (AssemblyLocation, key, flags), value, lifetime, allowEarlyCollection); /// /// IBuildEngine4.GetRegisteredTaskObject, but adds the current assembly path into the key /// + [Obsolete ("Use GetRegisteredTaskObjectAssemblyLocal (engine, key, lifetime, flags) instead.")] public static object GetRegisteredTaskObjectAssemblyLocal (this IBuildEngine4 engine, object key, RegisteredTaskObjectLifetime lifetime) => - engine.GetRegisteredTaskObject ((AssemblyLocation, key), lifetime); + GetRegisteredTaskObjectAssemblyLocal (engine, key, lifetime, flags: RegisterTaskObjectKeyFlags.IncludeProjectFile); + + /// + /// IBuildEngine4.GetRegisteredTaskObject, but adds the current assembly path into the key + /// + public static object GetRegisteredTaskObjectAssemblyLocal (this IBuildEngine4 engine, object key, RegisteredTaskObjectLifetime lifetime, RegisterTaskObjectKeyFlags flags = RegisterTaskObjectKeyFlags.IncludeProjectFile) => + engine.GetRegisteredTaskObject (engine.GetKey (AssemblyLocation, key, flags), lifetime); + /// /// Generic version of IBuildEngine4.GetRegisteredTaskObject, but adds the current assembly path into the key /// + [Obsolete ("Use GetRegisteredTaskObjectAssemblyLocal (engine, key, lifetime, flags) instead.")] public static T GetRegisteredTaskObjectAssemblyLocal (this IBuildEngine4 engine, object key, RegisteredTaskObjectLifetime lifetime) - where T : class => - engine.GetRegisteredTaskObject ((AssemblyLocation, key), lifetime) as T; + where T : class => GetRegisteredTaskObjectAssemblyLocal (engine, key, lifetime, flags: RegisterTaskObjectKeyFlags.IncludeProjectFile); + /// + /// Generic version of IBuildEngine4.GetRegisteredTaskObject, but adds the current assembly path into the key + /// + public static T GetRegisteredTaskObjectAssemblyLocal (this IBuildEngine4 engine, object key, RegisteredTaskObjectLifetime lifetime, RegisterTaskObjectKeyFlags flags = RegisterTaskObjectKeyFlags.IncludeProjectFile) + where T : class => + engine.GetRegisteredTaskObject (engine.GetKey (AssemblyLocation, key, flags), lifetime) as T; /// /// IBuildEngine4.UnregisterTaskObject, but adds the current assembly path into the key /// + [Obsolete ("Use UnregisterTaskObjectAssemblyLocal (engine, key, lifetime, flags) instead.")] public static object UnregisterTaskObjectAssemblyLocal (this IBuildEngine4 engine, object key, RegisteredTaskObjectLifetime lifetime) => - engine.UnregisterTaskObject ((AssemblyLocation, key), lifetime); + UnregisterTaskObjectAssemblyLocal (engine, key, lifetime, flags: RegisterTaskObjectKeyFlags.IncludeProjectFile); + + /// + /// IBuildEngine4.UnregisterTaskObject, but adds the current assembly path into the key + /// + public static object UnregisterTaskObjectAssemblyLocal (this IBuildEngine4 engine, object key, RegisteredTaskObjectLifetime lifetime, RegisterTaskObjectKeyFlags flags = RegisterTaskObjectKeyFlags.IncludeProjectFile) => + engine.UnregisterTaskObject (engine.GetKey (AssemblyLocation, key, flags), lifetime); /// /// Generic version of IBuildEngine4.UnregisterTaskObject, but adds the current assembly path into the key /// + [Obsolete ("Use UnregisterTaskObjectAssemblyLocal (engine, key, lifetime, flags) instead.")] public static T UnregisterTaskObjectAssemblyLocal (this IBuildEngine4 engine, object key, RegisteredTaskObjectLifetime lifetime) + where T : class => UnregisterTaskObjectAssemblyLocal (engine, key, lifetime, flags: RegisterTaskObjectKeyFlags.IncludeProjectFile); + + /// + /// Generic version of IBuildEngine4.UnregisterTaskObject, but adds the current assembly path into the key + /// + public static T UnregisterTaskObjectAssemblyLocal (this IBuildEngine4 engine, object key, RegisteredTaskObjectLifetime lifetime, RegisterTaskObjectKeyFlags flags = RegisterTaskObjectKeyFlags.IncludeProjectFile) where T : class => - engine.UnregisterTaskObject ((AssemblyLocation, key), lifetime) as T; + engine.UnregisterTaskObject (engine.GetKey (AssemblyLocation, key, flags), lifetime) as T; + + /// + /// Method to calculate the key for the RegisterTaskObject. This is based on the + /// RegisterTaskObjectKeyFlags which are passed. + /// + static object GetKey (this IBuildEngine4 engine, string location, object key, RegisterTaskObjectKeyFlags flags) + { + return ((flags & RegisterTaskObjectKeyFlags.IncludeProjectFile) != 0) + ? (location, key, engine.ProjectFileOfTaskNode) + : (location, key, string.Empty); + } } } From 4589fbdd8defbcc0d1a7af8ece1576cce11417d0 Mon Sep 17 00:00:00 2001 From: Jonathan Pryor Date: Wed, 11 Jan 2023 14:20:21 -0500 Subject: [PATCH 168/308] Fix CS0121 ambiguity errors. (#200) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Context: 00664afb58dd040a24d593f8d1ef5d2f64952cae When attempting to update xamarin/xamarin-android to use 4ea2d5ad, lots of CS0121 errors appeared: …/src/Xamarin.Android.Build.Tasks/Tasks/FilterAssemblies.cs(80,18): error CS0121: The call is ambiguous between the following methods or properties: 'MSBuildExtensions.RegisterTaskObjectAssemblyLocal(IBuildEngine4, object, object, RegisteredTaskObjectLifetime, bool)' and 'MSBuildExtensions.RegisterTaskObjectAssemblyLocal(IBuildEngine4, object, object, RegisteredTaskObjectLifetime, bool, RegisterTaskObjectKeyFlags)' …/src/Xamarin.Android.Build.Tasks/Tasks/FilterAssemblies.cs(87,18): error CS0121: The call is ambiguous between the following methods or properties: 'MSBuildExtensions.RegisterTaskObjectAssemblyLocal(IBuildEngine4, object, object, RegisteredTaskObjectLifetime, bool)' and 'MSBuildExtensions.RegisterTaskObjectAssemblyLocal(IBuildEngine4, object, object, RegisteredTaskObjectLifetime, bool, RegisterTaskObjectKeyFlags)' …/src/Xamarin.Android.Build.Tasks/Tasks/FilterAssemblies.cs(94,18): error CS0121: The call is ambiguous between the following methods or properties: 'MSBuildExtensions.RegisterTaskObjectAssemblyLocal(IBuildEngine4, object, object, RegisteredTaskObjectLifetime, bool)' and 'MSBuildExtensions.RegisterTaskObjectAssemblyLocal(IBuildEngine4, object, object, RegisteredTaskObjectLifetime, bool, RegisterTaskObjectKeyFlags)' …/src/Xamarin.Android.Build.Tasks/Tasks/FilterAssemblies.cs(98,17): error CS0121: The call is ambiguous between the following methods or properties: 'MSBuildExtensions.RegisterTaskObjectAssemblyLocal(IBuildEngine4, object, object, RegisteredTaskObjectLifetime, bool)' and 'MSBuildExtensions.RegisterTaskObjectAssemblyLocal(IBuildEngine4, object, object, RegisteredTaskObjectLifetime, bool, RegisterTaskObjectKeyFlags)' …/src/Xamarin.Android.Build.Tasks/Tasks/GenerateCompressedAssembliesNativeSourceFiles.cs(72,17): error CS0121: The call is ambiguous between the following methods or properties: 'MSBuildExtensions.RegisterTaskObjectAssemblyLocal(IBuildEngine4, object, object, RegisteredTaskObjectLifetime, bool)' and 'MSBuildExtensions.RegisterTaskObjectAssemblyLocal(IBuildEngine4, object, object, RegisteredTaskObjectLifetime, bool, RegisterTaskObjectKeyFlags)' …/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJniRemappingNativeCode.cs(95,17): error CS0121: The call is ambiguous between the following methods or properties: 'MSBuildExtensions.RegisterTaskObjectAssemblyLocal(IBuildEngine4, object, object, RegisteredTaskObjectLifetime, bool)' and 'MSBuildExtensions.RegisterTaskObjectAssemblyLocal(IBuildEngine4, object, object, RegisteredTaskObjectLifetime, bool, RegisterTaskObjectKeyFlags)' …/src/Xamarin.Android.Build.Tasks/Tasks/WriteLockFile.cs(35,19): error CS0121: The call is ambiguous between the following methods or properties: 'MSBuildExtensions.RegisterTaskObjectAssemblyLocal(IBuildEngine4, object, object, RegisteredTaskObjectLifetime, bool)' and 'MSBuildExtensions.RegisterTaskObjectAssemblyLocal(IBuildEngine4, object, object, RegisteredTaskObjectLifetime, bool, RegisterTaskObjectKeyFlags)' …/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs(524,18): error CS0121: The call is ambiguous between the following methods or properties: 'MSBuildExtensions.RegisterTaskObjectAssemblyLocal(IBuildEngine4, object, object, RegisteredTaskObjectLifetime, bool)' and 'MSBuildExtensions.RegisterTaskObjectAssemblyLocal(IBuildEngine4, object, object, RegisteredTaskObjectLifetime, bool, RegisterTaskObjectKeyFlags)' …/src/Xamarin.Android.Build.Tasks/Tasks/GenerateJavaStubs.cs(578,17): error CS0121: The call is ambiguous between the following methods or properties: 'MSBuildExtensions.RegisterTaskObjectAssemblyLocal(IBuildEngine4, object, object, RegisteredTaskObjectLifetime, bool)' and 'MSBuildExtensions.RegisterTaskObjectAssemblyLocal(IBuildEngine4, object, object, RegisteredTaskObjectLifetime, bool, RegisterTaskObjectKeyFlags)' …/src/Xamarin.Android.Build.Tasks/Utilities/MonoAndroidHelper.cs(393,11): error CS0121: The call is ambiguous between the following methods or properties: 'MSBuildExtensions.RegisterTaskObjectAssemblyLocal(IBuildEngine4, object, object, RegisteredTaskObjectLifetime, bool)' and 'MSBuildExtensions.RegisterTaskObjectAssemblyLocal(IBuildEngine4, object, object, RegisteredTaskObjectLifetime, bool, RegisterTaskObjectKeyFlags)' …/src/Xamarin.Android.Build.Tasks/Utilities/MonoAndroidHelper.cs(440,11): error CS0121: The call is ambiguous between the following methods or properties: 'MSBuildExtensions.RegisterTaskObjectAssemblyLocal(IBuildEngine4, object, object, RegisteredTaskObjectLifetime, bool)' and 'MSBuildExtensions.RegisterTaskObjectAssemblyLocal(IBuildEngine4, object, object, RegisteredTaskObjectLifetime, bool, RegisterTaskObjectKeyFlags)' …/src/Xamarin.Android.Build.Tasks/Utilities/MonoAndroidHelper.cs(501,11): error CS0121: The call is ambiguous between the following methods or properties: 'MSBuildExtensions.RegisterTaskObjectAssemblyLocal(IBuildEngine4, object, object, RegisteredTaskObjectLifetime, bool)' and 'MSBuildExtensions.RegisterTaskObjectAssemblyLocal(IBuildEngine4, object, object, RegisteredTaskObjectLifetime, bool, RegisterTaskObjectKeyFlags)' This is because when given e.g.: partial class MSBuildExtensions { public static void RegisterTaskObjectAssemblyLocal (this IBuildEngine4 engine, object key, object value, RegisteredTaskObjectLifetime lifetime, bool allowEarlyCollection = false); public static void RegisterTaskObjectAssemblyLocal (this IBuildEngine4 engine, object key, object value, RegisteredTaskObjectLifetime lifetime, bool allowEarlyCollection = false, RegisterTaskObjectKeyFlags flags = RegisterTaskObjectKeyFlags.IncludeProjectFile); } then this expression: engine.RegisterTaskObjectAssemblyLocal(key, value, lifetime); has two possible matches, due to the default parameters. Fix this by *removing* the default `RegisteredTaskObjectLifetime` parameter value, and (when appropriate) adding an overload: partial class MSBuildExtensions { public static void RegisterTaskObjectAssemblyLocal (this IBuildEngine4 engine, object key, object value, RegisteredTaskObjectLifetime lifetime, bool allowEarlyCollection = false); public static void RegisterTaskObjectAssemblyLocal (this IBuildEngine4 engine, object key, object value, RegisteredTaskObjectLifetime lifetime, RegisterTaskObjectKeyFlags flagse); public static void RegisterTaskObjectAssemblyLocal (this IBuildEngine4 engine, object key, object value, RegisteredTaskObjectLifetime lifetime, bool allowEarlyCollection, RegisterTaskObjectKeyFlags flags); } Additionally, *remove* the `[Obsolete]` attributes. With `RegisterTaskObjectKeyFlags.IncludeProjectFile` part of the default behavior, there is no need to mark the original methods `[Obsolete]`. This allows existing code such as: var value = BuildEngine4.GetRegisteredTaskObjectAssemblyLocal (key, Lifetime); to remain unchanged. --- .../MSBuildExtensions.cs | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildExtensions.cs b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildExtensions.cs index 757b7540d52..f2c39b1a09b 100644 --- a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildExtensions.cs +++ b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildExtensions.cs @@ -258,68 +258,69 @@ public static void SetDestinationSubPath (this ITaskItem assembly) /// /// IBuildEngine4.RegisterTaskObject, but adds the current assembly path into the key /// - [Obsolete ("Use RegisterTaskObjectAssemblyLocal (engine, key, value, allowEarlyCollection, lifetime, flags) instead.")] public static void RegisterTaskObjectAssemblyLocal (this IBuildEngine4 engine, object key, object value, RegisteredTaskObjectLifetime lifetime, bool allowEarlyCollection = false) => RegisterTaskObjectAssemblyLocal (engine, key, value, lifetime, allowEarlyCollection: false, flags: RegisterTaskObjectKeyFlags.IncludeProjectFile); /// /// IBuildEngine4.RegisterTaskObject, but adds the current assembly path into the key /// - public static void RegisterTaskObjectAssemblyLocal (this IBuildEngine4 engine, object key, object value, RegisteredTaskObjectLifetime lifetime, bool allowEarlyCollection = false, RegisterTaskObjectKeyFlags flags = RegisterTaskObjectKeyFlags.IncludeProjectFile) => + public static void RegisterTaskObjectAssemblyLocal (this IBuildEngine4 engine, object key, object value, RegisteredTaskObjectLifetime lifetime, RegisterTaskObjectKeyFlags flags) => + engine.RegisterTaskObject (engine.GetKey (AssemblyLocation, key, flags), value, lifetime, allowEarlyCollection: false); + + /// + /// IBuildEngine4.RegisterTaskObject, but adds the current assembly path into the key + /// + public static void RegisterTaskObjectAssemblyLocal (this IBuildEngine4 engine, object key, object value, RegisteredTaskObjectLifetime lifetime, bool allowEarlyCollection, RegisterTaskObjectKeyFlags flags) => engine.RegisterTaskObject (engine.GetKey (AssemblyLocation, key, flags), value, lifetime, allowEarlyCollection); /// /// IBuildEngine4.GetRegisteredTaskObject, but adds the current assembly path into the key /// - [Obsolete ("Use GetRegisteredTaskObjectAssemblyLocal (engine, key, lifetime, flags) instead.")] public static object GetRegisteredTaskObjectAssemblyLocal (this IBuildEngine4 engine, object key, RegisteredTaskObjectLifetime lifetime) => GetRegisteredTaskObjectAssemblyLocal (engine, key, lifetime, flags: RegisterTaskObjectKeyFlags.IncludeProjectFile); /// /// IBuildEngine4.GetRegisteredTaskObject, but adds the current assembly path into the key /// - public static object GetRegisteredTaskObjectAssemblyLocal (this IBuildEngine4 engine, object key, RegisteredTaskObjectLifetime lifetime, RegisterTaskObjectKeyFlags flags = RegisterTaskObjectKeyFlags.IncludeProjectFile) => + public static object GetRegisteredTaskObjectAssemblyLocal (this IBuildEngine4 engine, object key, RegisteredTaskObjectLifetime lifetime, RegisterTaskObjectKeyFlags flags) => engine.GetRegisteredTaskObject (engine.GetKey (AssemblyLocation, key, flags), lifetime); /// /// Generic version of IBuildEngine4.GetRegisteredTaskObject, but adds the current assembly path into the key /// - [Obsolete ("Use GetRegisteredTaskObjectAssemblyLocal (engine, key, lifetime, flags) instead.")] public static T GetRegisteredTaskObjectAssemblyLocal (this IBuildEngine4 engine, object key, RegisteredTaskObjectLifetime lifetime) where T : class => GetRegisteredTaskObjectAssemblyLocal (engine, key, lifetime, flags: RegisterTaskObjectKeyFlags.IncludeProjectFile); /// /// Generic version of IBuildEngine4.GetRegisteredTaskObject, but adds the current assembly path into the key /// - public static T GetRegisteredTaskObjectAssemblyLocal (this IBuildEngine4 engine, object key, RegisteredTaskObjectLifetime lifetime, RegisterTaskObjectKeyFlags flags = RegisterTaskObjectKeyFlags.IncludeProjectFile) + public static T GetRegisteredTaskObjectAssemblyLocal (this IBuildEngine4 engine, object key, RegisteredTaskObjectLifetime lifetime, RegisterTaskObjectKeyFlags flags) where T : class => engine.GetRegisteredTaskObject (engine.GetKey (AssemblyLocation, key, flags), lifetime) as T; /// /// IBuildEngine4.UnregisterTaskObject, but adds the current assembly path into the key /// - [Obsolete ("Use UnregisterTaskObjectAssemblyLocal (engine, key, lifetime, flags) instead.")] public static object UnregisterTaskObjectAssemblyLocal (this IBuildEngine4 engine, object key, RegisteredTaskObjectLifetime lifetime) => UnregisterTaskObjectAssemblyLocal (engine, key, lifetime, flags: RegisterTaskObjectKeyFlags.IncludeProjectFile); /// /// IBuildEngine4.UnregisterTaskObject, but adds the current assembly path into the key /// - public static object UnregisterTaskObjectAssemblyLocal (this IBuildEngine4 engine, object key, RegisteredTaskObjectLifetime lifetime, RegisterTaskObjectKeyFlags flags = RegisterTaskObjectKeyFlags.IncludeProjectFile) => + public static object UnregisterTaskObjectAssemblyLocal (this IBuildEngine4 engine, object key, RegisteredTaskObjectLifetime lifetime, RegisterTaskObjectKeyFlags flags) => engine.UnregisterTaskObject (engine.GetKey (AssemblyLocation, key, flags), lifetime); /// /// Generic version of IBuildEngine4.UnregisterTaskObject, but adds the current assembly path into the key /// - [Obsolete ("Use UnregisterTaskObjectAssemblyLocal (engine, key, lifetime, flags) instead.")] public static T UnregisterTaskObjectAssemblyLocal (this IBuildEngine4 engine, object key, RegisteredTaskObjectLifetime lifetime) where T : class => UnregisterTaskObjectAssemblyLocal (engine, key, lifetime, flags: RegisterTaskObjectKeyFlags.IncludeProjectFile); /// /// Generic version of IBuildEngine4.UnregisterTaskObject, but adds the current assembly path into the key /// - public static T UnregisterTaskObjectAssemblyLocal (this IBuildEngine4 engine, object key, RegisteredTaskObjectLifetime lifetime, RegisterTaskObjectKeyFlags flags = RegisterTaskObjectKeyFlags.IncludeProjectFile) + public static T UnregisterTaskObjectAssemblyLocal (this IBuildEngine4 engine, object key, RegisteredTaskObjectLifetime lifetime, RegisterTaskObjectKeyFlags flags) where T : class => engine.UnregisterTaskObject (engine.GetKey (AssemblyLocation, key, flags), lifetime) as T; From 6d603f5998324c87d07f9bae812338bbe1b50d40 Mon Sep 17 00:00:00 2001 From: Jonathan Pryor Date: Thu, 12 Jan 2023 19:26:52 -0500 Subject: [PATCH 169/308] Revert IBuildEngine.ProjectFileOfTaskNode use. (#201) Reverts: 4589fbdd8defbcc0d1a7af8ece1576cce11417d0 Reverts: 00664afb58dd040a24d593f8d1ef5d2f64952cae Context: https://github.com/xamarin/xamarin-android/pull/7685#issuecomment-1381001885 The problem is that we misunderstood [`IBuildEngine.ProjectFileOfTaskNode`][0]: we *thought* (hoped?) that it would return `$(MSBuildProjectFullPath)`, the path to the `.csproj` being built. In actuality it returns `$(MSBuildThisFileFullPath)` for the file containing the Task invocation. Meaning if e.g. `Xamarin.Android.Common.targets` contains the Task invocation, then IBuildEngine.ProjectFileOfTaskNode` would be the path to `Xamarin.Android.Common.targets`, *not* the path of the `.csproj`. This in turn means it doesn't actually solve the problem we had. We will instead need to audit all use of `IBuildEngine4.RegisterTaskObject()` & related to ensure that the keys provided *also* contain paths to project-specific files, if necessary. Revert 4589fbdd and 00664afb, as the changes are not needed. [0]: https://learn.microsoft.com/en-us/dotnet/api/microsoft.build.framework.ibuildengine.projectfileoftasknode?view=msbuild-17-netcore --- .../MSBuildExtensions.cs | 65 ++----------------- 1 file changed, 6 insertions(+), 59 deletions(-) diff --git a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildExtensions.cs b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildExtensions.cs index f2c39b1a09b..d50b3d9367d 100644 --- a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildExtensions.cs +++ b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildExtensions.cs @@ -13,12 +13,6 @@ namespace Microsoft.Android.Build.Tasks { - [Flags] - public enum RegisterTaskObjectKeyFlags { - None = 0, - IncludeProjectFile = 1 << 0, - } - public static class MSBuildExtensions { public static void LogDebugMessage (this TaskLoggingHelper log, string message, params object[] messageArgs) @@ -259,80 +253,33 @@ public static void SetDestinationSubPath (this ITaskItem assembly) /// IBuildEngine4.RegisterTaskObject, but adds the current assembly path into the key /// public static void RegisterTaskObjectAssemblyLocal (this IBuildEngine4 engine, object key, object value, RegisteredTaskObjectLifetime lifetime, bool allowEarlyCollection = false) => - RegisterTaskObjectAssemblyLocal (engine, key, value, lifetime, allowEarlyCollection: false, flags: RegisterTaskObjectKeyFlags.IncludeProjectFile); - - /// - /// IBuildEngine4.RegisterTaskObject, but adds the current assembly path into the key - /// - public static void RegisterTaskObjectAssemblyLocal (this IBuildEngine4 engine, object key, object value, RegisteredTaskObjectLifetime lifetime, RegisterTaskObjectKeyFlags flags) => - engine.RegisterTaskObject (engine.GetKey (AssemblyLocation, key, flags), value, lifetime, allowEarlyCollection: false); - - /// - /// IBuildEngine4.RegisterTaskObject, but adds the current assembly path into the key - /// - public static void RegisterTaskObjectAssemblyLocal (this IBuildEngine4 engine, object key, object value, RegisteredTaskObjectLifetime lifetime, bool allowEarlyCollection, RegisterTaskObjectKeyFlags flags) => - engine.RegisterTaskObject (engine.GetKey (AssemblyLocation, key, flags), value, lifetime, allowEarlyCollection); + engine.RegisterTaskObject ((AssemblyLocation, key), value, lifetime, allowEarlyCollection); /// /// IBuildEngine4.GetRegisteredTaskObject, but adds the current assembly path into the key /// public static object GetRegisteredTaskObjectAssemblyLocal (this IBuildEngine4 engine, object key, RegisteredTaskObjectLifetime lifetime) => - GetRegisteredTaskObjectAssemblyLocal (engine, key, lifetime, flags: RegisterTaskObjectKeyFlags.IncludeProjectFile); - - /// - /// IBuildEngine4.GetRegisteredTaskObject, but adds the current assembly path into the key - /// - public static object GetRegisteredTaskObjectAssemblyLocal (this IBuildEngine4 engine, object key, RegisteredTaskObjectLifetime lifetime, RegisterTaskObjectKeyFlags flags) => - engine.GetRegisteredTaskObject (engine.GetKey (AssemblyLocation, key, flags), lifetime); - + engine.GetRegisteredTaskObject ((AssemblyLocation, key), lifetime); /// /// Generic version of IBuildEngine4.GetRegisteredTaskObject, but adds the current assembly path into the key /// public static T GetRegisteredTaskObjectAssemblyLocal (this IBuildEngine4 engine, object key, RegisteredTaskObjectLifetime lifetime) - where T : class => GetRegisteredTaskObjectAssemblyLocal (engine, key, lifetime, flags: RegisterTaskObjectKeyFlags.IncludeProjectFile); - - /// - /// Generic version of IBuildEngine4.GetRegisteredTaskObject, but adds the current assembly path into the key - /// - public static T GetRegisteredTaskObjectAssemblyLocal (this IBuildEngine4 engine, object key, RegisteredTaskObjectLifetime lifetime, RegisterTaskObjectKeyFlags flags) where T : class => - engine.GetRegisteredTaskObject (engine.GetKey (AssemblyLocation, key, flags), lifetime) as T; + engine.GetRegisteredTaskObject ((AssemblyLocation, key), lifetime) as T; - /// - /// IBuildEngine4.UnregisterTaskObject, but adds the current assembly path into the key - /// - public static object UnregisterTaskObjectAssemblyLocal (this IBuildEngine4 engine, object key, RegisteredTaskObjectLifetime lifetime) => - UnregisterTaskObjectAssemblyLocal (engine, key, lifetime, flags: RegisterTaskObjectKeyFlags.IncludeProjectFile); /// /// IBuildEngine4.UnregisterTaskObject, but adds the current assembly path into the key /// - public static object UnregisterTaskObjectAssemblyLocal (this IBuildEngine4 engine, object key, RegisteredTaskObjectLifetime lifetime, RegisterTaskObjectKeyFlags flags) => - engine.UnregisterTaskObject (engine.GetKey (AssemblyLocation, key, flags), lifetime); + public static object UnregisterTaskObjectAssemblyLocal (this IBuildEngine4 engine, object key, RegisteredTaskObjectLifetime lifetime) => + engine.UnregisterTaskObject ((AssemblyLocation, key), lifetime); /// /// Generic version of IBuildEngine4.UnregisterTaskObject, but adds the current assembly path into the key /// public static T UnregisterTaskObjectAssemblyLocal (this IBuildEngine4 engine, object key, RegisteredTaskObjectLifetime lifetime) - where T : class => UnregisterTaskObjectAssemblyLocal (engine, key, lifetime, flags: RegisterTaskObjectKeyFlags.IncludeProjectFile); - - /// - /// Generic version of IBuildEngine4.UnregisterTaskObject, but adds the current assembly path into the key - /// - public static T UnregisterTaskObjectAssemblyLocal (this IBuildEngine4 engine, object key, RegisteredTaskObjectLifetime lifetime, RegisterTaskObjectKeyFlags flags) where T : class => - engine.UnregisterTaskObject (engine.GetKey (AssemblyLocation, key, flags), lifetime) as T; - - /// - /// Method to calculate the key for the RegisterTaskObject. This is based on the - /// RegisterTaskObjectKeyFlags which are passed. - /// - static object GetKey (this IBuildEngine4 engine, string location, object key, RegisterTaskObjectKeyFlags flags) - { - return ((flags & RegisterTaskObjectKeyFlags.IncludeProjectFile) != 0) - ? (location, key, engine.ProjectFileOfTaskNode) - : (location, key, string.Empty); - } + engine.UnregisterTaskObject ((AssemblyLocation, key), lifetime) as T; } } From adc053aaf8c227d2c35545f219e5f4ee6fe6aa1f Mon Sep 17 00:00:00 2001 From: Dean Ellis Date: Wed, 18 Jan 2023 18:57:53 +0000 Subject: [PATCH 170/308] Add *Task.ProjectSpecificTaskObjectKey() for RegisterTaskObject() use (#202) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Context: https://github.com/dotnet/maui/issues/11605 Context: https://github.com/dotnet/maui/pull/11387#issuecomment-1318738792 Context: http://github.com/xamarin/xamarin-android/commit/8bc7a3e84f95e70fe12790ac31ecd97957771cb2 In dotnet/maui#11605, when `$(AndroidEnableMarshalMethods)`=True (xamarin/xamarin-android@8bc7a3e8), the build may fail if the `.sln` contains more than one Android "App" project. We tracked this down to "undesired sharing" between project builds; the `obj` provided to [`IBuildEngine4.RegisterTaskObject()`][0] can be visible across project builds. Consider [``][1]: var marshalMethodsState = BuildEngine4.GetRegisteredTaskObjectAssemblyLocal (".:!MarshalMethods!:.", RegisteredTaskObjectLifetime.Build); Further consider a `.sln` with two "App" projects, as the "App" project build hits ``. The lifetime of `.Build` is *not* tied to the the `Build` target of a given `.csproj`; rather, it's for the *build process*. This can result in: 1. `dotnet build Project.sln` is run; `Project.sln` references `App1.csproj` and `App2.csproj`. 2. `App1.csproj` is built. 3. `App1.csproj` calls ``. 4. `App2.csproj` is later built as part of the process, and *also* calls ``. In particular note the key within ``: `".:!MarshalMethods!:."`. This value is identical in all projects, and means that that when `App2.csproj` is built, it will be using the same key as was used with `App1.csproj`, and thus could be inadvertently using data intended for `App1.csproj`! This would result build errors: …\Xamarin.Android.Common.targets(1717,3): error XAGPM7009: System.InvalidOperationException: Unable to translate assembly name 'Xamarin.AndroidX.Activity' to its index …\Xamarin.Android.Common.targets(1717,3): error XAGPM7009: at Xamarin.Android.Tasks.MarshalMethodsNativeAssemblyGenerator.WriteNativeMethods(LlvmIrGenerator generator, Dictionary`2 asmNameToIndex, LlvmIrVariableReference get_function_pointer_ref) …\Xamarin.Android.Common.targets(1717,3): error XAGPM7009: at Xamarin.Android.Tasks.MarshalMethodsNativeAssemblyGenerator.Write(LlvmIrGenerator generator) …\Xamarin.Android.Common.targets(1717,3): error XAGPM7009: at Xamarin.Android.Tasks.LLVMIR.LlvmIrComposer.Write(AndroidTargetArch arch, StreamWriter output, String fileName) …\Xamarin.Android.Common.targets(1717,3): error XAGPM7009: at Xamarin.Android.Tasks.GeneratePackageManagerJava.AddEnvironment() …\Xamarin.Android.Common.targets(1717,3): error XAGPM7009: at Xamarin.Android.Tasks.GeneratePackageManagerJava.RunTask() The sharing of `RegisterTaskObject()` data across project builds is rarely desirable. There are a few instances where it is safe to share the registered objects between projects, e.g. `java -version` version information (keyed on `java` path). However, most of the time it is specific to the project that is being built. Historically we have probably got away with this because "most" users only have one project. Update `AndroidTask` and `AndroidToolTask` to capture the current directory in a `WorkingDirectory` property like [`AsyncTask`][2] does. Introduce new `ProjectSpecificTaskObjectKey()` instance methods into `AndroidTask`, `AndroidAsyncTask`, and `AndroidToolTask` which can be used to generate a key which includes `WorkingDirectory`. This allows: var marshalMethodsState = BuildEngine4.GetRegisteredTaskObjectAssemblyLocal ( ProjectSpecificTaskObjectKey (".:!MarshalMethods!:."), RegisteredTaskObjectLifetime.Build ); When `ProjectSpecificTaskObjectKey()` is used the `WorkingDirectory` is included in the key with `RegisterTaskObject()`. This helps ensure that builds in different `.csproj` files will result in different keys. [0]: https://learn.microsoft.com/en-us/dotnet/api/microsoft.build.framework.ibuildengine4.registertaskobject?view=msbuild-17-netcore [1]: https://github.com/xamarin/xamarin-android/blob/c92ae5eb9fdcb3a2fd7c20f5b42dddf8b3ea781a/src/Xamarin.Android.Build.Tasks/Tasks/GeneratePackageManagerJava.cs#L407 [2]: https://github.com/xamarin/Xamarin.Build.AsyncTask/blob/8b5fc6c4d13a3dfd1d17a2007e2143b6da3447d7/Xamarin.Build.AsyncTask/AsyncTask.cs#L59 --- .../AndroidAsyncTask.cs | 2 + .../AndroidTask.cs | 10 ++ .../AndroidToolTask.cs | 10 ++ .../MSBuildExtensions.cs | 22 +++- .../AndroidToolTaskTests.cs | 100 +++++++++++++++++ .../Utilites/MockBuildEngine.cs | 102 ++++++++++++++++++ 6 files changed, 245 insertions(+), 1 deletion(-) create mode 100644 external/xamarin-android-tools/tests/Microsoft.Android.Build.BaseTasks-Tests/AndroidToolTaskTests.cs create mode 100644 external/xamarin-android-tools/tests/Microsoft.Android.Build.BaseTasks-Tests/Utilites/MockBuildEngine.cs diff --git a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/AndroidAsyncTask.cs b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/AndroidAsyncTask.cs index f3859e5093d..a04d86ff40b 100644 --- a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/AndroidAsyncTask.cs +++ b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/AndroidAsyncTask.cs @@ -46,5 +46,7 @@ public virtual bool RunTask () /// * RunTaskAsync is already on a background thread /// public virtual System.Threading.Tasks.Task RunTaskAsync () => System.Threading.Tasks.Task.CompletedTask; + + protected object ProjectSpecificTaskObjectKey (object key) => (key, WorkingDirectory); } } diff --git a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/AndroidTask.cs b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/AndroidTask.cs index 4b511c3ca0a..9acb7b8b056 100644 --- a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/AndroidTask.cs +++ b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/AndroidTask.cs @@ -1,6 +1,7 @@ // https://github.com/xamarin/xamarin-android/blob/9fca138604c53989e1cff7fc0c2e939583b4da28/src/Xamarin.Android.Build.Tasks/Tasks/AndroidTask.cs#L10 using System; +using System.IO; using Microsoft.Build.Utilities; namespace Microsoft.Android.Build.Tasks @@ -11,6 +12,13 @@ public abstract class AndroidTask : Task { public abstract string TaskPrefix { get; } + protected string WorkingDirectory { get; private set; } + + public AndroidTask () + { + WorkingDirectory = Directory.GetCurrentDirectory(); + } + public override bool Execute () { try { @@ -22,5 +30,7 @@ public override bool Execute () } public abstract bool RunTask (); + + protected object ProjectSpecificTaskObjectKey (object key) => (key, WorkingDirectory); } } diff --git a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/AndroidToolTask.cs b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/AndroidToolTask.cs index 38093a61a3c..cc01e8f5928 100644 --- a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/AndroidToolTask.cs +++ b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/AndroidToolTask.cs @@ -1,6 +1,7 @@ // https://github.com/xamarin/xamarin-android/blob/9fca138604c53989e1cff7fc0c2e939583b4da28/src/Xamarin.Android.Build.Tasks/Tasks/AndroidTask.cs#L75 using System; +using System.IO; using Microsoft.Build.Utilities; namespace Microsoft.Android.Build.Tasks @@ -9,6 +10,13 @@ public abstract class AndroidToolTask : ToolTask { public abstract string TaskPrefix { get; } + protected string WorkingDirectory { get; private set; } + + public AndroidToolTask () + { + WorkingDirectory = Directory.GetCurrentDirectory(); + } + public override bool Execute () { try { @@ -22,5 +30,7 @@ public override bool Execute () // Most ToolTask's do not override Execute and // just expect the base to be called public virtual bool RunTask () => base.Execute (); + + protected object ProjectSpecificTaskObjectKey (object key) => (key, WorkingDirectory); } } diff --git a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildExtensions.cs b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildExtensions.cs index d50b3d9367d..b467e833f8c 100644 --- a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildExtensions.cs +++ b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildExtensions.cs @@ -251,18 +251,30 @@ public static void SetDestinationSubPath (this ITaskItem assembly) /// /// IBuildEngine4.RegisterTaskObject, but adds the current assembly path into the key + /// The `key` should be unique to a project unless it is a global item. + /// Ideally the key should be the full path of a file in the project directory structure. + /// Or you can use the `ProjectSpecificTaskObjectKey` method of the `AndroidTask` to generate + /// a project specific key if needed. /// public static void RegisterTaskObjectAssemblyLocal (this IBuildEngine4 engine, object key, object value, RegisteredTaskObjectLifetime lifetime, bool allowEarlyCollection = false) => engine.RegisterTaskObject ((AssemblyLocation, key), value, lifetime, allowEarlyCollection); /// /// IBuildEngine4.GetRegisteredTaskObject, but adds the current assembly path into the key + /// The `key` should be unique to a project unless it is a global item. + /// Ideally the key should be the full path of a file in the project directory structure. + /// Or you can use the `ProjectSpecificTaskObjectKey` method of the `AndroidTask` to generate + /// a project specific key if needed. /// public static object GetRegisteredTaskObjectAssemblyLocal (this IBuildEngine4 engine, object key, RegisteredTaskObjectLifetime lifetime) => engine.GetRegisteredTaskObject ((AssemblyLocation, key), lifetime); /// /// Generic version of IBuildEngine4.GetRegisteredTaskObject, but adds the current assembly path into the key + /// The `key` should be unique to a project unless it is a global item. + /// Ideally the key should be the full path of a file in the project directory structure. + /// Or you can use the `ProjectSpecificTaskObjectKey` method of the `AndroidTask` to generate + /// a project specific key if needed. /// public static T GetRegisteredTaskObjectAssemblyLocal (this IBuildEngine4 engine, object key, RegisteredTaskObjectLifetime lifetime) where T : class => @@ -271,12 +283,20 @@ public static T GetRegisteredTaskObjectAssemblyLocal (this IBuildEngine4 engi /// /// IBuildEngine4.UnregisterTaskObject, but adds the current assembly path into the key + /// The `key` should be unique to a project unless it is a global item. + /// Ideally the key should be the full path of a file in the project directory structure. + /// Or you can use the `ProjectSpecificTaskObjectKey` method of the `AndroidTask` to generate + /// a project specific key if needed. /// public static object UnregisterTaskObjectAssemblyLocal (this IBuildEngine4 engine, object key, RegisteredTaskObjectLifetime lifetime) => engine.UnregisterTaskObject ((AssemblyLocation, key), lifetime); /// - /// Generic version of IBuildEngine4.UnregisterTaskObject, but adds the current assembly path into the key + /// Generic version of IBuildEngine4.UnregisterTaskObject, but adds the current assembly path into the key. + /// The `key` should be unique to a project unless it is a global item. + /// Ideally the key should be the full path of a file in the project directory structure. + /// Or you can use the `ProjectSpecificTaskObjectKey` method of the `AndroidTask` to generate + /// a project specific key if needed. /// public static T UnregisterTaskObjectAssemblyLocal (this IBuildEngine4 engine, object key, RegisteredTaskObjectLifetime lifetime) where T : class => diff --git a/external/xamarin-android-tools/tests/Microsoft.Android.Build.BaseTasks-Tests/AndroidToolTaskTests.cs b/external/xamarin-android-tools/tests/Microsoft.Android.Build.BaseTasks-Tests/AndroidToolTaskTests.cs new file mode 100644 index 00000000000..8b35ce5ba1d --- /dev/null +++ b/external/xamarin-android-tools/tests/Microsoft.Android.Build.BaseTasks-Tests/AndroidToolTaskTests.cs @@ -0,0 +1,100 @@ +using System.IO; +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.Android.Build.BaseTasks.Tests.Utilities; +using Microsoft.Android.Build.Tasks; +using NUnit.Framework; +using Microsoft.Build.Framework; +using Xamarin.Build; + +namespace Microsoft.Android.Build.BaseTasks.Tests +{ + [TestFixture] + public class AndroidToolTaskTests + { + public class MyAndroidTask : AndroidTask { + public override string TaskPrefix {get;} = "MAT"; + public string Key { get; set; } + public string Value { get; set; } + public bool ProjectSpecific { get; set; } = false; + public override bool RunTask () + { + var key = ProjectSpecific ? ProjectSpecificTaskObjectKey (Key) : (Key, (object)string.Empty); + BuildEngine4.RegisterTaskObjectAssemblyLocal (key, Value, RegisteredTaskObjectLifetime.Build); + return true; + } + } + + public class MyOtherAndroidTask : AndroidTask { + public override string TaskPrefix {get;} = "MOAT"; + public string Key { get; set; } + public bool ProjectSpecific { get; set; } = false; + + [Output] + public string Value { get; set; } + public override bool RunTask () + { + var key = ProjectSpecific ? ProjectSpecificTaskObjectKey (Key) : (Key, (object)string.Empty); + Value = BuildEngine4.GetRegisteredTaskObjectAssemblyLocal (key, RegisteredTaskObjectLifetime.Build); + return true; + } + } + + [Test] + [TestCase (true, true, true)] + [TestCase (false, false, true)] + [TestCase (true, false, false)] + [TestCase (false, true, false)] + public void TestRegisterTaskObjectCanRetrieveCorrectItem (bool projectSpecificA, bool projectSpecificB, bool expectedResult) + { + var engine = new MockBuildEngine (TestContext.Out) { + }; + var task = new MyAndroidTask () { + BuildEngine = engine, + Key = "Foo", + Value = "Foo", + ProjectSpecific = projectSpecificA, + }; + task.Execute (); + var task2 = new MyOtherAndroidTask () { + BuildEngine = engine, + Key = "Foo", + ProjectSpecific = projectSpecificB, + }; + task2.Execute (); + Assert.AreEqual (expectedResult, string.Compare (task2.Value, task.Value, ignoreCase: true) == 0); + } + + [Test] + [TestCase (true, true, false)] + [TestCase (false, false, true)] + [TestCase (true, false, false)] + [TestCase (false, true, false)] + public void TestRegisterTaskObjectFailsWhenDirectoryChanges (bool projectSpecificA, bool projectSpecificB, bool expectedResult) + { + var engine = new MockBuildEngine (TestContext.Out) { + }; + MyAndroidTask task; + var currentDir = Directory.GetCurrentDirectory (); + Directory.SetCurrentDirectory (Path.Combine (currentDir, "..")); + try { + task = new MyAndroidTask () { + BuildEngine = engine, + Key = "Foo", + Value = "Foo", + ProjectSpecific = projectSpecificA, + }; + } finally { + Directory.SetCurrentDirectory (currentDir); + } + task.Execute (); + var task2 = new MyOtherAndroidTask () { + BuildEngine = engine, + Key = "Foo", + ProjectSpecific = projectSpecificB, + }; + task2.Execute (); + Assert.AreEqual (expectedResult, string.Compare (task2.Value, task.Value, ignoreCase: true) == 0); + } + } +} diff --git a/external/xamarin-android-tools/tests/Microsoft.Android.Build.BaseTasks-Tests/Utilites/MockBuildEngine.cs b/external/xamarin-android-tools/tests/Microsoft.Android.Build.BaseTasks-Tests/Utilites/MockBuildEngine.cs new file mode 100644 index 00000000000..38155fcf2c9 --- /dev/null +++ b/external/xamarin-android-tools/tests/Microsoft.Android.Build.BaseTasks-Tests/Utilites/MockBuildEngine.cs @@ -0,0 +1,102 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using Microsoft.Build.Framework; + +namespace Microsoft.Android.Build.BaseTasks.Tests.Utilities { + public class MockBuildEngine : IBuildEngine, IBuildEngine2, IBuildEngine3, IBuildEngine4 { + public MockBuildEngine (TextWriter output, IList errors = null, IList warnings = null, IList messages = null, IList customEvents = null) + { + this.Output = output; + this.Errors = errors; + this.Warnings = warnings; + this.Messages = messages; + this.CustomEvents = customEvents; + } + + private TextWriter Output { get; } + + private IList Errors { get; } + + private IList Warnings { get; } + + private IList Messages { get; } + + private IList CustomEvents { get; } + + int IBuildEngine.ColumnNumberOfTaskNode => -1; + + bool IBuildEngine.ContinueOnError => false; + + int IBuildEngine.LineNumberOfTaskNode => -1; + + string IBuildEngine.ProjectFileOfTaskNode => "this.xml"; + + bool IBuildEngine2.IsRunningMultipleNodes => false; + + bool IBuildEngine.BuildProjectFile (string projectFileName, string [] targetNames, IDictionary globalProperties, IDictionary targetOutputs) => true; + + void IBuildEngine.LogCustomEvent (CustomBuildEventArgs e) + { + this.Output.WriteLine ($"Custom: {e.Message}"); + if (CustomEvents != null) + CustomEvents.Add (e); + } + + void IBuildEngine.LogErrorEvent (BuildErrorEventArgs e) + { + this.Output.WriteLine ($"Error: {e.Message}"); + if (Errors != null) + Errors.Add (e); + } + + void IBuildEngine.LogMessageEvent (BuildMessageEventArgs e) + { + this.Output.WriteLine ($"Message: {e.Message}"); + if (Messages != null) + Messages.Add (e); + } + + void IBuildEngine.LogWarningEvent (BuildWarningEventArgs e) + { + this.Output.WriteLine ($"Warning: {e.Message}"); + if (Warnings != null) + Warnings.Add (e); + } + + private Dictionary Tasks = new Dictionary (); + + void IBuildEngine4.RegisterTaskObject (object key, object obj, RegisteredTaskObjectLifetime lifetime, bool allowEarlyCollection) + { + Tasks.Add (key, obj); + } + + object IBuildEngine4.GetRegisteredTaskObject (object key, RegisteredTaskObjectLifetime lifetime) + { + object obj = null; + Tasks.TryGetValue (key, out obj); + return obj; + } + + object IBuildEngine4.UnregisterTaskObject (object key, RegisteredTaskObjectLifetime lifetime) + { + var obj = Tasks [key]; + Tasks.Remove (key); + return obj; + } + + BuildEngineResult IBuildEngine3.BuildProjectFilesInParallel (string [] projectFileNames, string [] targetNames, IDictionary [] globalProperties, IList [] removeGlobalProperties, string [] toolsVersion, bool returnTargetOutputs) + { + throw new NotImplementedException (); + } + + void IBuildEngine3.Yield () { } + + void IBuildEngine3.Reacquire () { } + + bool IBuildEngine2.BuildProjectFile (string projectFileName, string [] targetNames, IDictionary globalProperties, IDictionary targetOutputs, string toolsVersion) => true; + + bool IBuildEngine2.BuildProjectFilesInParallel (string [] projectFileNames, string [] targetNames, IDictionary [] globalProperties, IDictionary [] targetOutputsPerProject, string [] toolsVersion, bool useResultsCache, bool unloadProjectsOnCompletion) => true; + } +} From a2dce0d97339d7bc85bb4b3c5728d5563246b34d Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Mon, 27 Feb 2023 11:29:40 +0100 Subject: [PATCH 171/308] Bump LibZipSharp version (#204) Context: https://github.com/xamarin/LibZipSharp/pull/125 The new LibZipSharp version potentially fixes the issue we see from time to time on Ci, when an attempt to process a zip archive (APK, in our case) ends with an error when trying to open the archive. --- .../MSBuildReferences.projitems | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems index 2ce698cc1e6..9c3cff2d61c 100644 --- a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems +++ b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems @@ -5,7 +5,7 @@ 17.3.2 - 2.0.7 + 2.1.0 7.1.0-final.1.21458.1 From 352c3ee160f5e4e5191ca42b680e058b979c0c10 Mon Sep 17 00:00:00 2001 From: Dean Ellis Date: Wed, 19 Apr 2023 18:55:25 +0100 Subject: [PATCH 172/308] [Xamarin.Android.Tools.AndroidSdk] Update platform-tools for API-T (#203) Context: https://dl-ssl.google.com/android/repository/repository2-3.xml Context: https://developer.android.com/studio/releases/platform-tools As of 2023-Mar-03 (according to comments in `repository2-3.xml`), the Android SDK platform-tools package version 34.0.1 is now (1) stable, and (2) the *only* platform-tools package listed in `repository2-3.xml`. Changes from 33.0.2 include: * Various changes to `adb` * Various changes to `fastboot` Update `$(AndroidSdkPlatformToolsVersion)` to 34.0.1 so that the Android SDK platform-tools 34.0.1 package is the default version used in Xamarin.Android / .NET Android builds. TODO: Visual Studio 17.7 will be updated to install platform-tools 34.0.1 by default as well. --- .../Xamarin.Android.Tools.Versions.props | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.Versions.props b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.Versions.props index 5d58bc7fcfb..bce8f2beb79 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.Versions.props +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.Versions.props @@ -4,14 +4,14 @@ 32.0.0 7.0 - 33.0.2 + 34.0.1 android-33 24.0.8215888 @@ -19,4 +19,4 @@ 26.1.1 - \ No newline at end of file + From 312034765f225aa72d7e4321543322c5bf7aa386 Mon Sep 17 00:00:00 2001 From: Peter Collins Date: Thu, 11 May 2023 13:33:53 -0400 Subject: [PATCH 173/308] [ci] Add weekly schedule for OneLocBuild (#207) We've been notified that the localization system will create work items for loc pipelines that do not have regular builds. To address this, we can add a weekly build that will always run the OneLocBuild task even if sources haven't changed. --- external/xamarin-android-tools/Localize/onelocbuild.yaml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/external/xamarin-android-tools/Localize/onelocbuild.yaml b/external/xamarin-android-tools/Localize/onelocbuild.yaml index d458975b39b..8af9f1037b6 100644 --- a/external/xamarin-android-tools/Localize/onelocbuild.yaml +++ b/external/xamarin-android-tools/Localize/onelocbuild.yaml @@ -12,6 +12,12 @@ schedules: branches: include: - main +- cron: "0 6 * * Sunday" + displayName: Run weekly on Sunday at 6:00 UTC + branches: + include: + - main + always: true jobs: - job: OneLocBuild From 860930b4a0d46f261fd2e099d4137f0f1ec1e22c Mon Sep 17 00:00:00 2001 From: Jonathan Pobst Date: Fri, 12 May 2023 10:30:56 -0500 Subject: [PATCH 174/308] [Xamarin.Android.Tools.AndroidSdk] Fix NRT warnings (#206) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix NRT warnings that are showing up in CI build logs, e.g. …/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidAppManifest.cs(179,34): warning CS8604: Possible null reference argument for parameter 'attribute' in 'int? AndroidAppManifest.ParseSdkVersion(XAttribute attribute)'. …/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidAppManifest.cs(250,22): warning CS8600: Converting null literal or possible null value to non-nullable type. …/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidAppManifest.cs(252,6): warning CS8602: Dereference of a possibly null reference. …/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidAppManifest.cs(34,15): warning CS8601: Possible null reference assignment. …/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidAppManifest.cs(35,8): warning CS8602: Dereference of a possibly null reference. …/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidAppManifest.cs(38,18): warning CS8601: Possible null reference assignment. …/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidSdkInfo.cs(198,11): warning CS8603: Possible null reference return. …/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidVersion.cs(74,18): warning CS8600: Converting null literal or possible null value to non-nullable type. …/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidVersion.cs(74,27): warning CS8602: Dereference of a possibly null reference. …/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidVersion.cs(75,24): warning CS8604: Possible null reference argument for parameter 'element' in 'XElement.explicit operator int(XElement element)'. …/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidVersion.cs(76,18): warning CS8600: Converting null literal or possible null value to non-nullable type. …/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidVersion.cs(78,25): warning CS8604: Possible null reference argument for parameter 'element' in 'XElement.explicit operator bool(XElement element)'. …/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidVersion.cs(80,38): warning CS8602: Dereference of a possibly null reference. …/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/JdkInfo.cs(403,23): warning CS8600: Converting null literal or possible null value to non-nullable type. …/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/JdkInfo.cs(406,12): warning CS8602: Dereference of a possibly null reference. …/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/JdkInfo.cs(52,10): warning CS8618: Non-nullable property 'JarPath' must contain a non-null value when exiting constructor. Consider declaring the property as nullable. …/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/JdkInfo.cs(52,10): warning CS8618: Non-nullable property 'JavacPath' must contain a non-null value when exiting constructor. Consider declaring the property as nullable. …/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/JdkInfo.cs(52,10): warning CS8618: Non-nullable property 'JavaPath' must contain a non-null value when exiting constructor. Consider declaring the property as nullable. …/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/JdkInfo.cs(64,26): warning CS8601: Possible null reference assignment. …/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Jdks/JdkLocations.MacOS.cs(23,29): warning CS8602: Dereference of a possibly null reference. …/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkUnix.cs(129,20): warning CS8602: Dereference of a possibly null reference. …/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkUnix.cs(49,21): warning CS8602: Dereference of a possibly null reference. Fix these warnings. Rework the `JdkInfo` constructor so that the C# compiler can determine that `JarPath`, `JavacPath`, and `JavaPath` aren't `null`. Change the return type of `AndroidSdkUnix.GetUnixConfigFile()` and parameter type of `AndroidSdkUnix.SaveConfig()` to be `XElement` instead of `XDocument`, to remove warnings about `XDocument.Root` possibly being `null`. --- .../AndroidAppManifest.cs | 22 ++++++++------ .../AndroidSdkInfo.cs | 2 +- .../AndroidVersion.cs | 10 +++---- .../JdkInfo.cs | 30 +++++++++++-------- .../Jdks/JdkLocations.MacOS.cs | 3 +- .../Sdks/AndroidSdkUnix.cs | 27 +++++++++-------- 6 files changed, 54 insertions(+), 40 deletions(-) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidAppManifest.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidAppManifest.cs index a8024c443bf..5ef7e138fa1 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidAppManifest.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidAppManifest.cs @@ -31,16 +31,20 @@ public class AndroidAppManifest throw new ArgumentNullException (nameof (doc)); this.versions = versions; this.doc = doc; - manifest = doc.Root; - if (manifest.Name != "manifest") + + if (doc.Root is null || doc.Root.Name != "manifest") throw new ArgumentException ("App manifest does not have 'manifest' root element", nameof (doc)); - application = manifest.Element ("application"); - if (application == null) + manifest = doc.Root; + + if (manifest.Element ("application") is XElement app) + application = app; + else manifest.Add (application = new XElement ("application")); - usesSdk = manifest.Element ("uses-sdk"); - if (usesSdk == null) + if (manifest.Element ("uses-sdk") is XElement uses) + usesSdk = uses; + else manifest.Add (usesSdk = new XElement ("uses-sdk")); } @@ -185,7 +189,7 @@ public int? TargetSdkVersion { set { usesSdk.SetAttributeValue (aNS + "targetSdkVersion", value == null ? null : value.ToString ()); } } - int? ParseSdkVersion (XAttribute attribute) + int? ParseSdkVersion (XAttribute? attribute) { var version = (string?) attribute; if (version == null || string.IsNullOrEmpty (version)) @@ -247,9 +251,9 @@ void AddAndroidPermissions (IEnumerable permissions) lastPerm = el; } } else { - var parentNode = (XNode) manifest.Element ("application") ?? manifest.LastNode; + var parentNode = (XNode?) manifest.Element ("application") ?? manifest.LastNode; foreach (var el in newElements) - parentNode.AddBeforeSelf (el); + parentNode!.AddBeforeSelf (el); } } diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidSdkInfo.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidSdkInfo.cs index 0b9cf605b93..e0eedb651fc 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidSdkInfo.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidSdkInfo.cs @@ -193,7 +193,7 @@ public static void DetectAndSetPreferredJavaSdkPathToLatest (Action static AndroidVersion Load (XDocument doc) { - var id = (string) doc.Root.Element ("Id"); - var level = (int) doc.Root.Element ("Level"); - var name = (string) doc.Root.Element ("Name"); - var version = (string) doc.Root.Element ("Version"); - var stable = (bool) doc.Root.Element ("Stable"); + var id = (string?) doc.Root?.Element ("Id") ?? throw new InvalidOperationException ("Missing Id element"); + var level = (int?) doc.Root?.Element ("Level") ?? throw new InvalidOperationException ("Missing Level element"); + var name = (string?) doc.Root?.Element ("Name") ?? throw new InvalidOperationException ("Missing Name element"); + var version = (string?) doc.Root?.Element ("Version") ?? throw new InvalidOperationException ("Missing Version element"); + var stable = (bool?) doc.Root?.Element ("Stable") ?? throw new InvalidOperationException ("Missing Stable element"); return new AndroidVersion (level, version.TrimStart ('v'), name, id, stable); } diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/JdkInfo.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/JdkInfo.cs index a6c8666092e..644ef90dc91 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/JdkInfo.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/JdkInfo.cs @@ -61,18 +61,15 @@ public JdkInfo (string homePath, string? locator = null, Action (); var jdkInclude = Path.Combine (HomePath, "include"); @@ -153,12 +150,21 @@ static IEnumerable FindLibrariesInDirectory (string dir, string libraryN return Directory.EnumerateFiles (dir, library, SearchOption.AllDirectories); } - void ValidateFile (string name, string? path) + void ValidateFile (string name, [NotNull]string? path) { if (path == null || !File.Exists (path)) throw new ArgumentException ($"Could not find required file `{name}` within `{HomePath}`; is this a valid JDK?", "homePath"); } + string RequireExecutableInDirectory (string binPath, string fileName) + { + var file = ProcessUtils.FindExecutablesInDirectory (binPath, fileName).FirstOrDefault (); + + ValidateFile (fileName, file); + + return file; + } + static Regex NonDigitMatcher = new Regex (@"[^\d]", RegexOptions.Compiled | RegexOptions.CultureInvariant); Version? GetJavaVersion () @@ -400,12 +406,12 @@ static IEnumerable GetLibexecJdkPaths (Action logger yield break; } foreach (var info in plist.Elements ("array").Elements ("dict")) { - var JVMHomePath = (XNode) info.Elements ("key").FirstOrDefault (e => e.Value == "JVMHomePath"); + var JVMHomePath = (XNode?) info.Elements ("key").FirstOrDefault (e => e.Value == "JVMHomePath"); if (JVMHomePath == null) continue; - while (JVMHomePath.NextNode.NodeType != XmlNodeType.Element) - JVMHomePath = JVMHomePath.NextNode; - var strElement = (XElement) JVMHomePath.NextNode; + while (JVMHomePath.NextNode!.NodeType != XmlNodeType.Element) + JVMHomePath = JVMHomePath.NextNode!; + var strElement = (XElement) JVMHomePath.NextNode!; var path = strElement.Value; yield return path; } diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Jdks/JdkLocations.MacOS.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Jdks/JdkLocations.MacOS.cs index 0559c9de300..1aeb1fa40d1 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Jdks/JdkLocations.MacOS.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Jdks/JdkLocations.MacOS.cs @@ -20,7 +20,8 @@ static IEnumerable GetUnixPreferredJdks (Action log static IEnumerable GetUnixConfiguredJdkPaths (Action logger) { var config = AndroidSdkUnix.GetUnixConfigFile (logger); - foreach (var java_sdk in config.Root.Elements ("java-sdk")) { + + foreach (var java_sdk in config.Elements ("java-sdk")) { var path = (string?) java_sdk.Attribute ("path"); if (path != null && !string.IsNullOrEmpty (path)) { yield return path; diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkUnix.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkUnix.cs index 82302a10281..788c41e2186 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkUnix.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkUnix.cs @@ -46,7 +46,7 @@ public override string NdkHostPlatform64Bit { public override string? PreferedAndroidSdkPath { get { var config_file = GetUnixConfigFile (Logger); - var androidEl = config_file.Root.Element ("android-sdk"); + var androidEl = config_file.Element ("android-sdk"); if (androidEl != null) { var path = (string?)androidEl.Attribute ("path"); @@ -61,7 +61,7 @@ public override string? PreferedAndroidSdkPath { public override string? PreferedAndroidNdkPath { get { var config_file = GetUnixConfigFile (Logger); - var androidEl = config_file.Root.Element ("android-ndk"); + var androidEl = config_file.Element ("android-ndk"); if (androidEl != null) { var path = (string?)androidEl.Attribute ("path"); @@ -76,7 +76,7 @@ public override string? PreferedAndroidNdkPath { public override string? PreferedJavaSdkPath { get { var config_file = GetUnixConfigFile (Logger); - var javaEl = config_file.Root.Element ("java-sdk"); + var javaEl = config_file.Element ("java-sdk"); if (javaEl != null) { var path = (string?)javaEl.Attribute ("path"); @@ -126,11 +126,12 @@ public override void SetPreferredAndroidSdkPath (string? path) path = NullIfEmpty (path); var doc = GetUnixConfigFile (Logger); - var androidEl = doc.Root.Element ("android-sdk"); + + var androidEl = doc.Element ("android-sdk"); if (androidEl == null) { androidEl = new XElement ("android-sdk"); - doc.Root.Add (androidEl); + doc.Add (androidEl); } androidEl.SetAttributeValue ("path", path); @@ -142,11 +143,12 @@ public override void SetPreferredJavaSdkPath (string? path) path = NullIfEmpty (path); var doc = GetUnixConfigFile (Logger); - var javaEl = doc.Root.Element ("java-sdk"); + + var javaEl = doc.Element ("java-sdk"); if (javaEl == null) { javaEl = new XElement ("java-sdk"); - doc.Root.Add (javaEl); + doc.Add (javaEl); } javaEl.SetAttributeValue ("path", path); @@ -158,18 +160,19 @@ public override void SetPreferredAndroidNdkPath (string? path) path = NullIfEmpty (path); var doc = GetUnixConfigFile (Logger); - var androidEl = doc.Root.Element ("android-ndk"); + + var androidEl = doc.Element ("android-ndk"); if (androidEl == null) { androidEl = new XElement ("android-ndk"); - doc.Root.Add (androidEl); + doc.Add (androidEl); } androidEl.SetAttributeValue ("path", path); SaveConfig (doc); } - void SaveConfig (XDocument doc) + void SaveConfig (XElement doc) { string cfg = UnixConfigPath; List ? created = null; @@ -229,7 +232,7 @@ private static string UnixConfigPath { } } - internal static XDocument GetUnixConfigFile (Action logger) + internal static XElement GetUnixConfigFile (Action logger) { var file = UnixConfigPath; XDocument? doc = null; @@ -254,7 +257,7 @@ internal static XDocument GetUnixConfigFile (Action logger) if (doc == null || doc.Root == null) { doc = new XDocument (new XElement ("monodroid")); } - return doc; + return doc.Root!; } void FixOwnership (List? paths) From eb0f8b0f7d3be46a1d8f2657e42785a876b89c8c Mon Sep 17 00:00:00 2001 From: Peter Collins Date: Wed, 31 May 2023 10:18:43 -0400 Subject: [PATCH 175/308] [Build Tasks] Add LogErrorForXmlNode (#209) Adds a copy of LogWarningForXmlNode that logs an error instead of a warning. --- .../MSBuildExtensions.cs | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildExtensions.cs b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildExtensions.cs index b467e833f8c..fd0909d754c 100644 --- a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildExtensions.cs +++ b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildExtensions.cs @@ -135,6 +135,33 @@ public static void LogCodedWarning (this TaskLoggingHelper log, string code, str log.LogWarning (string.Empty, code, string.Empty, file, lineNumber, 0, 0, 0, message, messageArgs); } + /// + /// Logs a coded error from a node in an XML document + /// + /// An element that implements IXmlLineInfo + public static void LogErrorForXmlNode (this TaskLoggingHelper log, string code, string file, object node, string message, params object [] messageArgs) + { + int lineNumber = 0; + int columnNumber = 0; + var lineInfo = node as IXmlLineInfo; + if (lineInfo != null && lineInfo.HasLineInfo ()) { + lineNumber = lineInfo.LineNumber; + columnNumber = lineInfo.LinePosition; + } + log.LogError ( + subcategory: string.Empty, + errorCode: code, + helpKeyword: string.Empty, + file: file, + lineNumber: lineNumber, + columnNumber: columnNumber, + endLineNumber: 0, + endColumnNumber: 0, + message: message, + messageArgs: messageArgs + ); + } + /// /// Logs a coded warning from a node in an XML document /// From 30edee47df92cf58814b1ae8416add9232096836 Mon Sep 17 00:00:00 2001 From: Dean Ellis Date: Fri, 9 Jun 2023 17:17:28 +0100 Subject: [PATCH 176/308] Bump LibZipSharp to 3.0.0 (#210) --- .../MSBuildReferences.projitems | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems index 9c3cff2d61c..b5976b342a5 100644 --- a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems +++ b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems @@ -5,7 +5,7 @@ 17.3.2 - 2.1.0 + 3.0.0 7.1.0-final.1.21458.1 From 52f03405789823affeeb6015dc10099c512071f8 Mon Sep 17 00:00:00 2001 From: Juan Marcelo Tondato Date: Thu, 3 Aug 2023 17:52:09 -0300 Subject: [PATCH 177/308] [Xamarin.Android.Tools.AndroidSdk] Add API-34 to KnownVersions (#212) --- .../src/Xamarin.Android.Tools.AndroidSdk/AndroidVersions.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidVersions.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidVersions.cs index c45fd175187..a4818d73cc0 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidVersions.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidVersions.cs @@ -191,6 +191,9 @@ static bool MatchesId (AndroidVersion version, string id) new AndroidVersion (33, "13.0") { AlternateIds = new[]{ "T" }, }, + new AndroidVersion (34, "14.0") { + AlternateIds = new[]{ "U", "UpsideDownCake", "Upside Down Cake" }, + }, }; } } From 5c9f5f9028630606d13185be3998927915916bca Mon Sep 17 00:00:00 2001 From: Juan Marcelo Tondato Date: Thu, 3 Aug 2023 17:54:17 -0300 Subject: [PATCH 178/308] [Xamarin.Android.Tools.AndroidSdk] Update SDK component for API-34 (#211) Update `$(AndroidSdkPlatformVersion)` to android-34. API-34 has been declared stable by Google, and will be the default API level for .NET 8. --- .../Xamarin.Android.Tools.Versions.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.Versions.props b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.Versions.props index bce8f2beb79..d93900efd27 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.Versions.props +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.Versions.props @@ -13,7 +13,7 @@ 7.0 34.0.1 - android-33 + android-34 24.0.8215888 From 21e7049243fc07933b9bb9d9d6e8d513acb46a63 Mon Sep 17 00:00:00 2001 From: Jonathan Pryor Date: Sat, 5 Aug 2023 04:36:07 -0400 Subject: [PATCH 179/308] [Xamarin.Android.Tools.AndroidSdk] Check all s (#214) Context: https://developercommunity.visualstudio.com/t/Cannot-deploy-to-Android-emulators-and-d/10428163 Context: https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1863835 Context: https://developer.android.com/guide/components/intents-filters The [``][0] and related elements within `AndroidManifest.xml` can have multiple [``][1]s specified. Android does not require that ``s be listed in any particular order. However, Visual Studio *does* care about the order (?!)! [Activity(Exported = true, Label = "@string/app_name", MainLauncher = true)] [IntentFilter( new[] { Android.Content.Intent.ActionView }, Categories = new[] { Android.Content.Intent.CategoryDefault, Android.Content.Intent.CategoryBrowsable, } )] [IntentFilter( new[] { Android.Content.Intent.ActionMain }, Categories = new[] { Android.Content.Intent.CategoryLauncher, Android.Content.Intent.CategoryLeanbackLauncher, } )] public partial class MainActivity : Activity {} If the `[IntentFilter]` with `Android.Content.Intent.ActionMain` is *not* first, then Visual Studio does not consider this Activity to be a launchable activity. The reason for this is, in part, because `AndroidAppManifest.GetLaunchableActivities()` only looked at the *first* `` to see if it had the category of `android.intent.category.LAUNCHER`. Update `AndroidAppManifest.GetLaunchableActivities()` so that *all* `` elements are checked for the `.LAUNCHER` category. **Workaround**: Specify the `[IntentFilter]` with `Intent.ActionMain` first: [Activity(Exported = true, Label = "@string/app_name", MainLauncher = true)] [IntentFilter( new[] { Android.Content.Intent.ActionMain }, Categories = new[] { Android.Content.Intent.CategoryLauncher, Android.Content.Intent.CategoryLeanbackLauncher, } )] [IntentFilter( new[] { Android.Content.Intent.ActionView }, Categories = new[] { Android.Content.Intent.CategoryDefault, Android.Content.Intent.CategoryBrowsable, } )] public partial class MainActivity : Activity {} Note: this change, in and of itself, may not be sufficient to fix Visual Studio, as there are a few other places that have the same "only check the first ``" bug. [0]: https://developer.android.com/guide/topics/manifest/activity-element [1]: https://developer.android.com/guide/topics/manifest/intent-filter-element --- .../AndroidAppManifest.cs | 3 +-- .../AndroidAppManifestTests.cs | 10 ++++++++++ .../Resources/manifest-simplewidget.xml | 11 +++++++++++ 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidAppManifest.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidAppManifest.cs index 5ef7e138fa1..7ba12c9aa33 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidAppManifest.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidAppManifest.cs @@ -323,8 +323,7 @@ void RemoveAndroidPermissions (IEnumerable permissions) IEnumerable GetLaunchableActivities () { foreach (var activity in application.Elements ("activity")) { - var filter = activity.Element ("intent-filter"); - if (filter != null) { + foreach (var filter in activity.Elements ("intent-filter")) { foreach (var category in filter.Elements ("category")) if (category != null && (string?)category.Attribute (aName) == "android.intent.category.LAUNCHER") yield return activity; diff --git a/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/AndroidAppManifestTests.cs b/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/AndroidAppManifestTests.cs index c387ce22851..243224d58ef 100644 --- a/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/AndroidAppManifestTests.cs +++ b/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/AndroidAppManifestTests.cs @@ -45,6 +45,16 @@ static XDocument GetTestAppManifest () } } + [Test] + public void GetLaunchableActivityNames () + { + var versions = new AndroidVersions (Array.Empty()); + var manifest = AndroidAppManifest.Load (GetTestAppManifest (), versions); + var launchers = manifest.GetLaunchableActivityNames ().ToList (); + Assert.AreEqual (1, launchers.Count); + Assert.AreEqual (".HasMultipleIntentFilters", launchers [0]); + } + [Test] public void SetNewPermissions () { diff --git a/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/Resources/manifest-simplewidget.xml b/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/Resources/manifest-simplewidget.xml index 966639d4e51..c834c6cfa2c 100644 --- a/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/Resources/manifest-simplewidget.xml +++ b/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/Resources/manifest-simplewidget.xml @@ -22,6 +22,17 @@ + + + + + + + + + + + From 75d13b62b607364bb16d99ff0e5033090391ff9a Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Wed, 23 Aug 2023 13:12:53 -0500 Subject: [PATCH 180/308] [build] set `$(DisableTransitiveFrameworkReferenceDownloads)`=true (#216) As we consume nightly .NET 8 builds, they sometimes depend on nightly .NET 7 builds. One error you can run into is: error NU1102: Unable to find package Microsoft.AspNetCore.App.Ref with version (= 7.0.11) error NU1102: Unable to find package Microsoft.WindowsDesktop.App.Ref with version (= 7.0.11) For projects that are not even ASP.NET or Windows desktop apps! To even be able to access these feeds, they would need to be an entry within `NuGet.config` similar to We don't currently track these packages, because we don't actually use them. The .NET SDK team has provided a setting to workaround this, [`$(DisableTransitiveFrameworkReferenceDownloads)`][0], we have been [using in xamarin/xamarin-android for some time][1]. Let's do the same here to avoid this problem as seen in 2b8ac126. [0]: https://learn.microsoft.com/en-us/dotnet/core/project-sdk/msbuild-props#disabletransitiveframeworkreferencedownloads [1]: https://github.com/xamarin/xamarin-android/blob/6768c731d327c8148c45304c895ca8987a9cc2f1/Directory.Build.props#L26-L27 --- external/xamarin-android-tools/Directory.Build.props | 2 ++ 1 file changed, 2 insertions(+) diff --git a/external/xamarin-android-tools/Directory.Build.props b/external/xamarin-android-tools/Directory.Build.props index 33d982fb3d9..2a36ef78454 100644 --- a/external/xamarin-android-tools/Directory.Build.props +++ b/external/xamarin-android-tools/Directory.Build.props @@ -3,6 +3,8 @@ Debug + + true Date: Tue, 19 Sep 2023 20:07:17 -0300 Subject: [PATCH 181/308] Update OpenJDK location for OpenJDK17 on windows - VS installer start using a zip payload to install the OpenJDK on %programFiles%/Android/openjdk/jdk-* --- .../Jdks/MicrosoftOpenJdkLocations.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Jdks/MicrosoftOpenJdkLocations.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Jdks/MicrosoftOpenJdkLocations.cs index 9274eb6b0cf..157cbd5bc96 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Jdks/MicrosoftOpenJdkLocations.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Jdks/MicrosoftOpenJdkLocations.cs @@ -12,6 +12,7 @@ class MicrosoftOpenJdkLocations : JdkLocations { internal static IEnumerable GetMicrosoftOpenJdks (Action logger) { return GetMacOSSystemJdks ("microsoft-*.jdk", logger) + .Concat (GetWindowsFileSystemJdks (Path.Combine ("Android", "openjdk", "jdk-*"), logger)) .Concat (GetWindowsFileSystemJdks (Path.Combine ("Microsoft", "jdk-*"), logger)) .Concat (GetWindowsRegistryJdks (logger, @"SOFTWARE\Microsoft\JDK", "*", @"hotspot\MSI", "Path")) .OrderByDescending (jdk => jdk, JdkInfoVersionComparer.Default); From 2d7065a82934417d51d5c3e7575208ade55b76b4 Mon Sep 17 00:00:00 2001 From: Marek Habersack Date: Wed, 15 Nov 2023 18:10:06 +0000 Subject: [PATCH 182/308] Update the maximum NDK version to 26 (#219) This tracks the current NDK LTS release. --- .../src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkBase.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkBase.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkBase.cs index f00b1a1bb20..2737f09bb21 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkBase.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkBase.cs @@ -11,7 +11,7 @@ abstract class AndroidSdkBase { // When this changes, update the test: Xamarin.Android.Tools.Tests.AndroidSdkInfoTests.Ndk_MultipleNdkVersionsInSdk const int MinimumCompatibleNDKMajorVersion = 16; - const int MaximumCompatibleNDKMajorVersion = 25; + const int MaximumCompatibleNDKMajorVersion = 26; static readonly char[] SourcePropertiesKeyValueSplit = new char[] { '=' }; From 9e5b14ce6b985cea8dec6be0d6afa35ac0b7e92d Mon Sep 17 00:00:00 2001 From: Juan Marcelo Tondato Date: Thu, 9 Nov 2023 13:43:35 -0300 Subject: [PATCH 183/308] Bump android-sdk platforms-tools to version 34.0.5 That revison is available on Xamarin Android Feed for d17.9 --- .../Xamarin.Android.Tools.Versions.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.Versions.props b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.Versions.props index d93900efd27..9a06185e2ab 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.Versions.props +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.Versions.props @@ -11,7 +11,7 @@ --> 32.0.0 7.0 - 34.0.1 + 34.0.5 android-34 24.0.8215888 From 70f5829c3c50dba387efb57e5c702ea497e66686 Mon Sep 17 00:00:00 2001 From: Juan Marcelo Tondato Date: Thu, 9 Nov 2023 13:45:26 -0300 Subject: [PATCH 184/308] Bump android-sdk cmdline-tools to version 11.0 That version is available on Xamarin Android feed for d17.9 Enables the posibility to creates avd for pixel 6/7 on Visual Studio Device Manager tool --- .../Xamarin.Android.Tools.Versions.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.Versions.props b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.Versions.props index 9a06185e2ab..5d6c7494a62 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.Versions.props +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.Versions.props @@ -10,7 +10,7 @@ along with any other repo which references androidtools. --> 32.0.0 - 7.0 + 11.0 34.0.5 android-34 From 023e7a2c78c17a88af8030b151a4234e8754ce4b Mon Sep 17 00:00:00 2001 From: Juan Marcelo Tondato Date: Wed, 15 Nov 2023 22:22:13 -0300 Subject: [PATCH 185/308] Bump android-sdk build-tool version to 34.0.0 --- .../Xamarin.Android.Tools.Versions.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.Versions.props b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.Versions.props index 5d6c7494a62..f05391c4188 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.Versions.props +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.Versions.props @@ -9,7 +9,7 @@ If this file is changed the submodule for androidtools should be updated, along with any other repo which references androidtools. --> - 32.0.0 + 34.0.0 11.0 34.0.5 From bbdb2a43651b8e10b97ec46889a577bae30c0f6e Mon Sep 17 00:00:00 2001 From: Juan Marcelo Tondato Date: Wed, 15 Nov 2023 22:23:00 -0300 Subject: [PATCH 186/308] Bump android-sdk NDK version to 26.1.10909125 --- .../Xamarin.Android.Tools.Versions.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.Versions.props b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.Versions.props index f05391c4188..13e6387a815 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.Versions.props +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.Versions.props @@ -14,7 +14,7 @@ 34.0.5 android-34 - 24.0.8215888 + 26.1.10909125 26.1.1 From c3f608f2cbce3935d972fbcfa42aaa4dea2d2392 Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Thu, 30 Nov 2023 09:58:15 -0600 Subject: [PATCH 187/308] [build] update $(MSBuildPackageReferenceVersion) to 17.6.3 (#221) Context: https://github.com/xamarin/xamarin-android/pull/8366 xamarin/xamarin-android#8366 is attempting to build xamarin-android against .NET 9 previews, and currently fails: (Restore target) -> tests/MSBuildDeviceIntegration/MSBuildDeviceIntegration.csproj : error NU1605: Warning As Error: Detected package downgrade: Microsoft.Build.Framework from 17.5.0 to 17.3.2. Reference the package directly from the project to select a different version. tests/MSBuildDeviceIntegration/MSBuildDeviceIntegration.csproj : error NU1605: MSBuildDeviceIntegration -> MSBuild.StructuredLogger 2.2.100 -> doh (>= 17.5.0) tests/MSBuildDeviceIntegration/MSBuildDeviceIntegration.csproj : error NU1605: MSBuildDeviceIntegration -> Microsoft.Build.Framework (>= 17.3.2) tests/MSBuildDeviceIntegration/MSBuildDeviceIntegration.csproj : error NU1605: Warning As Error: Detected package downgrade: Microsoft.Build.Utilities.Core from 17.5.0 to 17.3.2. Reference the package directly from the project to select a different version. tests/MSBuildDeviceIntegration/MSBuildDeviceIntegration.csproj : error NU1605: MSBuildDeviceIntegration -> MSBuild.StructuredLogger 2.2.100 -> Microsoft.Build.Utilities.Core (>= 17.5.0) tests/MSBuildDeviceIntegration/MSBuildDeviceIntegration.csproj : error NU1605: MSBuildDeviceIntegration -> Microsoft.Build.Utilities.Core (>= 17.3.2) We need to update `MSBuild.StructuredLogger` package to be able to move to .NET 9, as the `.binlog` file format changed in .NET 9. The newer `MSBuild.StructuredLogger` package, in turn, requires a newer `Microsoft.Build.Framework` package version, which is controlled here. Update the `$(MSBuildPackageReferenceVersion)` MSBuild property so that (1) it can now be overridden, which would allow version updates in the future without requiring submodule updates, and (2) update the the default value to 17.6.3. I fear that if we move to 17.8.3, we may potentially break other repos. VS 2022 17.6 is an LTS release that feels "safe", is new enough to solve the issue, and hopefully won't break anyone? Additionally, update the `System.Security.Cryptography.Xml` package so that it now uses an overridable `$(SystemSecurityCryptographyXmlVersion)` MSBuild property, which defaults to 7.0.1, in order to fix this build error: src\Microsoft.Android.Build.BaseTasks\Microsoft.Android.Build.BaseTasks.csproj : error NU1605: Detected package downgrade: System.Security.Cryptography.Xml from 7.0.1 to 6.0.1. Reference the package directly from the project to select a different version. src\Microsoft.Android.Build.BaseTasks\Microsoft.Android.Build.BaseTasks.csproj : error NU1605: Microsoft.Android.Build.BaseTasks -> Microsoft.Build.Tasks.Core 17.6.3 -> System.Security.Cryptography.Xml (>= 7.0.1) src\Microsoft.Android.Build.BaseTasks\Microsoft.Android.Build.BaseTasks.csproj : error NU1605: Microsoft.Android.Build.BaseTasks -> System.Security.Cryptography.Xml (>= 6.0.1) tests\Microsoft.Android.Build.BaseTasks-Tests\Microsoft.Android.Build.BaseTasks-Tests.csproj : error NU1605: Detected package downgrade: System.Security.Cryptography.Xml from 7.0.1 to 6.0.1. Reference the package directly from the project to select a different version. tests\Microsoft.Android.Build.BaseTasks-Tests\Microsoft.Android.Build.BaseTasks-Tests.csproj : error NU1605: Microsoft.Android.Build.BaseTasks-Tests -> Microsoft.Build.Tasks.Core 17.6.3 -> System.Security.Cryptography.Xml (>= 7.0.1) tests\Microsoft.Android.Build.BaseTasks-Tests\Microsoft.Android.Build.BaseTasks-Tests.csproj : error NU1605: Microsoft.Android.Build.BaseTasks-Tests -> System.Security.Cryptography.Xml (>= 6.0.1) --- .../MSBuildReferences.projitems | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems index b5976b342a5..4c6a97c4e24 100644 --- a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems +++ b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems @@ -4,7 +4,8 @@ - 17.3.2 + 17.8.3 + 7.0.1 3.0.0 7.1.0-final.1.21458.1 @@ -14,7 +15,7 @@ - + From 4a4037af77fe3222d598669e976b0bd915695ebb Mon Sep 17 00:00:00 2001 From: Jonathan Pobst Date: Thu, 30 Nov 2023 06:13:07 -1000 Subject: [PATCH 188/308] [MSBuildReferences.projitems] Require opt-in to use `Microsoft.Build` (#220) Projects that import `MSBuildReferences.projitems` receive a `@(PackageReference)` to the `Microsoft.Build` NuGet package. However, many consumers have `$(TargetFramework)`=netstandard2.0, and the `Microsoft.Build` package does not have support netstandard2.0, resulting in the following warnings: Warning NU1701 Package 'Microsoft.Build 17.3.2' was restored using '.NETFramework,Version=v4.6.1, .NETFramework,Version=v4.6.2, .NETFramework,Version=v4.7, .NETFramework,Version=v4.7.1, .NETFramework,Version=v4.7.2, .NETFramework,Version=v4.8, .NETFramework,Version=v4.8.1' instead of the project target framework '.NETStandard,Version=v2.0'. This package may not be fully compatible with your project. Warning NU1701 Package 'Microsoft.IO.Redist 6.0.0' was restored using '.NETFramework,Version=v4.6.1, .NETFramework,Version=v4.6.2, .NETFramework,Version=v4.7, .NETFramework,Version=v4.7.1, .NETFramework,Version=v4.7.2, .NETFramework,Version=v4.8, .NETFramework,Version=v4.8.1' instead of the project target framework '.NETStandard,Version=v2.0'. This package may not be fully compatible with your project. Most consumers do not actually need this package, so we can make it opt-in to prevent the warnings and extra dependencies in those projects. Packages opt-in to referencing the `Microsoft.Build` package by setting the `$(_IncludeMicrosoftBuildPackage)` MSBuild property to true: <_IncludeMicrosoftBuildPackage>true Note: this will be a "breaking" change to an unknown amount of consumers. We're crossing our fingers that this won't break too much. Enabling this change fixes 226 CI warnings in xamarin-android. --- .../MSBuildReferences.projitems | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems index 4c6a97c4e24..91a10a353f2 100644 --- a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems +++ b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems @@ -11,7 +11,7 @@ - + From 3626cc72c1bf2b74a050edb846a9a5ba9ac2bcc3 Mon Sep 17 00:00:00 2001 From: Peter Collins Date: Wed, 17 Jan 2024 10:17:56 -0800 Subject: [PATCH 189/308] [ci] Add API Scan job (#225) Context: https://devdiv.visualstudio.com/DevDiv/_wiki/wikis/DevDiv.wiki/25351/APIScan-step-by-step-guide-to-setting-up-a-Pipeline The ApiScan task has been added to pipeline runs against `main`. This task should help us identify related issues earlier, rather than having to wait for a full scan of VS. --- .../azure-pipelines.yaml | 76 ++++++++++++++++++- 1 file changed, 75 insertions(+), 1 deletion(-) diff --git a/external/xamarin-android-tools/azure-pipelines.yaml b/external/xamarin-android-tools/azure-pipelines.yaml index d0f314fbb17..8c2bcfcf3a2 100644 --- a/external/xamarin-android-tools/azure-pipelines.yaml +++ b/external/xamarin-android-tools/azure-pipelines.yaml @@ -10,6 +10,10 @@ pr: - d16-* - d17-* +parameters: +- name: ApiScanSourceBranch + default: 'refs/heads/main' + # Global variables variables: - name: DotNetCoreVersion @@ -66,9 +70,79 @@ jobs: displayName: 'Build NuGet' condition: and(succeeded(), eq(variables['agent.os'], 'Windows_NT')) + - task: PublishPipelineArtifact@1 + displayName: Upload Build Output + inputs: + path: bin/Debug + artifactName: Output - $(System.JobName) + - task: PublishPipelineArtifact@1 displayName: Upload Artifacts inputs: path: $(Build.ArtifactStagingDirectory) - artifactName: $(vmImage) + artifactName: Artifacts - $(System.JobName) condition: always() + +- job: api_scan + displayName: API Scan + dependsOn: build + condition: and(eq(dependencies.build.result, 'Succeeded'), eq(variables['Build.SourceBranch'], '${{ parameters.ApiScanSourceBranch }}')) + pool: + name: Azure Pipelines + vmImage: windows-2022 + timeoutInMinutes: 480 + workspace: + clean: all + steps: + - task: DownloadPipelineArtifact@2 + displayName: Download build artifacts + inputs: + artifactName: Output - windows + downloadPath: $(Build.SourcesDirectory) + + - task: CopyFiles@2 + displayName: Collect Files for APIScan + inputs: + Contents: | + $(Build.SourcesDirectory)\**\?(*.dll|*.exe|*.pdb) + !$(Build.SourcesDirectory)\**\ls-jdks.* + TargetFolder: $(Build.StagingDirectory)\apiscan + OverWrite: true + flattenFolders: true + + - powershell: Get-ChildItem -Path "$(Build.StagingDirectory)\apiscan" -Recurse + displayName: List Files for APIScan + + - task: APIScan@2 + displayName: Run APIScan + inputs: + softwareFolder: $(Build.StagingDirectory)\apiscan + symbolsFolder: 'SRV*http://symweb;$(Build.StagingDirectory)\apiscan' + softwareName: $(ApiScanName) + softwareVersionNum: $(Build.SourceBranchName)-$(Build.SourceVersion)$(System.JobAttempt) + isLargeApp: true + toolVersion: Latest + env: + AzureServicesAuthConnectionString: runAs=App;AppId=$(ApiScanClientId);TenantId=$(ApiScanTenant);AppKey=$(ApiScanSecret) + + - task: SdtReport@2 + displayName: Guardian Export - Security Report + inputs: + GdnExportAllTools: false + GdnExportGdnToolApiScan: true + GdnExportOutputSuppressionFile: source.gdnsuppress + + - task: PublishSecurityAnalysisLogs@3 + displayName: Publish Guardian Artifacts + inputs: + ArtifactName: APIScan Logs + ArtifactType: Container + AllTools: false + APIScan: true + ToolLogsNotFoundAction: Warning + + - task: PostAnalysis@2 + displayName: Fail Build on Guardian Issues + inputs: + GdnBreakAllTools: false + GdnBreakGdnToolApiScan: true From 462be9009a184771366137d0e04371400cdbfc74 Mon Sep 17 00:00:00 2001 From: Peter Collins Date: Wed, 17 Jan 2024 10:18:17 -0800 Subject: [PATCH 190/308] [ci] Only enable CodeQL on Windows build job (#224) Enabling CodeQL on macOS seems to be occasionally causing issues with the Run Tests step: The argument -p:UseSharedCompilation=false is invalid. Please use the /help option to check the list of valid arguments. We can avoid this by only enabling CodeQL on the Windows build job. --- external/xamarin-android-tools/azure-pipelines.yaml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/external/xamarin-android-tools/azure-pipelines.yaml b/external/xamarin-android-tools/azure-pipelines.yaml index 8c2bcfcf3a2..487dd20cc44 100644 --- a/external/xamarin-android-tools/azure-pipelines.yaml +++ b/external/xamarin-android-tools/azure-pipelines.yaml @@ -18,8 +18,6 @@ parameters: variables: - name: DotNetCoreVersion value: 6.0.x - - name: Codeql.Enabled - value: True jobs: - job: build @@ -33,6 +31,7 @@ jobs: vmImage: macOS-12 windows: vmImage: windows-2022 + Codeql.Enabled: true pool: vmImage: $(vmImage) From 8368ef61bbc31c1446a3a0bf261b6c16d2394c73 Mon Sep 17 00:00:00 2001 From: Peter Collins Date: Tue, 23 Jan 2024 09:24:43 -0800 Subject: [PATCH 191/308] [Xamarin.Android.Tools.Versions] Add JavaSdkVersion (#226) A `$(JavaSdkVersion)` property has been added to specify the preferred Java SDK version that will be returned by ``. --- .../Xamarin.Android.Tools.Versions.props | 1 + 1 file changed, 1 insertion(+) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.Versions.props b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.Versions.props index 13e6387a815..7d66259b2e4 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.Versions.props +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.Versions.props @@ -15,6 +15,7 @@ android-34 26.1.10909125 + 17.0.8.1 26.1.1 From 93876015c0c2114542520d244b39293c5e290d11 Mon Sep 17 00:00:00 2001 From: Jeffrey Stedfast Date: Wed, 7 Feb 2024 10:57:03 -0500 Subject: [PATCH 192/308] Only conditionally include in the AndroidManifest.xml when saving Fixes https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1874249 --- .../AndroidAppManifest.cs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidAppManifest.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidAppManifest.cs index 7ba12c9aa33..5955292b60a 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidAppManifest.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidAppManifest.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Linq; using System.Xml; using System.Collections.Generic; @@ -45,7 +45,7 @@ public class AndroidAppManifest if (manifest.Element ("uses-sdk") is XElement uses) usesSdk = uses; else - manifest.Add (usesSdk = new XElement ("uses-sdk")); + usesSdk = new XElement ("uses-sdk"); } public static string CanonicalizePackageName (string packageNameOrAssemblyName) @@ -71,7 +71,6 @@ public static AndroidAppManifest Create (string packageName, string appLabel, An return new AndroidAppManifest (versions, XDocument.Parse ( @" - ")) { @@ -102,6 +101,14 @@ public static AndroidAppManifest Load (XDocument doc, AndroidVersions versions) public void Write (XmlWriter writer) { + // Make sure that if the XML element does not have any attributes (i.e. minSdkVersion + // and targetSdkVersion), do NOT write it into the output. This is to avoid issues like + // https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1874249/ + if (usesSdk.HasAttributes && usesSdk.Parent == null) + manifest.Add (usesSdk); + else if (!usesSdk.HasAttributes && usesSdk.Parent != null) + usesSdk.Remove (); + doc.Save (writer); } From 52c204d3ef1a71558eed02d0c1237b021666d447 Mon Sep 17 00:00:00 2001 From: Jeffrey Stedfast Date: Thu, 8 Feb 2024 09:25:45 -0500 Subject: [PATCH 193/308] Added unit tests --- .../AndroidAppManifestTests.cs | 42 ++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/AndroidAppManifestTests.cs b/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/AndroidAppManifestTests.cs index 243224d58ef..5c58199aa52 100644 --- a/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/AndroidAppManifestTests.cs +++ b/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/AndroidAppManifestTests.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; @@ -172,5 +172,45 @@ public void CanGetAppTheme () Assert.AreEqual ("@android:style/Theme.Material.Light", manifest.ApplicationTheme); } + + [Test] + public void CanAddAndRemoveUsesSdk () + { + XNamespace aNS = "http://schemas.android.com/apk/res/android"; + var versions = new AndroidVersions (new AndroidVersion [0]); + var doc = XDocument.Parse (@" + + + + + "); + var manifest = AndroidAppManifest.Load (doc, versions); + + manifest.MinSdkVersion = null; + manifest.TargetSdkVersion = null; + + var sb = new StringBuilder (); + using (var writer = XmlWriter.Create (sb)) { + manifest.Write (writer); + } + + var newDoc = XDocument.Parse (sb.ToString ()); + var usesSdk = newDoc.Element ("manifest").Element ("uses-sdk"); + Assert.IsNull (usesSdk, "uses-sdk should not exist"); + + manifest.MinSdkVersion = 8; + manifest.TargetSdkVersion = 12; + + sb = new StringBuilder (); + using (var writer = XmlWriter.Create (sb)) { + manifest.Write (writer); + } + + newDoc = XDocument.Parse (sb.ToString ()); + usesSdk = newDoc.Element ("manifest").Element ("uses-sdk"); + Assert.IsNotNull (usesSdk, "uses-sdk should exist"); + Assert.AreEqual ("8", usesSdk.Attribute (aNS + "minSdkVersion").Value); + Assert.AreEqual ("12", usesSdk.Attribute (aNS + "targetSdkVersion").Value); + } } } From 03019a5aa57a6c5f7a0d9bd353e75e651032764b Mon Sep 17 00:00:00 2001 From: Dean Ellis Date: Thu, 22 Feb 2024 09:32:31 +0000 Subject: [PATCH 194/308] Bump LibZipSharp to 3.1.1 (#228) --- .../MSBuildReferences.projitems | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems index 91a10a353f2..7b7cb9a7e8b 100644 --- a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems +++ b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems @@ -6,7 +6,7 @@ 17.8.3 7.0.1 - 3.0.0 + 3.1.1 7.1.0-final.1.21458.1 From 1542b53bee5ca29ca693a5c55b821bbf93dfd83b Mon Sep 17 00:00:00 2001 From: Peter Collins Date: Thu, 11 Apr 2024 13:30:14 -0700 Subject: [PATCH 195/308] [ci] Disable API Scan on xamarin/public (#231) We have migrated most of our API Scan runs to authenticate with a new managed identity workflow. It is not trivial to add this to pipeline runs in the xamarin/public azure pipelines project. With the old authentication approach being removed shortly we'll disable this API Scan job in the public project for now, and continue to get coverage through xamarin/androidtools and xamarin/xamarin-android scans. --- external/xamarin-android-tools/azure-pipelines.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/external/xamarin-android-tools/azure-pipelines.yaml b/external/xamarin-android-tools/azure-pipelines.yaml index 487dd20cc44..e8872f9bad2 100644 --- a/external/xamarin-android-tools/azure-pipelines.yaml +++ b/external/xamarin-android-tools/azure-pipelines.yaml @@ -85,7 +85,8 @@ jobs: - job: api_scan displayName: API Scan dependsOn: build - condition: and(eq(dependencies.build.result, 'Succeeded'), eq(variables['Build.SourceBranch'], '${{ parameters.ApiScanSourceBranch }}')) + condition: false + #condition: and(eq(dependencies.build.result, 'Succeeded'), eq(variables['Build.SourceBranch'], '${{ parameters.ApiScanSourceBranch }}')) pool: name: Azure Pipelines vmImage: windows-2022 From 6c55dce8fe89ef687b1cbb39eb344294c639aeb7 Mon Sep 17 00:00:00 2001 From: Dean Ellis Date: Wed, 17 Apr 2024 12:41:52 +0100 Subject: [PATCH 196/308] Check for ANDROID_HOME Sdk location on Windows. (#230) For some reason we never check for the `%ANDROID_HOME%` environment variable on Windows. This might cause issues with people using VSCode since we probably will be using environment variables as a backup when using that IDE. Add a check to use `%ANDROID_HOME%` after checking the registry entries. This way Visual Studio and Android SDK Installer paths will still be preferred. --- .../Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkWindows.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkWindows.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkWindows.cs index e1d940438e2..98f8b7e47ab 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkWindows.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkWindows.cs @@ -93,6 +93,9 @@ protected override IEnumerable GetAllAvailableAndroidSdks () if (CheckRegistryKeyForExecutable (root, ANDROID_INSTALLER_PATH, ANDROID_INSTALLER_KEY, wow, "platform-tools", Adb)) yield return RegistryEx.GetValueString (root, ANDROID_INSTALLER_PATH, ANDROID_INSTALLER_KEY, wow) ?? ""; + foreach (string dir in GetSdkFromEnvironmentVariables ()) + yield return dir; + // Check some hardcoded paths for good measure var paths = new string [] { Path.Combine (Environment.GetFolderPath (Environment.SpecialFolder.LocalApplicationData), "Xamarin", "MonoAndroid", "android-sdk-windows"), From f1e89455d4ac7e9b4706dd52f801979b9acf1e52 Mon Sep 17 00:00:00 2001 From: Peter Collins Date: Fri, 19 Apr 2024 11:37:11 -0700 Subject: [PATCH 197/308] [AndroidToolTask] Log tool output as error (#229) Context: #208 Updates AndroidToolTask to capture all standard output from the tool and log it as an error if the task fails. --- .../AndroidToolTask.cs | 17 ++++- .../UnhandledExceptionLogger.cs | 5 ++ .../AndroidToolTaskTests.cs | 62 ++++++++++++++++--- ...osoft.Android.Build.BaseTasks-Tests.csproj | 1 + 4 files changed, 76 insertions(+), 9 deletions(-) diff --git a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/AndroidToolTask.cs b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/AndroidToolTask.cs index cc01e8f5928..5e9cfe831e4 100644 --- a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/AndroidToolTask.cs +++ b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/AndroidToolTask.cs @@ -2,6 +2,8 @@ using System; using System.IO; +using System.Text; +using Microsoft.Build.Framework; using Microsoft.Build.Utilities; namespace Microsoft.Android.Build.Tasks @@ -12,6 +14,8 @@ public abstract class AndroidToolTask : ToolTask protected string WorkingDirectory { get; private set; } + StringBuilder toolOutput = new StringBuilder (); + public AndroidToolTask () { WorkingDirectory = Directory.GetCurrentDirectory(); @@ -20,13 +24,24 @@ public AndroidToolTask () public override bool Execute () { try { - return RunTask (); + bool taskResult = RunTask (); + if (!taskResult && !string.IsNullOrEmpty (toolOutput.ToString ())) { + Log.LogUnhandledToolError (TaskPrefix, toolOutput.ToString ().Trim ()); + } + toolOutput.Clear (); + return taskResult; } catch (Exception ex) { Log.LogUnhandledException (TaskPrefix, ex); return false; } } + protected override void LogEventsFromTextOutput (string singleLine, MessageImportance messageImportance) + { + base.LogEventsFromTextOutput (singleLine, messageImportance); + toolOutput.AppendLine (singleLine); + } + // Most ToolTask's do not override Execute and // just expect the base to be called public virtual bool RunTask () => base.Execute (); diff --git a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/UnhandledExceptionLogger.cs b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/UnhandledExceptionLogger.cs index 07a3fc3d7c2..67d2086882c 100644 --- a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/UnhandledExceptionLogger.cs +++ b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/UnhandledExceptionLogger.cs @@ -85,5 +85,10 @@ static void LogUnhandledException (Action logCodedError, string p else logCodedError (prefix + "7000", ex.ToString ()); } + + public static void LogUnhandledToolError (this TaskLoggingHelper log, string prefix, string toolOutput) + { + log.LogCodedError ($"XA{prefix}0000", toolOutput); + } } } diff --git a/external/xamarin-android-tools/tests/Microsoft.Android.Build.BaseTasks-Tests/AndroidToolTaskTests.cs b/external/xamarin-android-tools/tests/Microsoft.Android.Build.BaseTasks-Tests/AndroidToolTaskTests.cs index 8b35ce5ba1d..834d3986a71 100644 --- a/external/xamarin-android-tools/tests/Microsoft.Android.Build.BaseTasks-Tests/AndroidToolTaskTests.cs +++ b/external/xamarin-android-tools/tests/Microsoft.Android.Build.BaseTasks-Tests/AndroidToolTaskTests.cs @@ -1,18 +1,24 @@ using System.IO; using System.Collections.Generic; -using System.Threading.Tasks; using Microsoft.Android.Build.BaseTasks.Tests.Utilities; using Microsoft.Android.Build.Tasks; using NUnit.Framework; using Microsoft.Build.Framework; -using Xamarin.Build; +using NUnit.Framework.Internal; +using System.Linq; namespace Microsoft.Android.Build.BaseTasks.Tests { [TestFixture] public class AndroidToolTaskTests { - public class MyAndroidTask : AndroidTask { + List errors; + List warnings; + List messages; + MockBuildEngine engine; + + public class MyAndroidTask : AndroidTask + { public override string TaskPrefix {get;} = "MAT"; public string Key { get; set; } public string Value { get; set; } @@ -25,7 +31,8 @@ public override bool RunTask () } } - public class MyOtherAndroidTask : AndroidTask { + public class MyOtherAndroidTask : AndroidTask + { public override string TaskPrefix {get;} = "MOAT"; public string Key { get; set; } public bool ProjectSpecific { get; set; } = false; @@ -40,6 +47,24 @@ public override bool RunTask () } } + public class DotnetToolOutputTestTask : AndroidToolTask + { + public override string TaskPrefix {get;} = "DTOT"; + protected override string ToolName => "dotnet"; + protected override string GenerateFullPathToTool () => ToolExe; + public string CommandLineArgs { get; set; } = "--info"; + protected override string GenerateCommandLineCommands () => CommandLineArgs; + } + + [SetUp] + public void TestSetup() + { + errors = new List (); + warnings = new List (); + messages = new List (); + engine = new MockBuildEngine (TestContext.Out, errors, warnings, messages); + } + [Test] [TestCase (true, true, true)] [TestCase (false, false, true)] @@ -47,8 +72,6 @@ public override bool RunTask () [TestCase (false, true, false)] public void TestRegisterTaskObjectCanRetrieveCorrectItem (bool projectSpecificA, bool projectSpecificB, bool expectedResult) { - var engine = new MockBuildEngine (TestContext.Out) { - }; var task = new MyAndroidTask () { BuildEngine = engine, Key = "Foo", @@ -72,8 +95,6 @@ public void TestRegisterTaskObjectCanRetrieveCorrectItem (bool projectSpecificA, [TestCase (false, true, false)] public void TestRegisterTaskObjectFailsWhenDirectoryChanges (bool projectSpecificA, bool projectSpecificB, bool expectedResult) { - var engine = new MockBuildEngine (TestContext.Out) { - }; MyAndroidTask task; var currentDir = Directory.GetCurrentDirectory (); Directory.SetCurrentDirectory (Path.Combine (currentDir, "..")); @@ -96,5 +117,30 @@ public void TestRegisterTaskObjectFailsWhenDirectoryChanges (bool projectSpecifi task2.Execute (); Assert.AreEqual (expectedResult, string.Compare (task2.Value, task.Value, ignoreCase: true) == 0); } + + [Test] + [TestCase ("invalidcommand", false, "You intended to execute a .NET program, but dotnet-invalidcommand does not exist.")] + [TestCase ("--info", true, "")] + public void FailedAndroidToolTaskShouldLogOutputAsError (string args, bool expectedResult, string expectedErrorText) + { + var task = new DotnetToolOutputTestTask () { + BuildEngine = engine, + CommandLineArgs = args, + }; + var taskSucceeded = task.Execute (); + Assert.AreEqual (expectedResult, taskSucceeded, "Task execution did not return expected value."); + + if (taskSucceeded) { + Assert.IsEmpty (errors, "Successful task should not have any errors."); + } else { + Assert.IsNotEmpty (errors, "Task expected to fail should have errors."); + Assert.AreEqual ("MSB6006", errors [0].Code, + $"Expected error code MSB6006 but got {errors [0].Code}"); + Assert.AreEqual ("XADTOT0000", errors [1].Code, + $"Expected error code XADTOT0000 but got {errors [1].Code}"); + Assert.IsTrue (errors.Any (e => e.Message.Contains (expectedErrorText)), + "Task expected to fail should contain expected error text."); + } + } } } diff --git a/external/xamarin-android-tools/tests/Microsoft.Android.Build.BaseTasks-Tests/Microsoft.Android.Build.BaseTasks-Tests.csproj b/external/xamarin-android-tools/tests/Microsoft.Android.Build.BaseTasks-Tests/Microsoft.Android.Build.BaseTasks-Tests.csproj index 52207653133..7f0fb6d769e 100644 --- a/external/xamarin-android-tools/tests/Microsoft.Android.Build.BaseTasks-Tests/Microsoft.Android.Build.BaseTasks-Tests.csproj +++ b/external/xamarin-android-tools/tests/Microsoft.Android.Build.BaseTasks-Tests/Microsoft.Android.Build.BaseTasks-Tests.csproj @@ -9,6 +9,7 @@ false $(TestOutputFullPath) false + Major From d78892271064f2fb9527c721cf9c1a8da70c6636 Mon Sep 17 00:00:00 2001 From: Matt Sylvia Date: Wed, 22 May 2024 14:36:16 -0400 Subject: [PATCH 198/308] [ci] Update OneLocBuildToken (#233) Update the token in the Localization step to one backed by a managed identity. --- .../Localize/onelocbuild.yaml | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/external/xamarin-android-tools/Localize/onelocbuild.yaml b/external/xamarin-android-tools/Localize/onelocbuild.yaml index 8af9f1037b6..c390acc461f 100644 --- a/external/xamarin-android-tools/Localize/onelocbuild.yaml +++ b/external/xamarin-android-tools/Localize/onelocbuild.yaml @@ -33,6 +33,22 @@ jobs: - checkout: self clean: true + # https://eng.ms/docs/cloud-ai-platform/devdiv/one-engineering-system-1es/1es-docs/1es-security-configuration/configuration-guides/pat-burndown-guidance#authentication-from-pipelines + # Requires Azure client 2.x + - task: AzureCLI@2 + displayName: 'Set AzDO.OneLocBuildToken' + enabled: true + inputs: + azureSubscription: 'VSEng-AzureDevOps-ceapex-OneLocBuild' # Azure DevOps service connection + scriptType: 'pscore' + scriptLocation: 'inlineScript' + inlineScript: | + # if this fails, check out this bash script that includes diagnostics: + # https://gist.github.com/johnterickson/19f80a3e969e39f1000d118739176e62 + # Note that the resource is specified to limit the token to Azure DevOps + $token = az account get-access-token --query accessToken --resource 499b84ac-1321-427f-aa17-267ca6975798 -o tsv + Write-Host "##vso[task.setvariable variable=AzDO.OneLocBuildToken;issecret=true]${token}" + - task: OneLocBuild@2 displayName: OneLocBuild env: @@ -41,7 +57,7 @@ jobs: locProj: Localize/LocProject.json outDir: $(Build.StagingDirectory) packageSourceAuth: patAuth - patVariable: $(OneLocBuild--PAT) + patVariable: $(AzDO.OneLocBuildToken) isCreatePrSelected: true repoType: gitHub gitHubPatVariable: $(github--pat--vs-mobiletools-engineering-service2) From 035485f080710bb6c0cb1a2b05d4356aed2fec5d Mon Sep 17 00:00:00 2001 From: Matt Sylvia Date: Fri, 24 May 2024 19:41:52 -0400 Subject: [PATCH 199/308] Force loc task pool image to windows (#234) --- external/xamarin-android-tools/Localize/onelocbuild.yaml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/external/xamarin-android-tools/Localize/onelocbuild.yaml b/external/xamarin-android-tools/Localize/onelocbuild.yaml index c390acc461f..4b0220f630b 100644 --- a/external/xamarin-android-tools/Localize/onelocbuild.yaml +++ b/external/xamarin-android-tools/Localize/onelocbuild.yaml @@ -23,7 +23,10 @@ jobs: - job: OneLocBuild displayName: OneLocBuild condition: and(eq(variables['System.TeamProject'], 'DevDiv'), eq(variables['Build.SourceBranch'], 'refs/heads/main')) - pool: VSEngSS-MicroBuild2022-1ES + pool: + name: AzurePipelines-EO + demands: + - ImageOverride -equals 1ESPT-Windows2022 timeoutInMinutes: 30 variables: - group: Xamarin-Secrets From d903394a5de2d05281a170cacdc9db9cfeb3acf8 Mon Sep 17 00:00:00 2001 From: Dean Ellis Date: Tue, 11 Jun 2024 10:19:00 +0100 Subject: [PATCH 200/308] Bump LibZipSharp and Xamarin.Build.AsyncTask (#236) Bring in the 3.3.0 LibZipSharp to get the corrupt zip fix. Bring in the AsyncTask package to get the Telemetry API --- .../MSBuildReferences.projitems | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems index 7b7cb9a7e8b..f1b0ad08df3 100644 --- a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems +++ b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems @@ -6,7 +6,7 @@ 17.8.3 7.0.1 - 3.1.1 + 3.3.0 7.1.0-final.1.21458.1 @@ -17,7 +17,7 @@ - + From a31475c79ce6982634e2cd7cc7dd7e21d94e2cdd Mon Sep 17 00:00:00 2001 From: Dean Ellis Date: Tue, 11 Jun 2024 19:42:06 +0100 Subject: [PATCH 201/308] Bump to dotnet/msbuild/v17.9.5@33de0b22 (#232) Changes: https://github.com/dotnet/msbuild/compare/v17.8.3...v17.9.5 % git diff --shortstat v17.8.3...v17.9.5 994 files changed, 12077 insertions(+), 8431 deletions(-) Bump `$(MSBuildPackageReferenceVersion)` to 17.9.5, which updates us to use the NuGet packages: * [Microsoft.Build](https://www.nuget.org/packages/Microsoft.Build/17.9.5) * [Microsoft.Build.Framework](https://www.nuget.org/packages/Microsoft.Build.Framework/17.9.5) * [Microsoft.Build.Tasks.Core](https://www.nuget.org/packages/Microsoft.Build.Tasks.Core/17.9.5) * [Microsoft.Build.Utilities.Core](https://www.nuget.org/packages/Microsoft.Build.Utilities.Core/17.9.5) We're bumping in large part so that we can make potetial use of the new [`TaskLoggingHelper.LogTelemetry()` method][0] [0]: https://learn.microsoft.com/dotnet/api/microsoft.build.utilities.tasklogginghelper.logtelemetry --- .../MSBuildReferences.projitems | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems index f1b0ad08df3..96c56c0e80b 100644 --- a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems +++ b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems @@ -4,7 +4,7 @@ - 17.8.3 + 17.9.5 7.0.1 3.3.0 7.1.0-final.1.21458.1 From 014a1b5a9787bd9eb003e9fddffa00848b394509 Mon Sep 17 00:00:00 2001 From: Peter Collins Date: Thu, 13 Jun 2024 14:19:49 -0400 Subject: [PATCH 202/308] Update README.md and build scripts (#238) --- .../xamarin-android-tools/CODE-OF-CONDUCT.md | 6 ++++ external/xamarin-android-tools/LICENSE | 2 -- external/xamarin-android-tools/Makefile | 5 +-- external/xamarin-android-tools/README.md | 35 ++++++------------- .../azure-pipelines.yaml | 12 +++---- .../build-tools/scripts/msbuild.mk | 4 +-- .../Xamarin.Android.Tools.AndroidSdk.csproj | 2 +- 7 files changed, 26 insertions(+), 40 deletions(-) create mode 100644 external/xamarin-android-tools/CODE-OF-CONDUCT.md diff --git a/external/xamarin-android-tools/CODE-OF-CONDUCT.md b/external/xamarin-android-tools/CODE-OF-CONDUCT.md new file mode 100644 index 00000000000..775f221c98e --- /dev/null +++ b/external/xamarin-android-tools/CODE-OF-CONDUCT.md @@ -0,0 +1,6 @@ +# Code of Conduct + +This project has adopted the code of conduct defined by the Contributor Covenant +to clarify expected behavior in our community. + +For more information, see the [.NET Foundation Code of Conduct](https://dotnetfoundation.org/code-of-conduct). diff --git a/external/xamarin-android-tools/LICENSE b/external/xamarin-android-tools/LICENSE index e5d11ed6686..67f20e2e241 100644 --- a/external/xamarin-android-tools/LICENSE +++ b/external/xamarin-android-tools/LICENSE @@ -1,5 +1,3 @@ -xamarin-android-tools - The MIT License (MIT) Copyright (c) .NET Foundation Contributors diff --git a/external/xamarin-android-tools/Makefile b/external/xamarin-android-tools/Makefile index f9af94f984b..865b03118eb 100644 --- a/external/xamarin-android-tools/Makefile +++ b/external/xamarin-android-tools/Makefile @@ -5,14 +5,11 @@ V ?= 0 include build-tools/scripts/msbuild.mk all: - $(MSBUILD) /restore $(MSBUILD_FLAGS) Xamarin.Android.Tools.sln + $(MSBUILD) $(MSBUILD_FLAGS) Xamarin.Android.Tools.sln clean: -$(MSBUILD) $(MSBUILD_FLAGS) /t:Clean Xamarin.Android.Tools.sln -prepare: - nuget restore Xamarin.Android.Tools.sln - run-all-tests: dotnet test -l "console;verbosity=detailed" -l trx \ tests/Xamarin.Android.Tools.AndroidSdk-Tests/Xamarin.Android.Tools.AndroidSdk-Tests.csproj diff --git a/external/xamarin-android-tools/README.md b/external/xamarin-android-tools/README.md index 92720c90b99..fb3bf39cdd4 100644 --- a/external/xamarin-android-tools/README.md +++ b/external/xamarin-android-tools/README.md @@ -1,11 +1,11 @@ -# xamarin-android-tools +# android-tools -**xamarin-android-tools** is a repo to easily share code between the -[xamarin-android][x-a] repo and the Xamarin.Android commercial tooling, +**android-tools** is a repo to easily share code between the +[xamarin-android][android] repo and the .NET for Android commercial tooling, such as IDE extensions, without requiring that the IDE extensions -submodule the entire **xamarin-android** repo, which is gigantic. +submodule the entire **android** repo, which is gigantic. -[x-a]: https://github.com/xamarin/xamarin-android +[android]: https://github.com/xamarin/xamarin-android # Build Status @@ -23,7 +23,7 @@ submodule the entire **xamarin-android** repo, which is gigantic. # Build Requirements -**xamarin-android-tools** requires Mono 4.8 or later and `nuget`. +**-android-tools** requires .NET 6 or later. # Build Configuration @@ -38,18 +38,11 @@ The default `make all` target accepts the following optional # Build -To build **xamarin-android-tools**: +To build **android-tools**: - msbuild /restore Xamarin.Android.Tools.sln + dotnet build Xamarin.Android.Tools.sln -Alternatively, first prepare the project: - - make prepare - -This will perform `nuget restore` and any other pre-build tasks -that need to be performed. - -Next, run `make`: +Alternatively run `make`: make @@ -71,19 +64,13 @@ e.g. `bin\TestDebug`, contains unit tests and related files. * `bin\$(Configuration)`: redistributable build artifacts. * `bin\Test$(Configuration)`: Unit tests and related files. -# Contributing +# Distribution Package versioning follows [Semantic Versioning 2.0.0](https://semver.org/). The major version in the `nuget.version` file should be updated when a breaking change is introduced. The minor version should be updated when new functionality is added. The patch version will be automatically determined by the number of commits since the last version change. -This project has adopted the code of conduct defined by the Contributor Covenant -to clarify expected behavior in our community. For more information, see the -[.NET Foundation Code of Conduct](http://www.dotnetfoundation.org/code-of-conduct). - -# Distribution - Xamarin.Android.Tools.AndroidSdk nupkg files are produced for every build which occurrs on [Azure Devops](https://dev.azure.com/xamarin/Xamarin/_build?definitionId=2&_a=summary). To download one of these packages, navigate to the build you are interested in and click on the `Artifacts` button. @@ -111,4 +98,4 @@ We use [Mono's Coding Guidelines](http://www.mono-project.com/community/contribu # Reporting Bugs -We use [GitHub](https://github.com/xamarin/xamarin-android-tools/issues) to track issues. +We use [GitHub](https://github.com/dotnet/android-tools/issues) to track issues. diff --git a/external/xamarin-android-tools/azure-pipelines.yaml b/external/xamarin-android-tools/azure-pipelines.yaml index e8872f9bad2..46eb96ec5c7 100644 --- a/external/xamarin-android-tools/azure-pipelines.yaml +++ b/external/xamarin-android-tools/azure-pipelines.yaml @@ -1,14 +1,12 @@ -name: Xamarin.Android.Tools $(Rev:r) +name: Android.Tools $(Rev:r) trigger: - - main - - d16-* - - d17-* +- main +- release/* pr: - - main - - d16-* - - d17-* +- main +- release/* parameters: - name: ApiScanSourceBranch diff --git a/external/xamarin-android-tools/build-tools/scripts/msbuild.mk b/external/xamarin-android-tools/build-tools/scripts/msbuild.mk index 54adf86daaf..6201e263b88 100644 --- a/external/xamarin-android-tools/build-tools/scripts/msbuild.mk +++ b/external/xamarin-android-tools/build-tools/scripts/msbuild.mk @@ -22,7 +22,7 @@ # $(MSBUILD): The MSBuild program to use. Defaults to `xbuild` unless overridden. # $(MSBUILD_FLAGS): Additional MSBuild flags; contains $(CONFIGURATION), $(V), $(MSBUILD_ARGS). -MSBUILD = msbuild +MSBUILD = dotnet build MSBUILD_FLAGS = /p:Configuration=$(CONFIGURATION) $(MSBUILD_ARGS) ifeq ($(OS),Darwin) @@ -35,7 +35,7 @@ ifneq ($(V),0) MSBUILD_FLAGS += /v:diag endif # $(V) != 0 -ifeq ($(MSBUILD),msbuild) +ifeq ($(MSBUILD),dotnet build) USE_MSBUILD = 1 endif # $(MSBUILD) == msbuild diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj index d37e9cb6985..ffaf31cd40f 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj @@ -10,7 +10,7 @@ Xamarin.Android.Tools Xamarin MIT - https://github.com/xamarin/xamarin-android-tools + https://github.com/dotnet/android-tools Xamarin tools for interacting with the Android SDK. Copyright © Xamarin 2011-2016 Xamarin;Xamarin.Android From de96238db5ddc3d17da239224261247bf6387b06 Mon Sep 17 00:00:00 2001 From: Peter Collins Date: Fri, 14 Jun 2024 14:27:56 -0400 Subject: [PATCH 203/308] Update pipeline and pipeline info in README.md (#239) The pipeline has been moved to the devdiv org, and in the future can be split into public/internal pipelines in the dotnet org if necessary. --- external/xamarin-android-tools/README.md | 19 ++---------- .../azure-pipelines.yaml | 29 ++++++++++--------- 2 files changed, 18 insertions(+), 30 deletions(-) diff --git a/external/xamarin-android-tools/README.md b/external/xamarin-android-tools/README.md index fb3bf39cdd4..93efde718ba 100644 --- a/external/xamarin-android-tools/README.md +++ b/external/xamarin-android-tools/README.md @@ -1,4 +1,5 @@ # android-tools +[![Build Status](https://dev.azure.com/devdiv/DevDiv/_apis/build/status%2FXamarin%2FAndroid%2Fandroid-tools?branchName=main)](https://dev.azure.com/devdiv/DevDiv/_build/latest?definitionId=22338&branchName=main) **android-tools** is a repo to easily share code between the [xamarin-android][android] repo and the .NET for Android commercial tooling, @@ -7,20 +8,6 @@ submodule the entire **android** repo, which is gigantic. [android]: https://github.com/xamarin/xamarin-android -# Build Status - -| Platform | Status | -|-----------------------|--------| -| **macOS** | [![macOS Build Status][azure-macOS-icon]][azure-macOS-status] | -| **Windows** | [![Windows Build Status][azure-Windows-icon]][azure-Windows-status] | - - -[azure-macOS-icon]: https://dev.azure.com/xamarin/public/_apis/build/status/xamarin-android-tools -[azure-macOS-status]: https://dev.azure.com/xamarin/public/_build/latest?definitionId=3 -[azure-Windows-icon]: https://dev.azure.com/xamarin/public/_apis/build/status/xamarin-android-tools -[azure-Windows-status]: https://dev.azure.com/xamarin/public/_build/latest?definitionId=3 - - # Build Requirements **-android-tools** requires .NET 6 or later. @@ -71,7 +58,7 @@ The major version in the `nuget.version` file should be updated when a breaking The minor version should be updated when new functionality is added. The patch version will be automatically determined by the number of commits since the last version change. -Xamarin.Android.Tools.AndroidSdk nupkg files are produced for every build which occurrs on [Azure Devops](https://dev.azure.com/xamarin/Xamarin/_build?definitionId=2&_a=summary). +Xamarin.Android.Tools.AndroidSdk nupkg files are produced for every build which occurrs on [Azure Devops](https://devdiv.visualstudio.com/DevDiv/_build?definitionId=22338). To download one of these packages, navigate to the build you are interested in and click on the `Artifacts` button. Alternatively, "unofficial" releases are currently hosted on the [Xamarin.Android](https://dev.azure.com/xamarin/public/_packaging?_a=feed&feed=Xamarin.Android) feed. @@ -85,8 +72,6 @@ Add the feed to your project's `NuGet.config` to reference these packages: ``` -An Azure Pipelines [Release ](https://dev.azure.com/xamarin/public/_release?view=mine&_a=releases&definitionId=12) can be manually triggered to push a new version to this feed. - # Mailing Lists To discuss this project, and participate in the design, we use the diff --git a/external/xamarin-android-tools/azure-pipelines.yaml b/external/xamarin-android-tools/azure-pipelines.yaml index 46eb96ec5c7..7c83e8f2234 100644 --- a/external/xamarin-android-tools/azure-pipelines.yaml +++ b/external/xamarin-android-tools/azure-pipelines.yaml @@ -1,4 +1,4 @@ -name: Android.Tools $(Rev:r) +name: android-tools $(Rev:r) trigger: - main @@ -10,7 +10,7 @@ pr: parameters: - name: ApiScanSourceBranch - default: 'refs/heads/main' + default: refs/heads/main # Global variables variables: @@ -21,22 +21,17 @@ jobs: - job: build displayName: Build and Test timeoutInMinutes: 60 - cancelTimeoutInMinutes: 2 - strategy: matrix: macOS: - vmImage: macOS-12 + vmImage: macOS-13 windows: vmImage: windows-2022 Codeql.Enabled: true - pool: vmImage: $(vmImage) - workspace: clean: all - steps: - checkout: self clean: true @@ -46,11 +41,14 @@ jobs: inputs: version: $(DotNetCoreVersion) - - script: dotnet build Xamarin.Android.Tools.sln -bl:$(Build.ArtifactStagingDirectory)/build.binlog - displayName: 'Build solution Xamarin.Android.Tools.sln' + - task: DotNetCoreCLI@2 + displayName: Build solution Xamarin.Android.Tools.sln + inputs: + projects: Xamarin.Android.Tools.sln + arguments: -bl:$(Build.ArtifactStagingDirectory)/build.binlog - task: DotNetCoreCLI@2 - displayName: 'Run Tests' + displayName: Run Tests inputs: command: test projects: bin/TestDebug-net*/**/*-Tests.dll @@ -63,8 +61,13 @@ jobs: Write-Host "##vso[task.setvariable variable=xat.nuget.version]$version" condition: and(succeeded(), eq(variables['agent.os'], 'Windows_NT')) - - script: dotnet pack src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj -p:Version=$(xat.nuget.version) -p:PackageOutputPath=$(Build.ArtifactStagingDirectory) -bl:$(Build.ArtifactStagingDirectory)/pack.binlog - displayName: 'Build NuGet' + - task: DotNetCoreCLI@2 + displayName: Build NuGet + inputs: + command: custom + projects: src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj + custom: pack + arguments: -p:Version=$(xat.nuget.version) -p:PackageOutputPath=$(Build.ArtifactStagingDirectory) -bl:$(Build.ArtifactStagingDirectory)/pack.binlog condition: and(succeeded(), eq(variables['agent.os'], 'Windows_NT')) - task: PublishPipelineArtifact@1 From a1853041c3c3befdc3dc4df5627134b2a2d10d29 Mon Sep 17 00:00:00 2001 From: Peter Collins Date: Thu, 27 Jun 2024 13:12:43 -0400 Subject: [PATCH 204/308] [BaseTasks] Import Xamarin.Build.AsyncTask (#237) We've decided to move https://github.com/xamarin/Xamarin.Build.AsyncTask into this repository as part of dotnet org migration efforts. Source and test content from https://github.com/xamarin/Xamarin.Build.AsyncTask/commit/db4ce14dac has been added to Microsoft.Android.Build.BaseTasks. The `AndroidAsyncTask` and `AsyncTask` classes have been merged, we've decided we no longer need both levels of abstraction. `Microsoft.Android.Build.BaseTasks-Tests` has been updated to target net8.0 as I noticed the tests were no longer running: /Users/runner/hostedtoolcache/dotnet/dotnet test /Users/runner/work/1/s/bin/TestDebug-net6.0/Microsoft.Android.Build.BaseTasks-Tests.dll --logger trx --results-directory /Users/runner/work/_temp Starting test execution, please wait... A total of 1 test files matched the specified pattern. No test is available in /Users/runner/work/1/s/bin/TestDebug-net6.0/Microsoft.Android.Build.BaseTasks-Tests.dll. Make sure that test discoverer & executors are registered and platform & framework version settings are appropriate and try again. --- .../azure-pipelines.yaml | 5 + .../AndroidAsyncTask.cs | 52 --- .../AsyncTask.cs | 437 ++++++++++++++++++ .../AsyncTaskExtensions.cs | 1 - .../MSBuildExtensions.cs | 1 - .../MSBuildReferences.projitems | 1 - .../UnhandledExceptionLogger.cs | 1 - .../AsyncTaskExtensionsTests.cs | 18 +- .../AsyncTaskTests.cs | 105 +++++ ...osoft.Android.Build.BaseTasks-Tests.csproj | 2 +- 10 files changed, 559 insertions(+), 64 deletions(-) delete mode 100644 external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/AndroidAsyncTask.cs create mode 100644 external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/AsyncTask.cs create mode 100644 external/xamarin-android-tools/tests/Microsoft.Android.Build.BaseTasks-Tests/AsyncTaskTests.cs diff --git a/external/xamarin-android-tools/azure-pipelines.yaml b/external/xamarin-android-tools/azure-pipelines.yaml index 7c83e8f2234..ce23fb5bfd1 100644 --- a/external/xamarin-android-tools/azure-pipelines.yaml +++ b/external/xamarin-android-tools/azure-pipelines.yaml @@ -41,6 +41,11 @@ jobs: inputs: version: $(DotNetCoreVersion) + - task: UseDotNet@2 + displayName: Use .NET Core 8.0.x + inputs: + version: 8.0.x + - task: DotNetCoreCLI@2 displayName: Build solution Xamarin.Android.Tools.sln inputs: diff --git a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/AndroidAsyncTask.cs b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/AndroidAsyncTask.cs deleted file mode 100644 index a04d86ff40b..00000000000 --- a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/AndroidAsyncTask.cs +++ /dev/null @@ -1,52 +0,0 @@ -// https://github.com/xamarin/xamarin-android/blob/9fca138604c53989e1cff7fc0c2e939583b4da28/src/Xamarin.Android.Build.Tasks/Tasks/AndroidTask.cs#L27 - -using System; -using Xamarin.Build; -using static System.Threading.Tasks.TaskExtensions; - -namespace Microsoft.Android.Build.Tasks -{ - public abstract class AndroidAsyncTask : AsyncTask - { - public abstract string TaskPrefix { get; } - - public override bool Execute () - { - try { - return RunTask (); - } catch (Exception ex) { - this.LogUnhandledException (TaskPrefix, ex); - return false; - } - } - - /// - /// Typically `RunTaskAsync` will be the preferred method to override, - /// however this method can be overridden instead for Tasks that will - /// run quickly and do not need to be asynchronous. - /// - public virtual bool RunTask () - { - Yield (); - try { - this.RunTask (() => RunTaskAsync ()) - .Unwrap () - .ContinueWith (Complete); - - // This blocks on AsyncTask.Execute, until Complete is called - return base.Execute (); - } finally { - Reacquire (); - } - } - - /// - /// Override this method for simplicity of AsyncTask usage: - /// * Yield / Reacquire is handled for you - /// * RunTaskAsync is already on a background thread - /// - public virtual System.Threading.Tasks.Task RunTaskAsync () => System.Threading.Tasks.Task.CompletedTask; - - protected object ProjectSpecificTaskObjectKey (object key) => (key, WorkingDirectory); - } -} diff --git a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/AsyncTask.cs b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/AsyncTask.cs new file mode 100644 index 00000000000..7a8a6072c6c --- /dev/null +++ b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/AsyncTask.cs @@ -0,0 +1,437 @@ +// https://github.com/xamarin/xamarin-android/blob/9fca138604c53989e1cff7fc0c2e939583b4da28/src/Xamarin.Android.Build.Tasks/Tasks/AndroidTask.cs#L27 +// https://github.com/xamarin/Xamarin.Build.AsyncTask/blob/db4ce14dacfef47435c238b1b681c124e60ea1a0/Xamarin.Build.AsyncTask/AsyncTask.cs + +using System; +using System.Diagnostics; +using System.IO; +using System.Linq; +using Microsoft.Build.Utilities; +using Microsoft.Build.Framework; +using System.Threading; +using static System.Threading.Tasks.TaskExtensions; +using System.Collections; +using System.Collections.Generic; + +namespace Microsoft.Android.Build.Tasks +{ + /// + /// Base class for tasks that need long-running cancellable asynchronous tasks + /// that don't block the UI thread in the IDE. + /// + public abstract class AsyncTask : Task, ICancelableTask + { + public abstract string TaskPrefix { get; } + + readonly CancellationTokenSource cts = new CancellationTokenSource (); + readonly Queue logMessageQueue = new Queue (); + readonly Queue warningMessageQueue = new Queue (); + readonly Queue errorMessageQueue = new Queue (); + readonly Queue customMessageQueue = new Queue (); + readonly Queue telemetryMessageQueue = new Queue (); + readonly ManualResetEvent logDataAvailable = new ManualResetEvent (false); + readonly ManualResetEvent errorDataAvailable = new ManualResetEvent (false); + readonly ManualResetEvent warningDataAvailable = new ManualResetEvent (false); + readonly ManualResetEvent customDataAvailable = new ManualResetEvent (false); + readonly ManualResetEvent telemetryDataAvailable = new ManualResetEvent (false); + readonly ManualResetEvent taskCancelled = new ManualResetEvent (false); + readonly ManualResetEvent completed = new ManualResetEvent (false); + bool isRunning = true; + object eventlock = new object (); + int uiThreadId = 0; + + /// + /// Indicates if the task will yield the node during tool execution. + /// + public bool YieldDuringToolExecution { get; set; } + + /// + /// The cancellation token to notify the cancellation requests + /// + public CancellationToken CancellationToken => cts.Token; + + /// + /// Gets the current working directory for the task, which is captured at task + /// creation time from . + /// + protected string WorkingDirectory { get; private set; } + + [Obsolete ("Do not use the Log.LogXXXX from within your Async task as it will Lock the Visual Studio UI. Use the this.LogXXXX methods instead.")] + private new TaskLoggingHelper Log => base.Log; + + /// + /// Initializes the task. + /// + protected AsyncTask () + { + YieldDuringToolExecution = false; + WorkingDirectory = Directory.GetCurrentDirectory (); + uiThreadId = Thread.CurrentThread.ManagedThreadId; + } + + public void Cancel () + => taskCancelled.Set (); + + protected void Complete (System.Threading.Tasks.Task task) + { + if (task.Exception != null) { + var ex = task.Exception.GetBaseException (); + this.LogUnhandledException (TaskPrefix, ex); + } + Complete (); + } + + public void Complete () + => completed.Set (); + + public void LogDebugTaskItems (string message, string [] items) + { + LogDebugMessage (message); + + if (items == null) + return; + + foreach (var item in items) + LogDebugMessage (" {0}", item); + } + + public void LogDebugTaskItems (string message, ITaskItem [] items) + { + LogDebugMessage (message); + + if (items == null) + return; + + foreach (var item in items) + LogDebugMessage (" {0}", item.ItemSpec); + } + + public void LogTelemetry (string eventName, IDictionary properties) + { + if (uiThreadId == Thread.CurrentThread.ManagedThreadId) { +#pragma warning disable 618 + Log.LogTelemetry (eventName, properties); + return; +#pragma warning restore 618 + } + + var data = new TelemetryEventArgs () { + EventName = eventName, + Properties = properties + }; + EnqueueMessage (telemetryMessageQueue, data, telemetryDataAvailable); + } + + public void LogMessage (string message) + => LogMessage (message, importance: MessageImportance.Normal); + + public void LogMessage (string message, params object [] messageArgs) + => LogMessage (string.Format (message, messageArgs)); + + public void LogDebugMessage (string message) + => LogMessage (message, importance: MessageImportance.Low); + + public void LogDebugMessage (string message, params object [] messageArgs) + => LogMessage (string.Format (message, messageArgs), importance: MessageImportance.Low); + + public void LogMessage (string message, MessageImportance importance = MessageImportance.Normal) + { + if (uiThreadId == Thread.CurrentThread.ManagedThreadId) { +#pragma warning disable 618 + Log.LogMessage (importance, message); + return; +#pragma warning restore 618 + } + + var data = new BuildMessageEventArgs ( + message: message, + helpKeyword: null, + senderName: null, + importance: importance + ); + EnqueueMessage (logMessageQueue, data, logDataAvailable); + } + + public void LogError (string message) + => LogCodedError (code: null, message: message, file: null, lineNumber: 0); + + public void LogError (string message, params object [] messageArgs) + => LogCodedError (code: null, message: string.Format (message, messageArgs)); + + public void LogCodedError (string code, string message) + => LogCodedError (code: code, message: message, file: null, lineNumber: 0); + + public void LogCodedError (string code, string message, params object [] messageArgs) + => LogCodedError (code: code, message: string.Format (message, messageArgs), file: null, lineNumber: 0); + + public void LogCodedError (string code, string file, int lineNumber, string message, params object [] messageArgs) + => LogCodedError (code: code, message: string.Format (message, messageArgs), file: file, lineNumber: lineNumber); + + public void LogCodedError (string code, string message, string file, int lineNumber) + { + if (uiThreadId == Thread.CurrentThread.ManagedThreadId) { +#pragma warning disable 618 + Log.LogError ( + subcategory: null, + errorCode: code, + helpKeyword: null, + file: file, + lineNumber: lineNumber, + columnNumber: 0, + endLineNumber: 0, + endColumnNumber: 0, + message: message + ); + return; +#pragma warning restore 618 + } + + var data = new BuildErrorEventArgs ( + subcategory: null, + code: code, + file: file, + lineNumber: lineNumber, + columnNumber: 0, + endLineNumber: 0, + endColumnNumber: 0, + message: message, + helpKeyword: null, + senderName: null + ); + EnqueueMessage (errorMessageQueue, data, errorDataAvailable); + } + + public void LogWarning (string message) + => LogCodedWarning (code: null, message: message, file: null, lineNumber: 0); + + public void LogWarning (string message, params object [] messageArgs) + => LogCodedWarning (code: null, message: string.Format (message, messageArgs)); + + public void LogCodedWarning (string code, string message) + => LogCodedWarning (code: code, message: message, file: null, lineNumber: 0); + + public void LogCodedWarning (string code, string message, params object [] messageArgs) + => LogCodedWarning (code: code, message: string.Format (message, messageArgs), file: null, lineNumber: 0); + + public void LogCodedWarning (string code, string file, int lineNumber, string message, params object [] messageArgs) + => LogCodedWarning (code: code, message: string.Format (message, messageArgs), file: file, lineNumber: lineNumber); + + public void LogCodedWarning (string code, string message, string file, int lineNumber) + { + if (uiThreadId == Thread.CurrentThread.ManagedThreadId) { +#pragma warning disable 618 + Log.LogWarning ( + subcategory: null, + warningCode: code, + helpKeyword: null, + file: file, + lineNumber: lineNumber, + columnNumber: 0, + endLineNumber: 0, + endColumnNumber: 0, + message: message + ); + return; +#pragma warning restore 618 + } + var data = new BuildWarningEventArgs ( + subcategory: null, + code: code, + file: file, + lineNumber: lineNumber, + columnNumber: 0, + endLineNumber: 0, + endColumnNumber: 0, + message: message, + helpKeyword: null, + senderName: null + ); + EnqueueMessage (warningMessageQueue, data, warningDataAvailable); + } + + public void LogCustomBuildEvent (CustomBuildEventArgs e) + { + if (uiThreadId == Thread.CurrentThread.ManagedThreadId) { + BuildEngine.LogCustomEvent (e); + return; + } + EnqueueMessage (customMessageQueue, e, customDataAvailable); + } + + bool ExecuteWaitForCompletion () + { + WaitForCompletion (); +#pragma warning disable 618 + return !Log.HasLoggedErrors; +#pragma warning restore 618 + } + + void EnqueueMessage (Queue queue, object item, ManualResetEvent resetEvent) + { + lock (queue.SyncRoot) { + queue.Enqueue (item); + lock (eventlock) { + if (isRunning) + resetEvent.Set (); + } + } + } + + void LogInternal (Queue queue, Action action, ManualResetEvent resetEvent) + { + lock (queue.SyncRoot) { + while (queue.Count > 0) { + var args = (T) queue.Dequeue (); + action (args); + } + resetEvent.Reset (); + } + } + + protected void Yield () + { + if (YieldDuringToolExecution && BuildEngine is IBuildEngine3) + ((IBuildEngine3) BuildEngine).Yield (); + } + + protected void Reacquire () + { + if (YieldDuringToolExecution && BuildEngine is IBuildEngine3) + ((IBuildEngine3) BuildEngine).Reacquire (); + } + + protected void WaitForCompletion () + { + WaitHandle [] handles = new WaitHandle [] { + logDataAvailable, + errorDataAvailable, + warningDataAvailable, + customDataAvailable, + telemetryDataAvailable, + taskCancelled, + completed, + }; + try { + while (isRunning) { + var index = (WaitHandleIndex) System.Threading.WaitHandle.WaitAny (handles, TimeSpan.FromMilliseconds (10)); + switch (index) { + case WaitHandleIndex.LogDataAvailable: + LogInternal (logMessageQueue, (e) => { +#pragma warning disable 618 + Log.LogMessage (e.Importance, e.Message); +#pragma warning restore 618 + }, logDataAvailable); + break; + case WaitHandleIndex.ErrorDataAvailable: + LogInternal (errorMessageQueue, (e) => { +#pragma warning disable 618 + Log.LogError ( + subcategory: null, + errorCode: e.Code, + helpKeyword: null, + file: e.File, + lineNumber: e.LineNumber, + columnNumber: e.ColumnNumber, + endLineNumber: e.EndLineNumber, + endColumnNumber: e.EndColumnNumber, + message: e.Message); +#pragma warning restore 618 + }, errorDataAvailable); + break; + case WaitHandleIndex.WarningDataAvailable: + LogInternal (warningMessageQueue, (e) => { +#pragma warning disable 618 + Log.LogWarning (subcategory: null, + warningCode: e.Code, + helpKeyword: null, + file: e.File, + lineNumber: e.LineNumber, + columnNumber: e.ColumnNumber, + endLineNumber: e.EndLineNumber, + endColumnNumber: e.EndColumnNumber, + message: e.Message); +#pragma warning restore 618 + }, warningDataAvailable); + break; + case WaitHandleIndex.CustomDataAvailable: + LogInternal (customMessageQueue, (e) => { + BuildEngine.LogCustomEvent (e); + }, customDataAvailable); + break; + case WaitHandleIndex.TelemetryDataAvailable: + LogInternal (telemetryMessageQueue, (e) => { + BuildEngine5.LogTelemetry (e.EventName, e.Properties); + }, telemetryDataAvailable); + break; + case WaitHandleIndex.TaskCancelled: + Cancel (); + cts.Cancel (); + isRunning = false; + break; + case WaitHandleIndex.Completed: + isRunning = false; + break; + } + } + + } finally { + + } + } + + public override bool Execute () + { + try { + return RunTask (); + } catch (Exception ex) { + this.LogUnhandledException (TaskPrefix, ex); + return false; + } + } + + /// + /// Typically `RunTaskAsync` will be the preferred method to override, + /// however this method can be overridden instead for Tasks that will + /// run quickly and do not need to be asynchronous. + /// + public virtual bool RunTask () + { + Yield (); + try { + this.RunTask (() => RunTaskAsync ()) + .Unwrap () + .ContinueWith (Complete); + + // This blocks on Execute, until Complete is called + return ExecuteWaitForCompletion (); + } finally { + Reacquire (); + } + } + + /// + /// Override this method for simplicity of AsyncTask usage: + /// + /// + /// Yield / Reacquire is handled for you + /// + /// + /// RunTaskAsync is already on a background thread + /// + /// + /// + public virtual System.Threading.Tasks.Task RunTaskAsync () => System.Threading.Tasks.Task.CompletedTask; + + protected object ProjectSpecificTaskObjectKey (object key) => (key, WorkingDirectory); + + private enum WaitHandleIndex + { + LogDataAvailable, + ErrorDataAvailable, + WarningDataAvailable, + CustomDataAvailable, + TelemetryDataAvailable, + TaskCancelled, + Completed, + } + } +} diff --git a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/AsyncTaskExtensions.cs b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/AsyncTaskExtensions.cs index fce4bc64e4d..6b643ee7d61 100644 --- a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/AsyncTaskExtensions.cs +++ b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/AsyncTaskExtensions.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; -using Xamarin.Build; namespace Microsoft.Android.Build.Tasks { diff --git a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildExtensions.cs b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildExtensions.cs index fd0909d754c..323c874e35b 100644 --- a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildExtensions.cs +++ b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildExtensions.cs @@ -9,7 +9,6 @@ using Microsoft.Build.Utilities; using Microsoft.Build.Framework; -using Xamarin.Build; namespace Microsoft.Android.Build.Tasks { diff --git a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems index 96c56c0e80b..5d6a62a5444 100644 --- a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems +++ b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems @@ -17,7 +17,6 @@ - diff --git a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/UnhandledExceptionLogger.cs b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/UnhandledExceptionLogger.cs index 67d2086882c..4cfe3c0bf92 100644 --- a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/UnhandledExceptionLogger.cs +++ b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/UnhandledExceptionLogger.cs @@ -5,7 +5,6 @@ using System.IO; using System.Threading.Tasks; using Microsoft.Build.Utilities; -using Xamarin.Build; namespace Microsoft.Android.Build.Tasks { diff --git a/external/xamarin-android-tools/tests/Microsoft.Android.Build.BaseTasks-Tests/AsyncTaskExtensionsTests.cs b/external/xamarin-android-tools/tests/Microsoft.Android.Build.BaseTasks-Tests/AsyncTaskExtensionsTests.cs index ecfc8667162..13a9762d170 100644 --- a/external/xamarin-android-tools/tests/Microsoft.Android.Build.BaseTasks-Tests/AsyncTaskExtensionsTests.cs +++ b/external/xamarin-android-tools/tests/Microsoft.Android.Build.BaseTasks-Tests/AsyncTaskExtensionsTests.cs @@ -2,7 +2,6 @@ using System.Threading.Tasks; using Microsoft.Android.Build.Tasks; using NUnit.Framework; -using Xamarin.Build; namespace Microsoft.Android.Build.BaseTasks.Tests { @@ -11,11 +10,16 @@ public class AsyncTaskExtensionsTests { const int Iterations = 32; + class TestAsyncTask : AsyncTask + { + public override string TaskPrefix => "TEST"; + } + [Test] public async Task RunTask () { bool set = false; - await new AsyncTask ().RunTask (delegate { set = true; }); // delegate { } has void return type + await new TestAsyncTask ().RunTask (delegate { set = true; }); // delegate { } has void return type Assert.IsTrue (set); } @@ -23,7 +27,7 @@ public async Task RunTask () public async Task RunTaskOfT () { bool set = false; - Assert.IsTrue (await new AsyncTask ().RunTask (() => set = true), "RunTask should return true"); + Assert.IsTrue (await new TestAsyncTask ().RunTask (() => set = true), "RunTask should return true"); Assert.IsTrue (set); } @@ -31,7 +35,7 @@ public async Task RunTaskOfT () public async Task WhenAll () { bool set = false; - await new AsyncTask ().WhenAll (new [] { 0 }, _ => set = true); + await new TestAsyncTask ().WhenAll (new [] { 0 }, _ => set = true); Assert.IsTrue (set); } @@ -40,7 +44,7 @@ public async Task WhenAllWithLock () { var input = new int [Iterations]; var output = new List (); - await new AsyncTask ().WhenAllWithLock (input, (i, l) => { + await new TestAsyncTask ().WhenAllWithLock (input, (i, l) => { lock (l) output.Add (i); }); Assert.AreEqual (Iterations, output.Count); @@ -50,7 +54,7 @@ public async Task WhenAllWithLock () public void ParallelForEach () { bool set = false; - new AsyncTask ().ParallelForEach (new [] { 0 }, _ => set = true); + new TestAsyncTask ().ParallelForEach (new [] { 0 }, _ => set = true); Assert.IsTrue (set); } @@ -59,7 +63,7 @@ public void ParallelForEachWithLock () { var input = new int [Iterations]; var output = new List (); - new AsyncTask ().ParallelForEachWithLock (input, (i, l) => { + new TestAsyncTask ().ParallelForEachWithLock (input, (i, l) => { lock (l) output.Add (i); }); Assert.AreEqual (Iterations, output.Count); diff --git a/external/xamarin-android-tools/tests/Microsoft.Android.Build.BaseTasks-Tests/AsyncTaskTests.cs b/external/xamarin-android-tools/tests/Microsoft.Android.Build.BaseTasks-Tests/AsyncTaskTests.cs new file mode 100644 index 00000000000..912b4c1a207 --- /dev/null +++ b/external/xamarin-android-tools/tests/Microsoft.Android.Build.BaseTasks-Tests/AsyncTaskTests.cs @@ -0,0 +1,105 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using System.Linq; +using Microsoft.Android.Build.BaseTasks.Tests.Utilities; +using Microsoft.Android.Build.Tasks; +using Microsoft.Build.Framework; +using NUnit.Framework; + +namespace Microsoft.Android.Build.BaseTasks.Tests +{ + [TestFixture] + public class AsyncTaskTests + { + List errors; + List warnings; + List messages; + MockBuildEngine engine; + + [SetUp] + public void TestSetup () + { + errors = new List (); + warnings = new List (); + messages = new List (); + engine = new MockBuildEngine (TestContext.Out, errors, warnings, messages); + } + + class AsyncTaskTest : AsyncTask + { + public override string TaskPrefix => "ATT"; + } + + public class AsyncMessage : AsyncTask + { + public override string TaskPrefix => "AM"; + + public string Text { get; set; } + + public override bool Execute () + { + LogTelemetry ("Test", new Dictionary () { { "Property", "Value" } }); + return base.Execute (); + } + + public override async Task RunTaskAsync () + { + await Task.Delay (5000); + LogMessage (Text); + Complete (); + } + } + + class AsyncTaskExceptionTest : AsyncTask + { + public override string TaskPrefix => "ATET"; + + public string ExceptionMessage { get; set; } + + public override Task RunTaskAsync () + { + throw new System.InvalidOperationException (ExceptionMessage); + } + } + + + [Test] + public void RunAsyncTask () + { + var task = new AsyncTaskTest () { + BuildEngine = engine, + }; + + Assert.IsTrue (task.Execute (), "Empty AsyncTask should have ran successfully."); + } + + [Test] + public void RunAsyncTaskOverride () + { + var message = "Hello Async World!"; + var task = new AsyncMessage () { + BuildEngine = engine, + Text = message, + }; + var taskSucceeded = task.Execute (); + Assert.IsTrue (messages.Any (e => e.Message.Contains (message)), + $"Task did not contain expected message text: '{message}'."); + } + + [Test] + public void RunAsyncTaskExpectedException () + { + var expectedException = "test exception!"; + var task = new AsyncTaskExceptionTest () { + BuildEngine = engine, + ExceptionMessage = expectedException, + }; + + Assert.IsFalse (task.Execute (), "Exception AsyncTask should have failed."); + Assert.IsTrue (errors.Count == 1, "Exception AsyncTask should have produced one error."); + Assert.IsTrue (errors[0].Message.Contains (expectedException), + $"Task did not contain expected error text: '{expectedException}'."); + } + + } +} diff --git a/external/xamarin-android-tools/tests/Microsoft.Android.Build.BaseTasks-Tests/Microsoft.Android.Build.BaseTasks-Tests.csproj b/external/xamarin-android-tools/tests/Microsoft.Android.Build.BaseTasks-Tests/Microsoft.Android.Build.BaseTasks-Tests.csproj index 7f0fb6d769e..99bdc267828 100644 --- a/external/xamarin-android-tools/tests/Microsoft.Android.Build.BaseTasks-Tests/Microsoft.Android.Build.BaseTasks-Tests.csproj +++ b/external/xamarin-android-tools/tests/Microsoft.Android.Build.BaseTasks-Tests/Microsoft.Android.Build.BaseTasks-Tests.csproj @@ -4,7 +4,7 @@ - net6.0 + net8.0 Microsoft.Android.Build.BaseTasks.Tests false $(TestOutputFullPath) From 25a95cfcb42e17958737c539d9d174fe0b245332 Mon Sep 17 00:00:00 2001 From: Dean Ellis Date: Tue, 6 Aug 2024 14:24:41 +0100 Subject: [PATCH 205/308] Be explicit about FileShare mode when using FileStream. (#240) We know that by default `FileShare` will be `Read` when using a `FileStream`. However lets be totally clear on what we want from this point on, so if there is a problem we know its not us. --- .../src/Microsoft.Android.Build.BaseTasks/Files.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Files.cs b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Files.cs index 75b0a3d20f5..9172847bb11 100644 --- a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Files.cs +++ b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Files.cs @@ -435,7 +435,7 @@ public static string HashFile (string filename) public static string HashFile (string filename, HashAlgorithm hashAlg) { - using (Stream file = new FileStream (filename, FileMode.Open, FileAccess.Read)) { + using (Stream file = new FileStream (filename, FileMode.Open, FileAccess.Read, FileShare.Read)) { byte[] hash = hashAlg.ComputeHash (file); return ToHexString (hash); } @@ -479,7 +479,7 @@ public static void DeleteFile (string filename, object log) public static bool IsPortablePdb (string filename) { try { - using (var fs = new FileStream (filename, FileMode.Open, FileAccess.Read)) { + using (var fs = new FileStream (filename, FileMode.Open, FileAccess.Read, FileShare.Read)) { using (var br = new BinaryReader (fs)) { return br.ReadUInt32 () == ppdb_signature; } From 2df8ce4725f25d9626a5ad6891af9aec8f5e1cf9 Mon Sep 17 00:00:00 2001 From: Peter Collins Date: Tue, 20 Aug 2024 10:57:42 -0400 Subject: [PATCH 206/308] Bump to dotnet/msbuild@10fbfbf2 (#241) Changes: https://github.com/dotnet/msbuild/compare/v17.9.5...v17.10.4 Updates $(MSBuildPackageReferenceVersion) to the latest VS 2022 LTS. This new package version brings in System.Security.Cryptography.Xml v8.0.0 as a transitive dependency, and as such the explicitl v7.0.1 package reference should no longer be necessary. An explicit package reference has been added for System.Formats.Asn1 v8.0.1 to address CVE-2024-38095. --- .../MSBuildReferences.projitems | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems index 5d6a62a5444..f781ee33125 100644 --- a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems +++ b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems @@ -4,8 +4,7 @@ - 17.9.5 - 7.0.1 + 17.10.4 3.3.0 7.1.0-final.1.21458.1 @@ -15,7 +14,8 @@ - + + From cbc3531ced4ce9433b3fdf1037191fa368de81dc Mon Sep 17 00:00:00 2001 From: Anita Chimnani <79668275+anchimna@users.noreply.github.com> Date: Wed, 4 Sep 2024 14:02:22 -0700 Subject: [PATCH 207/308] [Xamarin.Android.Tools.AndroidSdk] Add API-35 to KnownVersions (#243) Fixes: https://devdiv.visualstudio.com/DevDiv/_workitems/edit/2234904 --- .../src/Xamarin.Android.Tools.AndroidSdk/AndroidVersions.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidVersions.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidVersions.cs index a4818d73cc0..78c441ce47d 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidVersions.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidVersions.cs @@ -194,6 +194,9 @@ static bool MatchesId (AndroidVersion version, string id) new AndroidVersion (34, "14.0") { AlternateIds = new[]{ "U", "UpsideDownCake", "Upside Down Cake" }, }, + new AndroidVersion (35, "15.0") { + AlternateIds = new[]{ "V", "VanillaIceCream", "Vanilla Ice Cream" }, + }, }; } } From 70e4e7943b6be65ef30efde0304ac462baf5e74e Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Wed, 4 Sep 2024 20:16:57 -0500 Subject: [PATCH 208/308] [Microsoft.Android.Build.BaseTasks] report locked files in XA*7024 (#242) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Context: https://github.com/dotnet/msbuild/commit/5f27e4d861a19ecff094161ff203a8a5924bd3d2 Context: https://github.com/dotnet/msbuild/commit/8f2286275c24562f0b4b82cef9ff831bd10420c6 Since ~2017, MSBuild has added useful context to the `` task that reports *which* process holds file locks on Windows. This logic has slowly been added to more places in MSBuild, and eventually became the public [`LockCheck.GetLockedFileMessage()`][0] method in the [Microsoft.Build.Utilities.Core 17.11.4 NuGet][1]. Update `UnhandledExceptionLogger` so that when an `IOException` is encountered, we check the `IOException.Message` text for anything between single quotes, e.g. `'C:\this\is\a\path'`. After extracting any such value, *if* the value is a file, as per `File.Exists()`, we assume that this file is the one causing a file share-related exception, and call `LockCheck.GetLockedFileMessage(path)`. This allows us to report an error such as: error XAMYT7024: The file is locked by: "MSBuild (22040)". System.IO.IOException: The process cannot access the file 'D:\temp\tmphkqpda.tmp' because it is being used by another process. at Microsoft.Win32.SafeHandles.SafeFileHandle.CreateFile (String fullPath, FileMode mode, FileAccess access, FileShare share, FileOptions options) … [0]: https://learn.microsoft.com/en-us/dotnet/api/microsoft.build.utilities.lockcheck.getlockedfilemessage?view=msbuild-17-netcore [1]: https://www.nuget.org/packages/Microsoft.Build.Utilities.Core/17.11.4 --- .../MSBuildReferences.projitems | 2 +- .../UnhandledExceptionLogger.cs | 25 ++++- .../LockCheckTests.cs | 91 +++++++++++++++++++ 3 files changed, 115 insertions(+), 3 deletions(-) create mode 100644 external/xamarin-android-tools/tests/Microsoft.Android.Build.BaseTasks-Tests/LockCheckTests.cs diff --git a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems index f781ee33125..b61e6c3efd3 100644 --- a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems +++ b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems @@ -4,7 +4,7 @@ - 17.10.4 + 17.11.4 3.3.0 7.1.0-final.1.21458.1 diff --git a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/UnhandledExceptionLogger.cs b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/UnhandledExceptionLogger.cs index 4cfe3c0bf92..290df7a534d 100644 --- a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/UnhandledExceptionLogger.cs +++ b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/UnhandledExceptionLogger.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Text.RegularExpressions; using System.Threading.Tasks; using Microsoft.Build.Utilities; @@ -79,12 +80,32 @@ static void LogUnhandledException (Action logCodedError, string p logCodedError (prefix + "7027", ex.ToString ()); else if (ex is FileNotFoundException) // IOException logCodedError (prefix + "7028", ex.ToString ()); - else if (ex is IOException) - logCodedError (prefix + "7024", ex.ToString ()); + else if (ex is IOException ioex) + logCodedError (prefix + "7024", GetIOExceptionMessage (ioex)); else logCodedError (prefix + "7000", ex.ToString ()); } + static string GetIOExceptionMessage (IOException ex) + { + // If we find a file path in the message, and the file exists, check if it's locked + // en-US message is: + // The process cannot access the file 'D:\temp\tmpw5mhqp.tmp' because it is being used by another process. + var matches = Regex.Matches (ex.Message, @"'([^']+)'"); + for (int i = 0; i < matches.Count; ++i) { + string path = matches [i].Groups [1].Value; + if (!File.Exists (path)) { + continue; + } + string processes = LockCheck.GetLockedFileMessage (path); + if (string.IsNullOrEmpty (processes)) { + continue; + } + return $"{processes}.{Environment.NewLine}{ex.ToString ()}"; + } + return ex.ToString (); + } + public static void LogUnhandledToolError (this TaskLoggingHelper log, string prefix, string toolOutput) { log.LogCodedError ($"XA{prefix}0000", toolOutput); diff --git a/external/xamarin-android-tools/tests/Microsoft.Android.Build.BaseTasks-Tests/LockCheckTests.cs b/external/xamarin-android-tools/tests/Microsoft.Android.Build.BaseTasks-Tests/LockCheckTests.cs new file mode 100644 index 00000000000..cc68eef99fa --- /dev/null +++ b/external/xamarin-android-tools/tests/Microsoft.Android.Build.BaseTasks-Tests/LockCheckTests.cs @@ -0,0 +1,91 @@ +using System; +using System.Collections.Generic; +using System.IO; +using Microsoft.Android.Build.BaseTasks.Tests.Utilities; +using Microsoft.Android.Build.Tasks; +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; +using NUnit.Framework; + +namespace Microsoft.Android.Build.BaseTasks.Tests; + +[TestFixture] +public class LockCheckTests +{ + string tempFile; + Stream tempStream; + List errors; + List warnings; + List messages; + MockBuildEngine engine; + + [SetUp] + public void Setup () + { + tempFile = Path.GetTempFileName (); + tempStream = File.Create (tempFile); + errors = new List (); + warnings = new List (); + messages = new List (); + engine = new MockBuildEngine (TestContext.Out, errors, warnings, messages); + } + + [TearDown] + public void TearDown () + { + tempStream.Dispose (); + if (File.Exists (tempFile)) + File.Delete (tempFile); + } + + class MyTask : AndroidTask + { + public string Path { get; set; } + + public override string TaskPrefix => "MYT"; + + public override bool RunTask () + { + using var stream = File.Create (Path); + return false; + } + } + + void AssertFileLocked (string actual) + { + Assert.IsNotEmpty (actual); + StringAssert.StartsWith ("The file is locked by:", actual); + StringAssert.IsMatch (@"\d+", actual, "Should contain a PID!"); + } + + [Test] + public void LockCheck_FileLocked () + { + string actual = LockCheck.GetLockedFileMessage (tempFile); + if (OperatingSystem.IsWindows ()) { + AssertFileLocked (actual); + } else { + Assert.IsEmpty (actual); + } + } + + [Test] + public void LockCheck_AndroidTask () + { + if (!OperatingSystem.IsWindows ()) + Assert.Ignore ("Test only valid on Windows"); + + var task = new MyTask { + BuildEngine = engine, + Path = tempFile, + }; + task.Execute (); + + // error XAMYT7024: The file is locked by: "testhost (22040)" + // System.IO.IOException: The process cannot access the file 'D:\temp\tmphkqpda.tmp' because it is being used by another process. + // at Microsoft.Win32.SafeHandles.SafeFileHandle.CreateFile (String fullPath, FileMode mode, FileAccess access, FileShare share, FileOptions options) + // ... rest of stacktrace + Assert.AreEqual (1, errors.Count); + AssertFileLocked (errors [0].Message); + } +} From 3350da0254ddb987e792d183c7fa8f02a4e4a371 Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Thu, 12 Sep 2024 14:22:12 -0500 Subject: [PATCH 209/308] [Microsoft.Android.Build.BaseTasks] report locked files for UnauthorizedAccessException (#244) Context: https://github.com/dotnet/android/issues/9133 In the above issue, a customer got: error XARDF7019: System.UnauthorizedAccessException: Access to the path 'GoogleGson.dll' is denied. at System.IO.Directory.DeleteHelper(String fullPath, String userPath, Boolean recursive, Boolean throwOnTopLevelDirectoryNotFound, WIN32_FIND_DATA& data) at System.IO.Directory.Delete(String fullPath, String userPath, Boolean recursive, Boolean checkHost) at Xamarin.Android.Tasks.RemoveDirFixed.RunTask() in /Users/runner/work/1/s/xamarin-android/src/Xamarin.Android.Build.Tasks/Tasks/RemoveDirFixed.cs:line 54 MauiApp2 (net9.0-android) C:\Program Files\dotnet\packs\Microsoft.Android.Sdk.Windows\34.99.0-preview.6.340\tools\Xamarin.Android.Common.targets Let's expand upon 70e4e794 to include `UnauthorizedAccessException`. Unfortunately, I wasn't able to *reproduce* this exception, I tried several things: * `File.Delete` on a file * `File.Delete` on the currently running test assembly * `File.SetAttributes` to `ReadOnly` on a file * `Directory.Delete` on a directory with readonly files I settled on just throwing `UnauthorizedAccessException` with the exact same message reported in dotnet/android#9133. It may or may not work, depending on if the message has a valid path to the file. This still seems useful though -- better than nothing. --- .../UnhandledExceptionLogger.cs | 8 +++---- .../LockCheckTests.cs | 21 ++++++++++++++----- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/UnhandledExceptionLogger.cs b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/UnhandledExceptionLogger.cs index 290df7a534d..06a34fe9a96 100644 --- a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/UnhandledExceptionLogger.cs +++ b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/UnhandledExceptionLogger.cs @@ -62,8 +62,8 @@ static void LogUnhandledException (Action logCodedError, string p logCodedError (prefix + "7017", ex.ToString ()); else if (ex is TypeInitializationException) logCodedError (prefix + "7018", ex.ToString ()); - else if (ex is UnauthorizedAccessException) - logCodedError (prefix + "7019", ex.ToString ()); + else if (ex is UnauthorizedAccessException uaex) + logCodedError (prefix + "7019", GetFileLockedExceptionMessage (uaex)); else if (ex is ApplicationException) logCodedError (prefix + "7020", ex.ToString ()); else if (ex is KeyNotFoundException) @@ -81,12 +81,12 @@ static void LogUnhandledException (Action logCodedError, string p else if (ex is FileNotFoundException) // IOException logCodedError (prefix + "7028", ex.ToString ()); else if (ex is IOException ioex) - logCodedError (prefix + "7024", GetIOExceptionMessage (ioex)); + logCodedError (prefix + "7024", GetFileLockedExceptionMessage (ioex)); else logCodedError (prefix + "7000", ex.ToString ()); } - static string GetIOExceptionMessage (IOException ex) + static string GetFileLockedExceptionMessage (Exception ex) { // If we find a file path in the message, and the file exists, check if it's locked // en-US message is: diff --git a/external/xamarin-android-tools/tests/Microsoft.Android.Build.BaseTasks-Tests/LockCheckTests.cs b/external/xamarin-android-tools/tests/Microsoft.Android.Build.BaseTasks-Tests/LockCheckTests.cs index cc68eef99fa..eaf2806ea69 100644 --- a/external/xamarin-android-tools/tests/Microsoft.Android.Build.BaseTasks-Tests/LockCheckTests.cs +++ b/external/xamarin-android-tools/tests/Microsoft.Android.Build.BaseTasks-Tests/LockCheckTests.cs @@ -40,13 +40,13 @@ public void TearDown () class MyTask : AndroidTask { - public string Path { get; set; } - public override string TaskPrefix => "MYT"; + public Action Action { get; set; } + public override bool RunTask () { - using var stream = File.Create (Path); + Action (); return false; } } @@ -70,14 +70,25 @@ public void LockCheck_FileLocked () } [Test] - public void LockCheck_AndroidTask () + public void LockCheck_AndroidTask_FileCreate () => + LockCheck_AndroidTask (() => File.Create (tempFile)); + + [Test] + public void LockCheck_AndroidTask_FileDelete () => + LockCheck_AndroidTask (() => File.Delete (tempFile)); + + [Test] + public void LockCheck_AndroidTask_UnauthorizedAccessException () => + LockCheck_AndroidTask (() => throw new UnauthorizedAccessException ($"Access to the path '{tempFile}' is denied.")); + + void LockCheck_AndroidTask (Action action) { if (!OperatingSystem.IsWindows ()) Assert.Ignore ("Test only valid on Windows"); var task = new MyTask { BuildEngine = engine, - Path = tempFile, + Action = action, }; task.Execute (); From 5fd95105e3ca032eec829e7ee928d78d07a9a3ad Mon Sep 17 00:00:00 2001 From: Dean Ellis Date: Tue, 22 Oct 2024 16:57:55 +0100 Subject: [PATCH 210/308] [Microsoft.Android.Build.BaseTasks] retry when copying files (#245) Context: https://github.com/dotnet/android/issues/9133 Context: https://learn.microsoft.com/visualstudio/msbuild/copy-task?view=vs-2022 We sometimes get collisions between the Design-Time-Build (or AntiVirus) and our main build. This can result in errors such as: Error (active) XALNS7019 System.UnauthorizedAccessException: Access to the path 'D:\Projects\MauiApp2\obj\Debug\net9.0-android\android\assets\armeabi-v7a\MauiApp2.dll' is denied. at System.IO.__Error.WinIOError(Int32 errorCode, String maybeFullPath) at System.IO.File.InternalDelete(String path, Boolean checkHost) at Microsoft.Android.Build.Tasks.Files.CopyIfChanged(String source, String destination) in /Users/runner/work/1/s/xamarin-android/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Files.cs:line 125 at Xamarin.Android.Tasks.MonoAndroidHelper.CopyAssemblyAndSymbols(String source, String destination) in /Users/runner/work/1/s/xamarin-android/src/Xamarin.Android.Build.Tasks/Utilities/MonoAndroidHelper.cs:line 344 at Xamarin.Android.Tasks.LinkAssembliesNoShrink.CopyIfChanged(ITaskItem source, ITaskItem destination) in /Users/runner/work/1/s/xamarin-android/src/Xamarin.Android.Build.Tasks/Tasks/LinkAssembliesNoShrink.cs:line 161 at Xamarin.Android.Tasks.LinkAssembliesNoShrink.RunTask() in /Users/runner/work/1/s/xamarin-android/src/Xamarin.Android.Build.Tasks/Tasks/LinkAssembliesNoShrink.cs:line 76 at Microsoft.Android.Build.Tasks.AndroidTask.Execute() in /Users/runner/work/1/s/xamarin-android/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/AndroidTask.cs:line 25 MauiApp2 (net9.0-android) C:\Program Files\dotnet\packs\Microsoft.Android.Sdk.Windows\34.99.0-preview.6.340\tools\Xamarin.Android.Common.targets 1407 If we look at the [MSBuild `` task][0] we see that it has a retry system in the cases of `UnauthorizedAccessException` or `IOException` when the code is `ACCESS_DENIED` or `ERROR_SHARING_VIOLATION`. The `` task also has public `Retries` and `RetryDelayMilliseconds` properties to control behavior. Duplicate that kind of logic into our `Files.Copy*IfChanged()` helper methods. This should give our builds a bit more resiliency to these kinds of issues. Instead of adding new `Files.Copy*IfChanged()` method overloads which accept "retries" and "retryDelay" parameters, we instead use environment variables to allow overriding these values: * `DOTNET_ANDROID_FILE_WRITE_RETRY_ATTEMPTS`: The number of times to try to retry a copy operation; corresponds to the `Copy.Retries` MSBuild task property. The default value is 10. * `DOTNET_ANDROID_FILE_WRITE_RETRY_DELAY_MS`: The amount of time, in milliseconds, to delay between attempted copies; corresponds to the `Copy.RetryDelayMilliseconds` MSBuild task property. The default value is 1000 ms. [0]: https://github.com/dotnet/msbuild/blob/main/src/Tasks/Copy.cs#L897 --- .../Files.cs | 99 +++++++++++++++++++ .../Microsoft.Android.Build.BaseTasks.csproj | 1 + .../FilesTests.cs | 30 ++++++ 3 files changed, 130 insertions(+) diff --git a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Files.cs b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Files.cs index 9172847bb11..77b6e313311 100644 --- a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Files.cs +++ b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Files.cs @@ -9,11 +9,25 @@ using System.Text; using Xamarin.Tools.Zip; using Microsoft.Build.Utilities; +using System.Threading; +using System.Reflection.Metadata; +using System.Runtime.InteropServices; +using System.Collections; namespace Microsoft.Android.Build.Tasks { public static class Files { + const int ERROR_ACCESS_DENIED = -2147024891; + const int ERROR_SHARING_VIOLATION = -2147024864; + + const int DEFAULT_FILE_WRITE_RETRY_ATTEMPTS = 10; + + const int DEFAULT_FILE_WRITE_RETRY_DELAY_MS = 1000; + + static int fileWriteRetry = -1; + static int fileWriteRetryDelay = -1; + /// /// Windows has a MAX_PATH limit of 260 characters /// See: https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file#maximum-path-length-limitation @@ -28,6 +42,37 @@ public static class Files public static readonly Encoding UTF8withoutBOM = new UTF8Encoding (encoderShouldEmitUTF8Identifier: false); readonly static byte[] Utf8Preamble = Encoding.UTF8.GetPreamble (); + /// + /// Checks for the environment variable DOTNET_ANDROID_FILE_WRITE_RETRY_ATTEMPTS to + /// see if a custom value for the number of times to retry writing a file has been + /// set. + /// + /// The value of DOTNET_ANDROID_FILE_WRITE_RETRY_ATTEMPTS or the default of DEFAULT_FILE_WRITE_RETRY_ATTEMPTS + public static int GetFileWriteRetryAttempts () + { + if (fileWriteRetry == -1) { + var retryVariable = Environment.GetEnvironmentVariable ("DOTNET_ANDROID_FILE_WRITE_RETRY_ATTEMPTS"); + if (string.IsNullOrEmpty (retryVariable) || !int.TryParse (retryVariable, out fileWriteRetry)) + fileWriteRetry = DEFAULT_FILE_WRITE_RETRY_ATTEMPTS; + } + return fileWriteRetry; + } + + /// + /// Checks for the environment variable DOTNET_ANDROID_FILE_WRITE_RETRY_DELAY_MS to + /// see if a custom value for the delay between trying to write a file has been + /// set. + /// + /// The value of DOTNET_ANDROID_FILE_WRITE_RETRY_DELAY_MS or the default of DEFAULT_FILE_WRITE_RETRY_DELAY_MS + public static int GetFileWriteRetryDelay () + { + if (fileWriteRetryDelay == -1) { + var delayVariable = Environment.GetEnvironmentVariable ("DOTNET_ANDROID_FILE_WRITE_RETRY_DELAY_MS"); + if (string.IsNullOrEmpty (delayVariable) || !int.TryParse (delayVariable, out fileWriteRetryDelay)) + fileWriteRetryDelay = DEFAULT_FILE_WRITE_RETRY_DELAY_MS; + } + return fileWriteRetryDelay; + } /// /// Converts a full path to a \\?\ prefixed path that works on all Windows machines when over 260 characters /// NOTE: requires a *full path*, use sparingly @@ -111,6 +156,33 @@ public static bool ArchiveZip (string target, Action archiver) } public static bool CopyIfChanged (string source, string destination) + { + int retryCount = 0; + int attempts = GetFileWriteRetryAttempts (); + int delay = GetFileWriteRetryDelay (); + while (retryCount <= attempts) { + try { + return CopyIfChangedOnce (source, destination); + } catch (Exception e) { + switch (e) { + case UnauthorizedAccessException: + case IOException: + int code = Marshal.GetHRForException (e); + if ((code != ERROR_ACCESS_DENIED && code != ERROR_SHARING_VIOLATION) || retryCount == attempts) { + throw; + }; + break; + default: + throw; + } + } + retryCount++; + Thread.Sleep (delay); + } + return false; + } + + public static bool CopyIfChangedOnce (string source, string destination) { if (HasFileChanged (source, destination)) { var directory = Path.GetDirectoryName (destination); @@ -157,6 +229,33 @@ public static bool CopyIfBytesChanged (byte[] bytes, string destination) } public static bool CopyIfStreamChanged (Stream stream, string destination) + { + int retryCount = 0; + int attempts = GetFileWriteRetryAttempts (); + int delay = GetFileWriteRetryDelay (); + while (retryCount <= attempts) { + try { + return CopyIfStreamChangedOnce (stream, destination); + } catch (Exception e) { + switch (e) { + case UnauthorizedAccessException: + case IOException: + int code = Marshal.GetHRForException (e); + if ((code != ERROR_ACCESS_DENIED && code != ERROR_SHARING_VIOLATION) || retryCount >= attempts) { + throw; + }; + break; + default: + throw; + } + } + retryCount++; + Thread.Sleep (delay); + } + return false; + } + + public static bool CopyIfStreamChangedOnce (Stream stream, string destination) { if (HasStreamChanged (stream, destination)) { var directory = Path.GetDirectoryName (destination); diff --git a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Microsoft.Android.Build.BaseTasks.csproj b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Microsoft.Android.Build.BaseTasks.csproj index f3602ce5e84..e60a7b46b69 100644 --- a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Microsoft.Android.Build.BaseTasks.csproj +++ b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Microsoft.Android.Build.BaseTasks.csproj @@ -9,6 +9,7 @@ true ..\..\product.snk $(VendorPrefix)Microsoft.Android.Build.BaseTasks$(VendorSuffix) + 12.0 diff --git a/external/xamarin-android-tools/tests/Microsoft.Android.Build.BaseTasks-Tests/FilesTests.cs b/external/xamarin-android-tools/tests/Microsoft.Android.Build.BaseTasks-Tests/FilesTests.cs index e1953cb5a54..db02d2f7ef5 100644 --- a/external/xamarin-android-tools/tests/Microsoft.Android.Build.BaseTasks-Tests/FilesTests.cs +++ b/external/xamarin-android-tools/tests/Microsoft.Android.Build.BaseTasks-Tests/FilesTests.cs @@ -6,6 +6,8 @@ using System.IO; using System.Runtime.InteropServices; using System.Text; +using System.Threading; +using System.Threading.Tasks; using Xamarin.Tools.Zip; using Microsoft.Android.Build.Tasks; @@ -436,6 +438,34 @@ public void CopyIfStreamChanged_CasingChange () } } + [Test] + public async Task CopyIfChanged_LockedFile () + { + var dest = NewFile (contents: "foo", fileName: "foo_locked"); + var src = NewFile (contents: "foo0", fileName: "foo"); + using (var file = File.OpenWrite (dest)) { + Assert.Throws (() => Files.CopyIfChanged (src, dest)); + } + src = NewFile (contents: "foo1", fileName: "foo"); + Assert.IsTrue (Files.CopyIfChanged (src, dest)); + src = NewFile (contents: "foo2", fileName: "foo"); + dest = NewFile (contents: "foo", fileName: "foo_locked2"); + var ev = new ManualResetEvent (false); + var task = Task.Run (async () => { + var file = File.Open (dest, FileMode.OpenOrCreate, FileAccess.Write, FileShare.Read); + try { + ev.Set (); + await Task.Delay (2500); + } finally { + file.Close(); + file.Dispose (); + } + }); + ev.WaitOne (); + Assert.IsTrue (Files.CopyIfChanged (src, dest)); + await task; + } + [Test] public void ExtractAll () { From 040d9d71ccf0a35b150304294bf037d17eaa1bda Mon Sep 17 00:00:00 2001 From: Dean Ellis Date: Tue, 3 Dec 2024 16:38:21 +0000 Subject: [PATCH 211/308] Add support to discovering activity-alias activity elements (#247) Context https://developer.android.com/guide/topics/manifest/activity-alias-element. Fixes https://dev.azure.com/devdiv/DevDiv/_workitems/edit/2305723. So activity-alias allows users to change some settings before calling an activity. The idea is that your activity is no longer the main launcher, you still have that entry, but the activity-alias' become the main launchers. Here is a sample AndroidManifest.xml ```xml ``` The MainActivity.cs then becomes ```csharp [Activity(Theme = "@style/Maui.SplashTheme", LaunchMode = LaunchMode.SingleTop, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation | ConfigChanges.UiMode | ConfigChanges.ScreenLayout | ConfigChanges.SmallestScreenSize | ConfigChanges.Density)] [Register("com.companyname.mauiappwithmultipleicons.MainActivity")] public class MainActivity : MauiAppCompatActivity ``` Note that we do not use the MainLauncher property on the ActivityAttribute. --- .../Xamarin.Android.Tools.AndroidSdk/AndroidAppManifest.cs | 4 +++- .../AndroidAppManifestTests.cs | 3 ++- .../Resources/manifest-simplewidget.xml | 6 ++++++ .../Xamarin.Android.Tools.AndroidSdk-Tests.csproj | 2 +- 4 files changed, 12 insertions(+), 3 deletions(-) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidAppManifest.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidAppManifest.cs index 5955292b60a..87ed37a34eb 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidAppManifest.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidAppManifest.cs @@ -329,7 +329,9 @@ void RemoveAndroidPermissions (IEnumerable permissions) IEnumerable GetLaunchableActivities () { - foreach (var activity in application.Elements ("activity")) { + var activities = application.Elements ("activity"); + var aliases = application.Elements ("activity-alias"); + foreach (var activity in activities.Union (aliases)) { foreach (var filter in activity.Elements ("intent-filter")) { foreach (var category in filter.Elements ("category")) if (category != null && (string?)category.Attribute (aName) == "android.intent.category.LAUNCHER") diff --git a/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/AndroidAppManifestTests.cs b/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/AndroidAppManifestTests.cs index 5c58199aa52..92e81dbe0b6 100644 --- a/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/AndroidAppManifestTests.cs +++ b/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/AndroidAppManifestTests.cs @@ -51,8 +51,9 @@ public void GetLaunchableActivityNames () var versions = new AndroidVersions (Array.Empty()); var manifest = AndroidAppManifest.Load (GetTestAppManifest (), versions); var launchers = manifest.GetLaunchableActivityNames ().ToList (); - Assert.AreEqual (1, launchers.Count); + Assert.AreEqual (2, launchers.Count); Assert.AreEqual (".HasMultipleIntentFilters", launchers [0]); + Assert.AreEqual (".ActivityAlias", launchers [1]); } [Test] diff --git a/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/Resources/manifest-simplewidget.xml b/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/Resources/manifest-simplewidget.xml index c834c6cfa2c..b3e146a099e 100644 --- a/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/Resources/manifest-simplewidget.xml +++ b/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/Resources/manifest-simplewidget.xml @@ -33,6 +33,12 @@ + + + + + + diff --git a/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/Xamarin.Android.Tools.AndroidSdk-Tests.csproj b/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/Xamarin.Android.Tools.AndroidSdk-Tests.csproj index 19c4033d198..27b85243b50 100644 --- a/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/Xamarin.Android.Tools.AndroidSdk-Tests.csproj +++ b/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/Xamarin.Android.Tools.AndroidSdk-Tests.csproj @@ -4,7 +4,7 @@ - net6.0 + net8.0 true ..\..\product.snk false From 2044220b62f7ea142b05062695540aa31b2e0de2 Mon Sep 17 00:00:00 2001 From: Dean Ellis Date: Tue, 14 Jan 2025 16:33:41 +0000 Subject: [PATCH 212/308] [Xamarin.Android.Tools.AndroidSdk] Remove hardcoded Path (#248) Context: https://dev.azure.com/devdiv/DevDiv/_workitems/edit/2341994 Context: https://github.com/xamarin/androidtools/commit/94e46bcc818b287a659ff8cd0d4c0ab83c80255b Remove the hardcoded path `C:\android-sdk-windows` from the list of backup paths to check when scanning for the Android SDK. This path was first added in xamarin/androidtools@94e46bcc, with no explanation for *why* that was the backup path. Perhaps that was the convention at the time? --- .../Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkWindows.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkWindows.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkWindows.cs index 98f8b7e47ab..4c075f3a833 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkWindows.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkWindows.cs @@ -106,7 +106,6 @@ protected override IEnumerable GetAllAvailableAndroidSdks () : Path.Combine (Environment.GetFolderPath (Environment.SpecialFolder.ProgramFiles), "Android", "android-sdk"), Path.Combine (Environment.GetFolderPath (Environment.SpecialFolder.LocalApplicationData), "Android", "android-sdk"), Path.Combine (Environment.GetFolderPath (Environment.SpecialFolder.CommonApplicationData), "Android", "android-sdk"), - @"C:\android-sdk-windows" }; foreach (var basePath in paths) if (Directory.Exists (basePath)) From b41801e987baeb3c8a00f78c8de59b4c827ccc76 Mon Sep 17 00:00:00 2001 From: Peter Collins Date: Thu, 20 Feb 2025 11:56:37 -0500 Subject: [PATCH 213/308] [ci] Move PR build to dnceng public (#249) New pipeline: https://dev.azure.com/dnceng-public/public/_build?definitionId=307 --- .../azure-pipelines.yaml | 79 +------------------ ...arin.Android.Tools.AndroidSdk-Tests.csproj | 1 + 2 files changed, 3 insertions(+), 77 deletions(-) diff --git a/external/xamarin-android-tools/azure-pipelines.yaml b/external/xamarin-android-tools/azure-pipelines.yaml index ce23fb5bfd1..b8d22cf9b12 100644 --- a/external/xamarin-android-tools/azure-pipelines.yaml +++ b/external/xamarin-android-tools/azure-pipelines.yaml @@ -8,14 +8,10 @@ pr: - main - release/* -parameters: -- name: ApiScanSourceBranch - default: refs/heads/main - # Global variables variables: - name: DotNetCoreVersion - value: 6.0.x + value: 9.0.x jobs: - job: build @@ -24,10 +20,9 @@ jobs: strategy: matrix: macOS: - vmImage: macOS-13 + vmImage: macOS-15 windows: vmImage: windows-2022 - Codeql.Enabled: true pool: vmImage: $(vmImage) workspace: @@ -41,11 +36,6 @@ jobs: inputs: version: $(DotNetCoreVersion) - - task: UseDotNet@2 - displayName: Use .NET Core 8.0.x - inputs: - version: 8.0.x - - task: DotNetCoreCLI@2 displayName: Build solution Xamarin.Android.Tools.sln inputs: @@ -87,68 +77,3 @@ jobs: path: $(Build.ArtifactStagingDirectory) artifactName: Artifacts - $(System.JobName) condition: always() - -- job: api_scan - displayName: API Scan - dependsOn: build - condition: false - #condition: and(eq(dependencies.build.result, 'Succeeded'), eq(variables['Build.SourceBranch'], '${{ parameters.ApiScanSourceBranch }}')) - pool: - name: Azure Pipelines - vmImage: windows-2022 - timeoutInMinutes: 480 - workspace: - clean: all - steps: - - task: DownloadPipelineArtifact@2 - displayName: Download build artifacts - inputs: - artifactName: Output - windows - downloadPath: $(Build.SourcesDirectory) - - - task: CopyFiles@2 - displayName: Collect Files for APIScan - inputs: - Contents: | - $(Build.SourcesDirectory)\**\?(*.dll|*.exe|*.pdb) - !$(Build.SourcesDirectory)\**\ls-jdks.* - TargetFolder: $(Build.StagingDirectory)\apiscan - OverWrite: true - flattenFolders: true - - - powershell: Get-ChildItem -Path "$(Build.StagingDirectory)\apiscan" -Recurse - displayName: List Files for APIScan - - - task: APIScan@2 - displayName: Run APIScan - inputs: - softwareFolder: $(Build.StagingDirectory)\apiscan - symbolsFolder: 'SRV*http://symweb;$(Build.StagingDirectory)\apiscan' - softwareName: $(ApiScanName) - softwareVersionNum: $(Build.SourceBranchName)-$(Build.SourceVersion)$(System.JobAttempt) - isLargeApp: true - toolVersion: Latest - env: - AzureServicesAuthConnectionString: runAs=App;AppId=$(ApiScanClientId);TenantId=$(ApiScanTenant);AppKey=$(ApiScanSecret) - - - task: SdtReport@2 - displayName: Guardian Export - Security Report - inputs: - GdnExportAllTools: false - GdnExportGdnToolApiScan: true - GdnExportOutputSuppressionFile: source.gdnsuppress - - - task: PublishSecurityAnalysisLogs@3 - displayName: Publish Guardian Artifacts - inputs: - ArtifactName: APIScan Logs - ArtifactType: Container - AllTools: false - APIScan: true - ToolLogsNotFoundAction: Warning - - - task: PostAnalysis@2 - displayName: Fail Build on Guardian Issues - inputs: - GdnBreakAllTools: false - GdnBreakGdnToolApiScan: true diff --git a/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/Xamarin.Android.Tools.AndroidSdk-Tests.csproj b/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/Xamarin.Android.Tools.AndroidSdk-Tests.csproj index 27b85243b50..3f7bf1dfcb1 100644 --- a/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/Xamarin.Android.Tools.AndroidSdk-Tests.csproj +++ b/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/Xamarin.Android.Tools.AndroidSdk-Tests.csproj @@ -10,6 +10,7 @@ false $(TestOutputFullPath) false + Major From cbaafb2114af20b90b890bc315db2d405870b70a Mon Sep 17 00:00:00 2001 From: Jonathan Pryor Date: Fri, 21 Mar 2025 08:24:06 -0400 Subject: [PATCH 214/308] [Xamarin.Android.Tools.AndroidSdk] OS-specific dirs are OS-specific (#250) OS-specific JDK locators should only be attempted on the OS that they're intended for: macOS-specific JDK probe mechanisms such as `/usr/libexec/java_home -X` should only be used on macOS, and Linux-specific JDK probe mechanisms such as looking at `/usr/lib/jvm/*` should only be done on Linux. --- .../src/Xamarin.Android.Tools.AndroidSdk/JdkInfo.cs | 12 ++++++++++++ .../Jdks/MicrosoftDistJdkLocations.cs | 4 ++++ .../src/Xamarin.Android.Tools.AndroidSdk/OS.cs | 4 +++- 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/JdkInfo.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/JdkInfo.cs index 644ef90dc91..4599f2cdbaa 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/JdkInfo.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/JdkInfo.cs @@ -383,6 +383,10 @@ static IEnumerable GetLibexecJdks (Action logger) static IEnumerable GetLibexecJdkPaths (Action logger) { + if (!OS.IsMac) { + yield break; + } + var java_home = Path.GetFullPath ("/usr/libexec/java_home"); if (!File.Exists (java_home)) { yield break; @@ -429,6 +433,10 @@ static IEnumerable GetJavaAlternativesJdks (Action static IEnumerable GetJavaAlternativesJdkPaths () { + if (!OS.IsLinux) { + return Enumerable.Empty (); + } + var alternatives = Path.GetFullPath ("/usr/sbin/update-java-alternatives"); if (!File.Exists (alternatives)) return Enumerable.Empty (); @@ -464,6 +472,10 @@ static IEnumerable GetLibJvmJdks (Action logger) static IEnumerable GetLibJvmJdkPaths () { + if (!OS.IsLinux) { + yield break; + } + var jvm = "/usr/lib/jvm"; if (!Directory.Exists (jvm)) yield break; diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Jdks/MicrosoftDistJdkLocations.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Jdks/MicrosoftDistJdkLocations.cs index 00827c90ed2..5e9ae60e2ed 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Jdks/MicrosoftDistJdkLocations.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Jdks/MicrosoftDistJdkLocations.cs @@ -18,6 +18,10 @@ internal static IEnumerable GetMicrosoftDistJdks (Action GetMacOSMicrosoftDistJdkPaths () { + if (!OS.IsMac) { + return Array.Empty (); + } + var jdks = AppDomain.CurrentDomain.GetData ($"GetMacOSMicrosoftJdkPaths jdks override! {typeof (JdkInfo).AssemblyQualifiedName}") ?.ToString (); if (jdks == null) { diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/OS.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/OS.cs index b2247489753..1d139613421 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/OS.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/OS.cs @@ -11,6 +11,7 @@ public class OS { public readonly static bool IsWindows; public readonly static bool IsMac; + public readonly static bool IsLinux; internal readonly static string? ProgramFilesX86; @@ -20,6 +21,7 @@ static OS () { IsWindows = Path.DirectorySeparatorChar == '\\'; IsMac = !IsWindows && IsRunningOnMac (); + IsLinux = !IsWindows && !IsMac; if (IsWindows) { ProgramFilesX86 = GetProgramFilesX86 (); @@ -29,7 +31,7 @@ static OS () NativeLibraryFormat = "{0}.dll"; if (IsMac) NativeLibraryFormat = "lib{0}.dylib"; - if (!IsWindows && !IsMac) + if (IsLinux) NativeLibraryFormat = "lib{0}.so"; } From 88eb6cc09dff23bfb5ff62fb4c027ab5b4534dbd Mon Sep 17 00:00:00 2001 From: Peter Collins Date: Tue, 22 Apr 2025 10:33:38 -0700 Subject: [PATCH 215/308] [build] Update to net9.0 and netstandard2.0 (#251) Introduces a `$(DotNetTargetFramework)` property to control the target framework version used for exe projects, and sets it to the latest stable .NET version (`net9.0`). Library and build task projects have been updated to only target `netstandard2.0`, which is compatible with both .NET Framework and .NET Core. --- .../xamarin-android-tools/Directory.Build.props | 13 ++----------- external/xamarin-android-tools/azure-pipelines.yaml | 2 +- .../Microsoft.Android.Build.BaseTasks.csproj | 2 +- .../Xamarin.Android.Tools.AndroidSdk.csproj | 6 +----- .../Microsoft.Android.Build.BaseTasks-Tests.csproj | 5 +---- .../Xamarin.Android.Tools.AndroidSdk-Tests.csproj | 5 +---- .../tools/ls-jdks/ls-jdks.csproj | 2 +- 7 files changed, 8 insertions(+), 27 deletions(-) diff --git a/external/xamarin-android-tools/Directory.Build.props b/external/xamarin-android-tools/Directory.Build.props index 2a36ef78454..7676eaaac7b 100644 --- a/external/xamarin-android-tools/Directory.Build.props +++ b/external/xamarin-android-tools/Directory.Build.props @@ -5,6 +5,8 @@ Debug true + 9.0 + net$(DotNetTargetFrameworkVersion) obj\ - - - True - - - $(BaseIntermediateOutputPath)\$(Configuration)-$(TargetFramework.ToLowerInvariant()) - $(MSBuildThisFileDirectory)bin\Build$(Configuration)\$(TargetFramework.ToLowerInvariant())\ - $(MSBuildThisFileDirectory)bin\$(Configuration)\$(TargetFramework.ToLowerInvariant())\ - $(MSBuildThisFileDirectory)bin\Test$(Configuration)-$(TargetFramework.ToLowerInvariant())\ - - $(BaseIntermediateOutputPath)\$(Configuration) $(MSBuildThisFileDirectory)bin\Build$(Configuration)\ $(MSBuildThisFileDirectory)bin\$(Configuration)\ diff --git a/external/xamarin-android-tools/azure-pipelines.yaml b/external/xamarin-android-tools/azure-pipelines.yaml index b8d22cf9b12..d2e73d0f873 100644 --- a/external/xamarin-android-tools/azure-pipelines.yaml +++ b/external/xamarin-android-tools/azure-pipelines.yaml @@ -46,7 +46,7 @@ jobs: displayName: Run Tests inputs: command: test - projects: bin/TestDebug-net*/**/*-Tests.dll + projects: bin/TestDebug/**/*-Tests.dll - powershell: | $hashOfLastVersionChange = & "git" "log" "--follow" "-1" "--pretty=%H" "nuget.version" diff --git a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Microsoft.Android.Build.BaseTasks.csproj b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Microsoft.Android.Build.BaseTasks.csproj index e60a7b46b69..74aaf283435 100644 --- a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Microsoft.Android.Build.BaseTasks.csproj +++ b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Microsoft.Android.Build.BaseTasks.csproj @@ -3,7 +3,7 @@ - netstandard2.0;net6.0 + netstandard2.0 Microsoft.Android.Build.Tasks true true diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj index ffaf31cd40f..c89a3df7762 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj @@ -1,7 +1,7 @@ - netstandard2.0;net6.0 + netstandard2.0 8.0 enable INTERNAL_NULLABLE_ATTRIBUTES @@ -17,10 +17,6 @@ $(VendorPrefix)Xamarin.Android.Tools.AndroidSdk$(VendorSuffix) - - - - $(ToolOutputFullPath) diff --git a/external/xamarin-android-tools/tests/Microsoft.Android.Build.BaseTasks-Tests/Microsoft.Android.Build.BaseTasks-Tests.csproj b/external/xamarin-android-tools/tests/Microsoft.Android.Build.BaseTasks-Tests/Microsoft.Android.Build.BaseTasks-Tests.csproj index 99bdc267828..fa8b2e1d1dd 100644 --- a/external/xamarin-android-tools/tests/Microsoft.Android.Build.BaseTasks-Tests/Microsoft.Android.Build.BaseTasks-Tests.csproj +++ b/external/xamarin-android-tools/tests/Microsoft.Android.Build.BaseTasks-Tests/Microsoft.Android.Build.BaseTasks-Tests.csproj @@ -1,10 +1,7 @@ - - net8.0 + $(DotNetTargetFramework) Microsoft.Android.Build.BaseTasks.Tests false $(TestOutputFullPath) diff --git a/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/Xamarin.Android.Tools.AndroidSdk-Tests.csproj b/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/Xamarin.Android.Tools.AndroidSdk-Tests.csproj index 3f7bf1dfcb1..fb5973c374a 100644 --- a/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/Xamarin.Android.Tools.AndroidSdk-Tests.csproj +++ b/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/Xamarin.Android.Tools.AndroidSdk-Tests.csproj @@ -1,10 +1,7 @@ - - net8.0 + $(DotNetTargetFramework) true ..\..\product.snk false diff --git a/external/xamarin-android-tools/tools/ls-jdks/ls-jdks.csproj b/external/xamarin-android-tools/tools/ls-jdks/ls-jdks.csproj index af1eee9db06..b87bfa7a480 100644 --- a/external/xamarin-android-tools/tools/ls-jdks/ls-jdks.csproj +++ b/external/xamarin-android-tools/tools/ls-jdks/ls-jdks.csproj @@ -2,7 +2,7 @@ Exe - net472;net6.0 + $(DotNetTargetFramework) Xamarin.Android.Tools false $(ToolOutputFullPath) From 7ce6e0fe4e7ae476b12dda9be0ce1e904434f5f0 Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Wed, 4 Jun 2025 14:32:23 -0500 Subject: [PATCH 216/308] [msbuild] remove `Xamarin.Android.Tools.Versions.props` (#253) Context: https://github.com/dotnet/android/commit/b1c2b3325395dfa0547d2e161066b5509d3529dc Context: https://dev.azure.com/devdiv/DevDiv/_git/android-platform-support?path=/src/Xamarin.Installer.Build.Tasks/Xamarin.Installer.Common.props This file was moved to a private repo that is now shared with IDEs. Remove the file here to reduce confusion. It also is out of date, so we don't want anything using it. --- .../Xamarin.Android.Tools.AndroidSdk.csproj | 6 ----- .../Xamarin.Android.Tools.Versions.props | 23 ------------------- 2 files changed, 29 deletions(-) delete mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.Versions.props diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj index c89a3df7762..56186db14e7 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj @@ -54,10 +54,4 @@ Resources.Designer.cs - - - - PreserveNewest - - diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.Versions.props b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.Versions.props deleted file mode 100644 index 7d66259b2e4..00000000000 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.Versions.props +++ /dev/null @@ -1,23 +0,0 @@ - - - - - 34.0.0 - 11.0 - 34.0.5 - - android-34 - 26.1.10909125 - 17.0.8.1 - - - 26.1.1 - - From 0eed070700e1032f4799b89636151cf88265f252 Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Mon, 9 Jun 2025 11:12:56 -0500 Subject: [PATCH 217/308] [Xamarin.Android.Tools.AndroidSdk] Add API-36 to `KnownVersions` (#254) Context: https://devdiv.visualstudio.com/DevDiv/_workitems/edit/2234904 Context: https://github.com/dotnet/android/commit/2dd4f7473fa2d39be2168d178efb03ea33affffe It appears that IDEs use this list to decide what to show in API level dropdowns. In cbc3531, we simply added to this list. `Baklava` appears to be the only other name used for API 36. --- .../src/Xamarin.Android.Tools.AndroidSdk/AndroidVersions.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidVersions.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidVersions.cs index 78c441ce47d..e44f655bbcd 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidVersions.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidVersions.cs @@ -197,6 +197,9 @@ static bool MatchesId (AndroidVersion version, string id) new AndroidVersion (35, "15.0") { AlternateIds = new[]{ "V", "VanillaIceCream", "Vanilla Ice Cream" }, }, + new AndroidVersion (36, "16.0") { + AlternateIds = new[]{ "Baklava" }, + }, }; } } From 8496a7eedaa8dc8c4f4ccc9395c94c5841311e3b Mon Sep 17 00:00:00 2001 From: Emanuel Fernandez Dell'Oca <3286258+emaf@users.noreply.github.com> Date: Fri, 27 Jun 2025 17:38:39 -0400 Subject: [PATCH 218/308] Return empty properties when `java` fails to run (#255) If the Java executable we are trying to run fails to be be executed (i.e., locked file, broken symlink, or any raeson actually) we should just return an empty dictionary. That way when running `GetPathEnvironmentJdkPaths`, the invalid/not available executable is just ignored. Call stack from: * https://developercommunity.visualstudio.com/t/visual-studio-does-not-see-detect-the-co/10888076 System.ComponentModel.Win32Exception (0x80004005): The system cannot find the path specified at System.Diagnostics.Process.StartWithCreateProcess(ProcessStartInfo startInfo) at Xamarin.Android.Tools.ProcessUtils.Exec(ProcessStartInfo processStartInfo, DataReceivedEventHandler output, Boolean includeStderr) at Xamarin.Android.Tools.JdkInfo.GetJavaProperties(Action`2 logger, String java) at Xamarin.Android.Tools.JdkInfo.d__61.MoveNext() The message `The system cannot find the path specified` is a bit misleading because we are already validating the file exist before calling `GetJavaProperties`, and this can only occurr if we found a broken symlink. --- .../src/Xamarin.Android.Tools.AndroidSdk/JdkInfo.cs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/JdkInfo.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/JdkInfo.cs index 4599f2cdbaa..4969874b2c9 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/JdkInfo.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/JdkInfo.cs @@ -274,7 +274,8 @@ static Dictionary> GetJavaProperties (Action { + try { + ProcessUtils.Exec (javaProps, (o, e) => { const string ContinuedValuePrefix = " "; const string NewValuePrefix = " "; const string NameValueDelim = " = "; @@ -307,7 +308,14 @@ static Dictionary> GetJavaProperties (Action ()); values.Add (value); } - }); + }); + } + catch (Exception e) { + logger (TraceLevel.Error, $"Error retrieving Java properties by running `{javaProps.FileName} {javaProps.Arguments}`: {e.Message}"); + logger (TraceLevel.Verbose, e.ToString ()); + return props; + } + if (!foundPS) { logger (TraceLevel.Warning, $"No Java properties found; did not find `{PropertySettings}` in `{java} -XshowSettings:properties -version` output: ```{output.ToString ()}```"); } From 75189d6feecf482a935e6562833e53f8797e418e Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Wed, 16 Jul 2025 16:28:07 -0500 Subject: [PATCH 219/308] [build] add net9.0 & timmer/aot compatibility (#256) This library is already trim compatible by pure luck: not using any trimmer unsafe APIs. Let's add the `net9.0` target framework and opt into the analyzers. We can also skip `NullableAttributes.cs`, as this file is only needed for `netstandard2.0`. --- .../Xamarin.Android.Tools.AndroidSdk.csproj | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj index 56186db14e7..be8f6c45382 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj @@ -1,7 +1,7 @@ - netstandard2.0 + netstandard2.0;$(DotNetTargetFramework) 8.0 enable INTERNAL_NULLABLE_ATTRIBUTES @@ -21,6 +21,15 @@ $(ToolOutputFullPath) + + true + true + + + + + + all From 9441b41cfeb2a508f9c1855dd26525b7e62f70e6 Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Thu, 17 Jul 2025 09:22:39 -0500 Subject: [PATCH 220/308] [build] add way to opt out of multi-targeting (#257) In 75189d6f, we introduce `net9.0`, so we could get trim and aot analyzers. This broke the build in dotnet/android, because it passes in `$(DotNetTargetFramework)` as `net10.0`: /Users/builder/.dotnet/sdk/9.0.302/Sdks/Microsoft.NET.Sdk/targets/Microsoft.NET.TargetFrameworkInference.targets(166,5): error NETSDK1045: The current .NET SDK does not support targeting .NET 10.0. Either target .NET 9.0 or lower, or use a version of the .NET SDK that supports .NET 10.0. Download the .NET SDK from https://aka.ms/dotnet/download In the dotnet/android repo, we only use the `netstandard2.0` version of this library for MSBuild tasks that might run on .NET framework. Let's introduce `$(AndroidToolsDisableMultiTargeting)` that can be set in dotnet/android to resolve this. --- .../Xamarin.Android.Tools.AndroidSdk.csproj | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj index be8f6c45382..5fb3608b2b5 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj @@ -1,7 +1,8 @@ - netstandard2.0;$(DotNetTargetFramework) + netstandard2.0 + $(TargetFrameworks);$(DotNetTargetFramework) 8.0 enable INTERNAL_NULLABLE_ATTRIBUTES From 5f8edc0ba9464f6f2fe182bc43b9eda31f1b0770 Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Tue, 22 Jul 2025 14:19:37 -0500 Subject: [PATCH 221/308] [build] set `$(PackageVersion)` in `SetVersion` target (#258) To be able to distribute things in this repo as a NuGet package, we need to set `$(PackageVersion)` appropriately. I also made sure the `SetVersion` MSBuild target runs before `GenerateNuspec`. Otherwise, `dotnet pack` does not produce a `.nupkg` with the correct `$(PackageVersion)`. --- external/xamarin-android-tools/Directory.Build.targets | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/external/xamarin-android-tools/Directory.Build.targets b/external/xamarin-android-tools/Directory.Build.targets index 984d5852c25..7c5de4849b8 100644 --- a/external/xamarin-android-tools/Directory.Build.targets +++ b/external/xamarin-android-tools/Directory.Build.targets @@ -7,9 +7,10 @@ main - + $(GitSemVerMajor).$(GitSemVerMinor).$(GitSemVerPatch) + $(Version) $(Version); git-rev-head:$(GitCommit); git-branch:$(GitBranch) From 9d65a674287ef53772c44cb00a532760cbd03c89 Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Wed, 23 Jul 2025 08:44:37 -0500 Subject: [PATCH 222/308] [build] introduce `$(PackageVersionSuffix)` for dotnet/android (#259) This allows dotnet/android to add a `-preview.$(PackVersionCommitCount)` suffix for any `.nupkg` packages produced by this repo. --- external/xamarin-android-tools/Directory.Build.targets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/xamarin-android-tools/Directory.Build.targets b/external/xamarin-android-tools/Directory.Build.targets index 7c5de4849b8..36b61edf1ee 100644 --- a/external/xamarin-android-tools/Directory.Build.targets +++ b/external/xamarin-android-tools/Directory.Build.targets @@ -10,7 +10,7 @@ $(GitSemVerMajor).$(GitSemVerMinor).$(GitSemVerPatch) - $(Version) + $(Version)$(PackageVersionSuffix) $(Version); git-rev-head:$(GitCommit); git-branch:$(GitBranch) From 886f59f4533d03efa1f27e9959b6bbc8fe746191 Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Mon, 11 Aug 2025 09:08:14 -0500 Subject: [PATCH 223/308] [Microsoft.Android.Build.BaseTasks] update to MSBuild 17.14.8 (#260) Context: https://dev.azure.com/devdiv/DevDiv/_componentGovernance/223356/alert/11217227?typeId=22718360 --- .../MSBuildReferences.projitems | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems index b61e6c3efd3..eba90555831 100644 --- a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems +++ b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems @@ -4,7 +4,7 @@ - 17.11.4 + 17.14.8 3.3.0 7.1.0-final.1.21458.1 @@ -14,8 +14,6 @@ - - From da8bbe6e47fbb154a93145395ff875469e1ae3bf Mon Sep 17 00:00:00 2001 From: Jonathan Pryor Date: Mon, 25 Aug 2025 14:46:39 -0400 Subject: [PATCH 224/308] [Xamarin.Android.Tools.AndroidSdk] "Minor" SDK version support (#261) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Context: https://github.com/dotnet/android/pull/10438 9 months ago in [The First Developer Preview of Android 16][0]: > **Two Android API releases in 2025** > > * This preview is for the next major release of Android with a > planned launch in Q2 of 2025. This release is similar to all of > our API releases in the past, where we can have planned > behavior changes that are often tied to a targetSdkVersion. > > * … > > * We plan to have another release in Q4 of 2025 which also will > include new developer APIs. The Q2 major release will be the > only release in 2025 to include planned behavior changes that > could affect apps. The 3rd bullet point is a "25Q4 MINOR SDK RELEASE" , thus introducing the *concept* of a "minor" SDK version, with semantics: * [``][3]: > It's not possible to specify that an app either targets or > requires a minor SDK version. * [Using new APIs with major and minor releases][4]: > The new [`SDK_INT_FULL`][5] constant can be used for API checks… > > if (SDK_INT_FULL >= VERSION_CODES_FULL.[MAJOR or MINOR RELEASE]) { > // Use APIs introduced in a major or minor release > } > > You can also use the [`Build.getMinorSdkVersion()`][6] method to > get just the minor SDK version: > > minorSdkVersion = Build.getMinorSdkVersion(Build.VERSION_CODES_FULL.BAKLAVA); Update `AndroidVersion` and `AndroidVersions` to better support the concept of "minor SDK releases": * Add a new `AndroidVersion.VersionCodeFull` property, which is a `System.Version` -- not an `int` -- for which `Version.Major` matches `AndroidVersion.ApiLevel`. * Add a new internal `AndroidVersion.Ids` property, which is the= full set of "aliases" that should be checked when doing an "id" match. This simplifies `AndroidVersions` logic. `Ids` contains: `ApiLevel`, VersionCodeFull`, and `Id`. * Change `AndroidVersions.AlternateIds` into a set-only property which updates `AndroidVersion.Ids`. * Bump `$(LangVersion)`=9.0 to use target-typed `new()`. [0]: https://android-developers.googleblog.com/2024/11/the-first-developer-preview-android16.html [3]: https://developer.android.com/guide/topics/manifest/uses-sdk-element [4]: https://developer.android.com/about/versions/16/features#using-new [5]: https://developer.android.com/reference/android/os/Build.VERSION#SDK_INT_FULL [6]: https://developer.android.com/reference/android/os/Build#getMinorSdkVersion(int) --- .../AndroidVersion.cs | 35 +++++++++++-- .../AndroidVersions.cs | 14 ++++-- .../Xamarin.Android.Tools.AndroidSdk.csproj | 2 +- .../AndroidVersionTests.cs | 49 ++++++++++++++++++- .../AndroidVersionsTests.cs | 22 ++++++++- 5 files changed, 111 insertions(+), 11 deletions(-) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidVersion.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidVersion.cs index 1ae78dce2db..911f3c1790f 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidVersion.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidVersion.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.IO; using System.Xml.Linq; @@ -9,6 +10,9 @@ public class AndroidVersion // Android API Level. *Usually* corresponds to $(AndroidSdkPath)/platforms/android-$(ApiLevel)/android.jar public int ApiLevel { get; private set; } + // Android API Level; includes "minor" version bumps, e.g. Android 16 QPR2 is "36.1" while ApiLevel=36 + public Version VersionCodeFull { get; private set; } + // Android API Level ID. == ApiLevel on stable versions, will be e.g. `N` for previews: $(AndroidSdkPath)/platforms/android-N/android.jar public string Id { get; private set; } @@ -27,26 +31,42 @@ public class AndroidVersion // Is this API level stable? Should be False for non-numeric Id values. public bool Stable { get; private set; } + internal HashSet Ids { get; } = new (); + // Alternate Ids for a given API level. Allows for historical mapping, e.g. API-11 has alternate ID 'H'. - internal string[]? AlternateIds { get; set; } + internal string[]? AlternateIds { + set => Ids.UnionWith (value); + } public AndroidVersion (int apiLevel, string osVersion, string? codeName = null, string? id = null, bool stable = true) + : this (new Version (apiLevel, 0), osVersion, codeName, id, stable) + { + } + + public AndroidVersion (Version versionCodeFull, string osVersion, string? codeName = null, string? id = null, bool stable = true) { + if (versionCodeFull == null) + throw new ArgumentNullException (nameof (versionCodeFull)); if (osVersion == null) throw new ArgumentNullException (nameof (osVersion)); - ApiLevel = apiLevel; - Id = id ?? ApiLevel.ToString (); + ApiLevel = versionCodeFull.Major; + VersionCodeFull = versionCodeFull; + Id = id ?? (versionCodeFull.Minor != 0 ? versionCodeFull.ToString () : ApiLevel.ToString ()); CodeName = codeName; OSVersion = osVersion; TargetFrameworkVersion = Version.Parse (osVersion); FrameworkVersion = "v" + osVersion; Stable = stable; + + Ids.Add (ApiLevel.ToString ()); + Ids.Add (VersionCodeFull.ToString ()); + Ids.Add (Id); } public override string ToString () { - return $"(AndroidVersion: ApiLevel={ApiLevel} Id={Id} OSVersion={OSVersion} CodeName='{CodeName}' TargetFrameworkVersion={TargetFrameworkVersion} Stable={Stable})"; + return $"(AndroidVersion: ApiLevel={ApiLevel} VersionCodeFull={VersionCodeFull} Id={Id} OSVersion={OSVersion} CodeName='{CodeName}' TargetFrameworkVersion={TargetFrameworkVersion} Stable={Stable})"; } public static AndroidVersion Load (Stream stream) @@ -76,8 +96,13 @@ static AndroidVersion Load (XDocument doc) var name = (string?) doc.Root?.Element ("Name") ?? throw new InvalidOperationException ("Missing Name element"); var version = (string?) doc.Root?.Element ("Version") ?? throw new InvalidOperationException ("Missing Version element"); var stable = (bool?) doc.Root?.Element ("Stable") ?? throw new InvalidOperationException ("Missing Stable element"); + var versionCodeFull = (string?) doc.Root?.Element ("VersionCodeFull"); + + var fullLevel = string.IsNullOrWhiteSpace (versionCodeFull) + ? new Version (level, 0) + : Version.Parse (versionCodeFull); - return new AndroidVersion (level, version.TrimStart ('v'), name, id, stable); + return new AndroidVersion (fullLevel, version.TrimStart ('v'), name, id, stable); } } } diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidVersions.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidVersions.cs index e44f655bbcd..ce159226451 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidVersions.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidVersions.cs @@ -93,14 +93,13 @@ static bool MatchesFrameworkVersion (AndroidVersion version, string frameworkVer { return installedVersions.FirstOrDefault (v => MatchesId (v, id))?.ApiLevel ?? KnownVersions.FirstOrDefault (v => MatchesId (v, id))?.ApiLevel ?? + (Version.TryParse (id, out var versionCodeFull) ? (int?) versionCodeFull.Major : default (int?)) ?? (int.TryParse (id, out int apiLevel) ? apiLevel : default (int?)); } static bool MatchesId (AndroidVersion version, string id) { - return version.Id == id || - (version.AlternateIds?.Contains (id) ?? false) || - (version.ApiLevel.ToString () == id); + return version.Ids.Contains (id); } public string? GetIdFromApiLevel (int apiLevel) @@ -110,12 +109,21 @@ static bool MatchesId (AndroidVersion version, string id) apiLevel.ToString (); } + public string? GetIdFromVersionCodeFull (Version versionCodeFull) + { + return installedVersions.FirstOrDefault (v => v.VersionCodeFull == versionCodeFull)?.Id ?? + KnownVersions.FirstOrDefault (v => v.VersionCodeFull == versionCodeFull)?.Id ?? + versionCodeFull.ToString (); + } + // Sometimes, e.g. when new API levels are introduced, the "API level" is a letter, not a number, // e.g. 'API-H' for API-11, 'API-O' for API-26, etc. public string? GetIdFromApiLevel (string apiLevel) { if (int.TryParse (apiLevel, out var platform)) return GetIdFromApiLevel (platform); + if (Version.TryParse (apiLevel, out var versionCodeFull)) + return GetIdFromVersionCodeFull (versionCodeFull); return installedVersions.FirstOrDefault (v => MatchesId (v, apiLevel))?.Id ?? KnownVersions.FirstOrDefault (v => MatchesId (v, apiLevel))?.Id ?? apiLevel; diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj index 5fb3608b2b5..9d16f700941 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj @@ -3,7 +3,7 @@ netstandard2.0 $(TargetFrameworks);$(DotNetTargetFramework) - 8.0 + 9.0 enable INTERNAL_NULLABLE_ATTRIBUTES true diff --git a/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/AndroidVersionTests.cs b/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/AndroidVersionTests.cs index c162b5901be..d215469f6a8 100644 --- a/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/AndroidVersionTests.cs +++ b/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/AndroidVersionTests.cs @@ -14,6 +14,7 @@ public void Constructor_Exceptions () { Assert.Throws (() => new AndroidVersion (0, null)); Assert.Throws (() => new AndroidVersion (0, "not a number")); + Assert.Throws (() => new AndroidVersion ((Version) null, osVersion: "1.0")); } [Test] @@ -21,12 +22,33 @@ public void Constructor () { var v = new AndroidVersion (apiLevel: 1, osVersion: "2.3", codeName: "Four", id: "E", stable: false); Assert.AreEqual (1, v.ApiLevel); + Assert.AreEqual (new Version (1, 0), v.VersionCodeFull); Assert.AreEqual ("E", v.Id); Assert.AreEqual ("Four", v.CodeName); Assert.AreEqual ("2.3", v.OSVersion); Assert.AreEqual (new Version (2, 3), v.TargetFrameworkVersion); Assert.AreEqual ("v2.3", v.FrameworkVersion); Assert.AreEqual (false, v.Stable); + Assert.IsTrue (v.Ids.SetEquals (new [] { "1", "1.0", "E" }), $"Actual Ids: {{ {string.Join (", ", v.Ids)} }}"); + } + + [Test] + public void Constructor_NoId () + { + var v = new AndroidVersion (apiLevel: 1, osVersion: "2.3", codeName: "Four", stable: false); + Assert.AreEqual (1, v.ApiLevel); + Assert.AreEqual (new Version (1, 0), v.VersionCodeFull); + Assert.AreEqual ("1", v.Id); + Assert.AreEqual ("Four", v.CodeName); + Assert.AreEqual ("2.3", v.OSVersion); + Assert.AreEqual (new Version (2, 3), v.TargetFrameworkVersion); + Assert.AreEqual ("v2.3", v.FrameworkVersion); + Assert.AreEqual (false, v.Stable); + Assert.IsTrue (v.Ids.SetEquals (new [] { "1", "1.0" })); + + v = new AndroidVersion (new Version (2, 3), osVersion: "2.3", codeName: "Four", stable: false); + Assert.AreEqual ("2.3", v.Id); + Assert.IsTrue (v.Ids.SetEquals (new [] { "2", "2.3" }), $"Actual Ids: {{ {string.Join (", ", v.Ids)} }}"); } [Test] @@ -55,14 +77,39 @@ public void Load () v7.99.0 False "; - var v = AndroidVersion.Load (new MemoryStream (Encoding.UTF8.GetBytes (xml))); + var v = AndroidVersion.Load (new MemoryStream (Encoding.UTF8.GetBytes (xml))); Assert.AreEqual (26, v.ApiLevel); + Assert.AreEqual (new Version (26, 0), v.VersionCodeFull); + Assert.AreEqual ("O", v.Id); + Assert.AreEqual ("Android O", v.CodeName); + Assert.AreEqual ("7.99.0", v.OSVersion); + Assert.AreEqual (new Version (7, 99, 0), v.TargetFrameworkVersion); + Assert.AreEqual ("v7.99.0", v.FrameworkVersion); + Assert.AreEqual (false, v.Stable); + Assert.IsTrue (v.Ids.SetEquals (new [] { "26", "26.0", "O" }), $"Actual Ids: {{ {string.Join (", ", v.Ids)} }}"); + } + + [Test] + public void Load_VersionCodeFull_Replaces_Level () + { + var xml = @" + O + 26 + 27.1 + Android O + v7.99.0 + False +"; + var v = AndroidVersion.Load (new MemoryStream (Encoding.UTF8.GetBytes (xml))); + Assert.AreEqual (27, v.ApiLevel); + Assert.AreEqual (new Version (27, 1), v.VersionCodeFull); Assert.AreEqual ("O", v.Id); Assert.AreEqual ("Android O", v.CodeName); Assert.AreEqual ("7.99.0", v.OSVersion); Assert.AreEqual (new Version (7, 99, 0), v.TargetFrameworkVersion); Assert.AreEqual ("v7.99.0", v.FrameworkVersion); Assert.AreEqual (false, v.Stable); + Assert.IsTrue (v.Ids.SetEquals (new [] { "27", "27.1", "O" }), $"Actual Ids: {{ {string.Join (", ", v.Ids)} }}"); } } } diff --git a/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/AndroidVersionsTests.cs b/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/AndroidVersionsTests.cs index 1ebe8f2c365..d509fe5a962 100644 --- a/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/AndroidVersionsTests.cs +++ b/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/AndroidVersionsTests.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Generic; using System.IO; - +using Microsoft.VisualStudio.TestPlatform.Utilities; using NUnit.Framework; namespace Xamarin.Android.Tools.Tests @@ -92,6 +92,7 @@ public void Constructor_FrameworkDirectories () "", " Z", " 127", + " 127.1", " Z", " v108.1.99", " False", @@ -138,6 +139,9 @@ static AndroidVersions CreateTestVersions () new AndroidVersion (apiLevel: 3, osVersion: "1.2", id: "C", stable: true), // Hides/shadows a Known Version new AndroidVersion (apiLevel: 14, osVersion: "4.0", id: "II", stable: false), + // Demonstrates new "minor" release support + new AndroidVersion (versionCodeFull: new Version (36, 0), osVersion: "16.0", id: "Baklava", stable: true), + new AndroidVersion (versionCodeFull: new Version (36, 1), osVersion: "16.1", id: "CANARY", stable: false), }); } @@ -157,6 +161,7 @@ public void GetApiLevelFromFrameworkVersion () Assert.AreEqual (null, versions.GetApiLevelFromFrameworkVersion ("1.3")); Assert.AreEqual (14, versions.GetApiLevelFromFrameworkVersion ("v4.0")); Assert.AreEqual (14, versions.GetApiLevelFromFrameworkVersion ("4.0")); + Assert.AreEqual (36, versions.GetApiLevelFromFrameworkVersion ("16.1")); // via KnownVersions Assert.AreEqual (4, versions.GetApiLevelFromFrameworkVersion ("v1.6")); @@ -177,6 +182,8 @@ public void GetApiLevelFromId () Assert.AreEqual (3, versions.GetApiLevelFromId ("3")); Assert.AreEqual (14, versions.GetApiLevelFromId ("14")); Assert.AreEqual (14, versions.GetApiLevelFromId ("II")); + Assert.AreEqual (36, versions.GetApiLevelFromId ("36")); + Assert.AreEqual (36, versions.GetApiLevelFromId ("CANARY")); Assert.AreEqual (null, versions.GetApiLevelFromId ("D")); @@ -202,6 +209,13 @@ public void GetIdFromApiLevel () Assert.AreEqual ("II", versions.GetIdFromApiLevel ("14")); Assert.AreEqual ("II", versions.GetIdFromApiLevel ("II")); + Assert.AreEqual ("Baklava", versions.GetIdFromApiLevel (36)); + Assert.AreEqual ("Baklava", versions.GetIdFromApiLevel ("36")); + Assert.AreEqual ("Baklava", versions.GetIdFromApiLevel ("36.0")); + Assert.AreEqual ("Baklava", versions.GetIdFromApiLevel ("Baklava")); + Assert.AreEqual ("CANARY", versions.GetIdFromApiLevel ("36.1")); + Assert.AreEqual ("CANARY", versions.GetIdFromApiLevel ("CANARY")); + Assert.AreEqual ("-1", versions.GetIdFromApiLevel (-1)); Assert.AreEqual ("-1", versions.GetIdFromApiLevel ("-1")); Assert.AreEqual ("D", versions.GetIdFromApiLevel ("D")); @@ -226,6 +240,7 @@ public void GetIdFromFrameworkVersion () Assert.AreEqual ("C", versions.GetIdFromFrameworkVersion ("1.2")); Assert.AreEqual ("II", versions.GetIdFromFrameworkVersion ("v4.0")); Assert.AreEqual ("II", versions.GetIdFromFrameworkVersion ("4.0")); + Assert.AreEqual ("CANARY", versions.GetIdFromFrameworkVersion ("16.1")); Assert.AreEqual (null, versions.GetIdFromFrameworkVersion ("v0.99")); Assert.AreEqual (null, versions.GetIdFromFrameworkVersion ("0.99")); @@ -245,6 +260,7 @@ public void GetFrameworkVersionFromApiLevel () Assert.AreEqual ("v1.1", versions.GetFrameworkVersionFromApiLevel (2)); Assert.AreEqual ("v1.2", versions.GetFrameworkVersionFromApiLevel (3)); Assert.AreEqual ("v4.0", versions.GetFrameworkVersionFromApiLevel (14)); + Assert.AreEqual ("v16.0", versions.GetFrameworkVersionFromApiLevel (36)); // via KnownVersions Assert.AreEqual ("v2.3", versions.GetFrameworkVersionFromApiLevel (10)); @@ -264,6 +280,10 @@ public void GetFrameworkVersionFromId () Assert.AreEqual ("v1.2", versions.GetFrameworkVersionFromId ("C")); Assert.AreEqual ("v4.0", versions.GetFrameworkVersionFromId ("14")); Assert.AreEqual ("v4.0", versions.GetFrameworkVersionFromId ("II")); + Assert.AreEqual ("v16.0", versions.GetFrameworkVersionFromId ("36")); + Assert.AreEqual ("v16.0", versions.GetFrameworkVersionFromId ("Baklava")); + Assert.AreEqual ("v16.1", versions.GetFrameworkVersionFromId ("36.1")); + Assert.AreEqual ("v16.1", versions.GetFrameworkVersionFromId ("CANARY")); // via KnownVersions Assert.AreEqual ("v3.0", versions.GetFrameworkVersionFromId ("11")); From ba2696d129be128232b419e36c6b267b280b57b5 Mon Sep 17 00:00:00 2001 From: Jonathan Pryor Date: Mon, 8 Sep 2025 17:24:33 -0400 Subject: [PATCH 225/308] [Xamarin.Android.Tools.AndroidSdk] Prefer major versions (#262) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Context: https://github.com/dotnet/android/pull/10438 On dotnet/android#10438, many unit tests are failing due to the following warning: Xamarin.Android.Tooling.targets(88,5): warning XA4211: AndroidManifest.xml //uses-sdk/@android:targetSdkVersion 'CANARY' is less than $(TargetPlatformVersion) '36.0'. Using API-CANARY for ACW compilation. Looking at the diagnostic `.binlog`, we *also* see: Target "_ResolveSdks: …" in file "…\Xamarin.Android.Tooling.targets" from project "…\CodeBehindBuildTests.NET.csproj" (target "_ResolveMonoAndroidSdks" depends on it): … Added Item(s): _AndroidApiInfo= C:\a\_work\1\s\bin\Release\dotnet\packs\Microsoft.Android.Sdk.Windows\36.0.0-ci.pr.gh10438.318\tools\..\data\net10.0-android36.1\AndroidApiInfo.xml C:\a\_work\1\s\bin\Release\dotnet\packs\Microsoft.Android.Sdk.Windows\36.0.0-ci.pr.gh10438.318\tools\..\data\net10.0-android36\AndroidApiInfo.xml … Task "ResolveSdks" (TaskId:104) Task Parameter:AndroidSdkPath=C:\Android\android-sdk (TaskId:104) Task Parameter:MinimumSupportedJavaVersion=17.0 (TaskId:104) Task Parameter:JavaSdkPath=C:\hostedtoolcache\windows\Java_Temurin-Hotspot_jdk\17.0.16-8\x64 (TaskId:104) Task Parameter:LatestSupportedJavaVersion=21.0.99 (TaskId:104) Task Parameter: ReferenceAssemblyPaths= C:\a\_work\1\s\bin\Release\dotnet\packs\Microsoft.Android.Sdk.Windows\36.0.0-ci.pr.gh10438.318\data\net10.0-android36.1\ C:\a\_work\1\s\bin\Release\dotnet\packs\Microsoft.Android.Sdk.Windows\36.0.0-ci.pr.gh10438.318\data\net10.0-android36\ (TaskId:104) Task Parameter:CommandLineToolsVersion=19.0 (TaskId:104) The important thing being that the *preview* `net10.0-android36.1\AndroidApiInfo.xml` is being processed *before* the *stable* `net10.0-android36\AndroidApiInfo.xml`. This in turn means that when [``][0] calls `AndroidVersions.GetIdFromApiLevel("36")`, it obtained the `AndroidVersion` instance for API-CANARY, *not* API-36! Task "GetJavaPlatformJar" (TaskId:135) Task Parameter:TargetFramework=net10.0-android (TaskId:135) Task Parameter:BuildingInsideVisualStudio=False (TaskId:135) Task Parameter:SupportedOSPlatformVersion=21.0 (TaskId:135) Task Parameter:AndroidManifest=C:\a\_work\1\a\TestRelease\09-06_00.12.08\temp\CodeBehind\SuccessfulAndroidXApp\Release\project\Properties\AndroidManifest.xml (TaskId:135) Task Parameter:DesignTimeBuild=False (TaskId:135) Task Parameter:TargetPlatformVersion=36.0 (TaskId:135) Task Parameter:AndroidSdkDirectory=C:\Android\android-sdk (TaskId:135) Task Parameter:AndroidSdkPlatform=36 (TaskId:135) 1:7>C:\…\Properties\AndroidManifest.xml : warning XA4211: AndroidManifest.xml //uses-sdk/@android:targetSdkVersion 'CANARY' is less than $(TargetPlatformVersion) '36.0'. Using API-CANARY for ACW compilation. Output Property: JavaPlatformJarPath=C:\Android\android-sdk\platforms\android-CANARY\android.jar (TaskId:135) Output Property: _AndroidTargetSdkVersion=36 (TaskId:135) Done executing task "GetJavaPlatformJar". (TaskId:135) Note input has `TargetPlatformVersion=36.0`, while output has `JavaPlatformJarPath=…\android-CANARY\android.jar`. Update `AndroidVersionsTests` so that the preview API-36.1 is listed *before* API-36, as a *stable* API level (6 months in the future!), and *also* add stable API-37 and unstable API-37.1, to better represent this scenario. Fix this by updating `AndroidVersions` to *sort* `AndroidVersions.installedVersions` by `AndroidVersion.VersionCodeFull`. This ensures that `new Version(36, 0)` precedes `new Version(36, 1)`, and in turn ensures that `AndroidVersions.GetIdFromApiLevel("36")` returns info for API-36 and *not* API-CANARY. [0]: https://github.com/dotnet/android/blob/976544ed415a16b1d44ab06f36f53513361307a5/src/Xamarin.Android.Build.Tasks/Tasks/GetJavaPlatformJar.cs#L120 --- .../AndroidVersions.cs | 3 ++ .../AndroidVersionsTests.cs | 32 +++++++++++++++++-- 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidVersions.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidVersions.cs index ce159226451..e1d8d00ac9d 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidVersions.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidVersions.cs @@ -75,6 +75,9 @@ void LoadVersions (IEnumerable versions) MinStableVersion = version; } } + installedVersions.Sort ((x, y) => { + return x.VersionCodeFull.CompareTo (y.VersionCodeFull); + }); } public int? GetApiLevelFromFrameworkVersion (string frameworkVersion) diff --git a/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/AndroidVersionsTests.cs b/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/AndroidVersionsTests.cs index d509fe5a962..5b61e0576e8 100644 --- a/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/AndroidVersionsTests.cs +++ b/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/AndroidVersionsTests.cs @@ -140,8 +140,10 @@ static AndroidVersions CreateTestVersions () // Hides/shadows a Known Version new AndroidVersion (apiLevel: 14, osVersion: "4.0", id: "II", stable: false), // Demonstrates new "minor" release support + new AndroidVersion (versionCodeFull: new Version (36, 1), osVersion: "16.1", id: "CANARY", stable: true), new AndroidVersion (versionCodeFull: new Version (36, 0), osVersion: "16.0", id: "Baklava", stable: true), - new AndroidVersion (versionCodeFull: new Version (36, 1), osVersion: "16.1", id: "CANARY", stable: false), + new AndroidVersion (versionCodeFull: new Version (37, 1), osVersion: "17.1", id: "E", stable: false), + new AndroidVersion (versionCodeFull: new Version (37, 0), osVersion: "17.0", id: "D", stable: true), }); } @@ -161,7 +163,10 @@ public void GetApiLevelFromFrameworkVersion () Assert.AreEqual (null, versions.GetApiLevelFromFrameworkVersion ("1.3")); Assert.AreEqual (14, versions.GetApiLevelFromFrameworkVersion ("v4.0")); Assert.AreEqual (14, versions.GetApiLevelFromFrameworkVersion ("4.0")); + Assert.AreEqual (36, versions.GetApiLevelFromFrameworkVersion ("16.0")); Assert.AreEqual (36, versions.GetApiLevelFromFrameworkVersion ("16.1")); + Assert.AreEqual (37, versions.GetApiLevelFromFrameworkVersion ("17.0")); + Assert.AreEqual (37, versions.GetApiLevelFromFrameworkVersion ("17.1")); // via KnownVersions Assert.AreEqual (4, versions.GetApiLevelFromFrameworkVersion ("v1.6")); @@ -183,9 +188,13 @@ public void GetApiLevelFromId () Assert.AreEqual (14, versions.GetApiLevelFromId ("14")); Assert.AreEqual (14, versions.GetApiLevelFromId ("II")); Assert.AreEqual (36, versions.GetApiLevelFromId ("36")); + Assert.AreEqual (36, versions.GetApiLevelFromId ("36.1")); Assert.AreEqual (36, versions.GetApiLevelFromId ("CANARY")); + Assert.AreEqual (37, versions.GetApiLevelFromId ("37")); + Assert.AreEqual (37, versions.GetApiLevelFromId ("37.1")); + Assert.AreEqual (37, versions.GetApiLevelFromId ("D")); - Assert.AreEqual (null, versions.GetApiLevelFromId ("D")); + Assert.AreEqual (null, versions.GetApiLevelFromId ("Z")); // via KnownVersions Assert.AreEqual (11, versions.GetApiLevelFromId ("H")); @@ -216,9 +225,17 @@ public void GetIdFromApiLevel () Assert.AreEqual ("CANARY", versions.GetIdFromApiLevel ("36.1")); Assert.AreEqual ("CANARY", versions.GetIdFromApiLevel ("CANARY")); + Assert.AreEqual ("D", versions.GetIdFromApiLevel (37)); + Assert.AreEqual ("D", versions.GetIdFromApiLevel ("37")); + Assert.AreEqual ("D", versions.GetIdFromApiLevel ("37.0")); + Assert.AreEqual ("D", versions.GetIdFromApiLevel ("D")); + Assert.AreEqual ("E", versions.GetIdFromApiLevel ("37.1")); + Assert.AreEqual ("E", versions.GetIdFromApiLevel ("E")); + + // "GIGO" Assert.AreEqual ("-1", versions.GetIdFromApiLevel (-1)); Assert.AreEqual ("-1", versions.GetIdFromApiLevel ("-1")); - Assert.AreEqual ("D", versions.GetIdFromApiLevel ("D")); + Assert.AreEqual ("Z", versions.GetIdFromApiLevel ("Z")); // via KnownVersions Assert.AreEqual ("11", versions.GetIdFromApiLevel (11)); @@ -240,8 +257,12 @@ public void GetIdFromFrameworkVersion () Assert.AreEqual ("C", versions.GetIdFromFrameworkVersion ("1.2")); Assert.AreEqual ("II", versions.GetIdFromFrameworkVersion ("v4.0")); Assert.AreEqual ("II", versions.GetIdFromFrameworkVersion ("4.0")); + Assert.AreEqual ("Baklava", versions.GetIdFromFrameworkVersion ("16.0")); Assert.AreEqual ("CANARY", versions.GetIdFromFrameworkVersion ("16.1")); + Assert.AreEqual ("D", versions.GetIdFromFrameworkVersion ("17.0")); + Assert.AreEqual ("E", versions.GetIdFromFrameworkVersion ("17.1")); + // Unknown values Assert.AreEqual (null, versions.GetIdFromFrameworkVersion ("v0.99")); Assert.AreEqual (null, versions.GetIdFromFrameworkVersion ("0.99")); @@ -261,6 +282,7 @@ public void GetFrameworkVersionFromApiLevel () Assert.AreEqual ("v1.2", versions.GetFrameworkVersionFromApiLevel (3)); Assert.AreEqual ("v4.0", versions.GetFrameworkVersionFromApiLevel (14)); Assert.AreEqual ("v16.0", versions.GetFrameworkVersionFromApiLevel (36)); + Assert.AreEqual ("v17.0", versions.GetFrameworkVersionFromApiLevel (37)); // via KnownVersions Assert.AreEqual ("v2.3", versions.GetFrameworkVersionFromApiLevel (10)); @@ -284,6 +306,10 @@ public void GetFrameworkVersionFromId () Assert.AreEqual ("v16.0", versions.GetFrameworkVersionFromId ("Baklava")); Assert.AreEqual ("v16.1", versions.GetFrameworkVersionFromId ("36.1")); Assert.AreEqual ("v16.1", versions.GetFrameworkVersionFromId ("CANARY")); + Assert.AreEqual ("v17.0", versions.GetFrameworkVersionFromId ("37")); + Assert.AreEqual ("v17.0", versions.GetFrameworkVersionFromId ("D")); + Assert.AreEqual ("v17.1", versions.GetFrameworkVersionFromId ("37.1")); + Assert.AreEqual ("v17.1", versions.GetFrameworkVersionFromId ("E")); // via KnownVersions Assert.AreEqual ("v3.0", versions.GetFrameworkVersionFromId ("11")); From e27b2de6cd62e093daa761bb245e57398d331c0a Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Wed, 15 Oct 2025 12:43:21 -0500 Subject: [PATCH 226/308] Allow `MaximumCompatibleNDKMajorVersion=28` (#263) Context: https://github.com/dotnet/android/commit/80559b5b1daec9e9f0e12783c0349323bdad451c We are already using the 28.x Android NDK, so we should allow it here. I also updated a unit test that was outdated. --- .../Sdks/AndroidSdkBase.cs | 2 +- .../AndroidSdkInfoTests.cs | 9 ++++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkBase.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkBase.cs index 2737f09bb21..b714290f61c 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkBase.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkBase.cs @@ -11,7 +11,7 @@ abstract class AndroidSdkBase { // When this changes, update the test: Xamarin.Android.Tools.Tests.AndroidSdkInfoTests.Ndk_MultipleNdkVersionsInSdk const int MinimumCompatibleNDKMajorVersion = 16; - const int MaximumCompatibleNDKMajorVersion = 26; + const int MaximumCompatibleNDKMajorVersion = 28; static readonly char[] SourcePropertiesKeyValueSplit = new char[] { '=' }; diff --git a/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/AndroidSdkInfoTests.cs b/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/AndroidSdkInfoTests.cs index 043e89c6369..655b01494b6 100644 --- a/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/AndroidSdkInfoTests.cs +++ b/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/AndroidSdkInfoTests.cs @@ -71,7 +71,7 @@ public void Ndk_MultipleNdkVersionsInSdk () { // Must match like-named constants in AndroidSdkBase const int MinimumCompatibleNDKMajorVersion = 16; - const int MaximumCompatibleNDKMajorVersion = 25; + const int MaximumCompatibleNDKMajorVersion = 28; CreateSdks(out string root, out string jdk, out string ndk, out string sdk); @@ -95,9 +95,12 @@ public void Ndk_MultipleNdkVersionsInSdk () "23.1.7779620", "24.0.8215888", "25.0.8775105", - "26.0.3735928559", // 0xdeadbeef + "26.0.10792818", + "27.0.11718014", + "28.0.12433566", + "29.0.3735928559", // 0xdeadbeef }; - string expectedVersion = "25.0.8775105"; + string expectedVersion = ndkVersions [^2]; // Second to last is the highest compatible version string expectedNdkPath = Path.Combine (sdk, "ndk", expectedVersion); try { From d9d5cdd3301abdddf211205feaa1af3dcd899b11 Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Mon, 20 Oct 2025 15:46:10 -0500 Subject: [PATCH 227/308] [Xamarin.Android.Tools.AndroidSdk] Add API-36.1 to `KnownVersions` (#264) Context: https://github.com/dotnet/android/commit/35d471e71c986c302dbf019e5c7838e57d3e0a3e Context: https://android-developers.googleblog.com/2025/08/android-16-qpr2-beta-1-is-here.html Add API 36.1 to the list of known Android versions. `Canary` appears to be the only other name used for API 36.1. This is still Android 16.0, as it is a "quarterly release". --- .../src/Xamarin.Android.Tools.AndroidSdk/AndroidVersions.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidVersions.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidVersions.cs index e1d8d00ac9d..ef371570719 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidVersions.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidVersions.cs @@ -211,6 +211,9 @@ static bool MatchesId (AndroidVersion version, string id) new AndroidVersion (36, "16.0") { AlternateIds = new[]{ "Baklava" }, }, + new AndroidVersion (new Version ("36.1"), "16.0") { + AlternateIds = new[]{ "Canary" }, + }, }; } } From ffc54239a5f65a2968c48fbee6b341f09594bf00 Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Wed, 22 Oct 2025 14:58:49 -0500 Subject: [PATCH 228/308] Update `$(MSBuildPackageReferenceVersion)` to 17.14.28 (#265) --- .../MSBuildReferences.projitems | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems index eba90555831..4357bd67c55 100644 --- a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems +++ b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems @@ -4,7 +4,7 @@ - 17.14.8 + 17.14.28 3.3.0 7.1.0-final.1.21458.1 From 8f709da6546defd83c7a8a09a5603b756e318761 Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Wed, 21 Jan 2026 11:20:27 -0600 Subject: [PATCH 229/308] [Xamarin.Android.Tools.AndroidSdk] Add Android Studio's Android SDK paths (#266) We already had code looking for this path on macOS: ~/Library/Android/sdk This is where Android Studio installs its Android SDK. Unfortunately, windows is at `%localappdata%\Android\Sdk` and we had no code looking for it! I also added a path for Linux. This will also do a `Directory.Exists()` check before returning the new paths. We will also have to probe for these locations *last* to preserve existing behavior. --- .../Sdks/AndroidSdkUnix.cs | 8 +++++++- .../Sdks/AndroidSdkWindows.cs | 1 + 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkUnix.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkUnix.cs index 788c41e2186..8b315f88a0c 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkUnix.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkUnix.cs @@ -111,8 +111,14 @@ protected override IEnumerable GetAllAvailableAndroidSdks () } // Check some hardcoded paths for good measure + // macOS: ~/Library/Android/sdk var macSdkPath = Path.Combine (Environment.GetFolderPath (Environment.SpecialFolder.UserProfile), "Library", "Android", "sdk"); - yield return macSdkPath; + if (Directory.Exists (macSdkPath)) + yield return macSdkPath; + // Linux: ~/Android/Sdk + var linuxSdkPath = Path.Combine (Environment.GetFolderPath (Environment.SpecialFolder.UserProfile), "Android", "Sdk"); + if (Directory.Exists (linuxSdkPath)) + yield return linuxSdkPath; } protected override string GetShortFormPath (string path) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkWindows.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkWindows.cs index 4c075f3a833..ad1e970620c 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkWindows.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkWindows.cs @@ -106,6 +106,7 @@ protected override IEnumerable GetAllAvailableAndroidSdks () : Path.Combine (Environment.GetFolderPath (Environment.SpecialFolder.ProgramFiles), "Android", "android-sdk"), Path.Combine (Environment.GetFolderPath (Environment.SpecialFolder.LocalApplicationData), "Android", "android-sdk"), Path.Combine (Environment.GetFolderPath (Environment.SpecialFolder.CommonApplicationData), "Android", "android-sdk"), + Path.Combine (Environment.GetFolderPath (Environment.SpecialFolder.LocalApplicationData), "Android", "Sdk"), }; foreach (var basePath in paths) if (Directory.Exists (basePath)) From 650c92cf5ffbc9fe85ad3c0c5001043429ea8ede Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Tue, 27 Jan 2026 22:12:38 -0600 Subject: [PATCH 230/308] [Xamarin.Android.Tools.AndroidSdk] Prevent fallback to major API level for minor (#267) Context: https://github.com/dotnet/android/issues/10720 When requesting a minor API level (e.g., 36.1), do not fall back to the major version (e.g., 36) if the specific minor version platform is not installed. Adds a test to verify this behavior and references issue dotnet/android#10720. --- .../AndroidSdkInfo.cs | 8 ++++ .../AndroidSdkInfoTests.cs | 40 +++++++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidSdkInfo.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidSdkInfo.cs index e0eedb651fc..d6913e656ad 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidSdkInfo.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidSdkInfo.cs @@ -107,6 +107,14 @@ public string GetPlatformDirectoryFromId (string id) if (Directory.Exists (dir)) return dir; + // Only fall back to major API level if we weren't explicitly requesting a minor version. + // For example, if "36.1" was requested but android-36.1 doesn't exist, don't fall back to android-36 + // because the minor version may have APIs that the major version doesn't have. + // See: https://github.com/dotnet/android/issues/10720 + if (Version.TryParse (id, out var version) && version.Minor != 0) { + return null; + } + var level = versions.GetApiLevelFromId (id); dir = level.HasValue ? GetPlatformDirectory (level.Value) : null; if (dir != null && Directory.Exists (dir)) diff --git a/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/AndroidSdkInfoTests.cs b/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/AndroidSdkInfoTests.cs index 655b01494b6..e7a25217c6e 100644 --- a/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/AndroidSdkInfoTests.cs +++ b/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/AndroidSdkInfoTests.cs @@ -593,6 +593,46 @@ string UnixConfigPath { } } + [Test] + public void TryGetPlatformDirectoryFromApiLevel_MinorVersionDoesNotFallback () + { + // Verifies that when requesting a minor API level (like 36.1), we don't fall back + // to the major version (36) if the minor version platform is not installed. + // See: https://github.com/dotnet/android/issues/10720 + CreateSdks (out string root, out string jdk, out string ndk, out string sdk); + + // Only create android-36, not android-36.1 + var platformsPath = Path.Combine (sdk, "platforms"); + var platform36Path = Path.Combine (platformsPath, "android-36"); + Directory.CreateDirectory (platform36Path); + File.WriteAllText (Path.Combine (platform36Path, "android.jar"), ""); + + var logs = new StringWriter (); + Action logger = (level, message) => { + logs.WriteLine ($"[{level}] {message}"); + }; + + try { + var info = new AndroidSdkInfo (logger, androidSdkPath: sdk, androidNdkPath: ndk, javaSdkPath: jdk); + var versions = new AndroidVersions (new [] { + new AndroidVersion (36, "16.0"), + new AndroidVersion (new Version (36, 1), "16.0"), + }); + + // Requesting "36" should find android-36 + var dir36 = info.TryGetPlatformDirectoryFromApiLevel ("36", versions); + Assert.IsNotNull (dir36, "Should find android-36"); + Assert.AreEqual (platform36Path, dir36); + + // Requesting "36.1" should NOT fall back to android-36 + var dir361 = info.TryGetPlatformDirectoryFromApiLevel ("36.1", versions); + Assert.IsNull (dir361, "Should NOT fall back to android-36 when android-36.1 is requested but not installed"); + } + finally { + Directory.Delete (root, recursive: true); + } + } + [Test] public void GetBuildToolsPaths_StableVersionsFirst () { From 3689ae9ba88ac6eabfbaa285996596e96d95fffb Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Thu, 5 Feb 2026 13:15:02 -0600 Subject: [PATCH 231/308] Add JDK discovery for user-writable paths on Windows and macOS (#269) - Windows: `%LocalAppData%\Android\*jdk*` - matches `jdk-21`, `jdk-21.0.8.1-hotspot`, `microsoft-21.jdk`, etc. - macOS: `~/Library/Android/*jdk*/` - supports both bundle (`Contents/Home`) and flat JDK structures - Both integrated into `MicrosoftOpenJdkLocations.GetMicrosoftOpenJdks()` discovery chain - Rely on `TryGetJdkInfo()` for validation - Add parameterized tests for multiple folder naming patterns Co-authored-by: Jonathan Peppers --- .../Jdks/JdkLocations.MacOS.cs | 36 ++++++++ .../Jdks/JdkLocations.Windows.cs | 28 ++++++ .../Jdks/MicrosoftOpenJdkLocations.cs | 2 + .../JdkInfoTests.cs | 86 +++++++++++++++++++ 4 files changed, 152 insertions(+) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Jdks/JdkLocations.MacOS.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Jdks/JdkLocations.MacOS.cs index 1aeb1fa40d1..9eb85154ec0 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Jdks/JdkLocations.MacOS.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Jdks/JdkLocations.MacOS.cs @@ -31,6 +31,42 @@ static IEnumerable GetUnixConfiguredJdkPaths (Action const string MacOSJavaVirtualMachinesRoot = "/Library/Java/JavaVirtualMachines"; + protected static IEnumerable GetMacOSUserFileSystemJdks (Action logger) + { + if (!OS.IsMac) { + return Array.Empty (); + } + + // Search ~/Library/Android/*jdk*/ (matches microsoft-21.jdk, jdk-21, etc.) + var libraryAndroidRoot = Path.Combine (Environment.GetFolderPath (Environment.SpecialFolder.UserProfile), "Library", "Android"); + if (!Directory.Exists (libraryAndroidRoot)) { + return Array.Empty (); + } + + IEnumerable dirs; + try { + dirs = Directory.EnumerateDirectories (libraryAndroidRoot, "*jdk*"); + } + catch (IOException) { + return Array.Empty (); + } + + var toHome = Path.Combine ("Contents", "Home"); + var paths = new List (); + foreach (var dir in dirs) { + // Check for macOS .jdk bundle structure (Contents/Home) + var bundleHome = Path.Combine (dir, toHome); + if (Directory.Exists (bundleHome)) { + paths.Add (bundleHome); + } else { + // Flat JDK structure - let TryGetJdkInfo validate + paths.Add (dir); + } + } + + return FromPaths (paths, logger, "~/Library/Android/*jdk*/"); + } + protected static IEnumerable GetMacOSSystemJdks (string pattern, Action logger, string? locator = null) { if (!OS.IsMac) { diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Jdks/JdkLocations.Windows.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Jdks/JdkLocations.Windows.cs index 65e8ad400e3..1eb77c2c1e4 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Jdks/JdkLocations.Windows.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Jdks/JdkLocations.Windows.cs @@ -54,6 +54,34 @@ protected static IEnumerable GetWindowsFileSystemJdks (string pattern, } } + protected static IEnumerable GetWindowsUserFileSystemJdks (Action logger) + { + if (!OS.IsWindows) { + yield break; + } + + var root = Path.Combine (Environment.GetFolderPath (Environment.SpecialFolder.LocalApplicationData), "Android"); + if (!Directory.Exists (root)) { + yield break; + } + + IEnumerable homes; + try { + // Match any folder containing "jdk" (jdk-21, microsoft-21.jdk, jdk-21.0.8.1-hotspot, etc.) + homes = Directory.EnumerateDirectories (root, "*jdk*"); + } + catch (IOException) { + yield break; + } + + foreach (var home in homes) { + var jdk = JdkInfo.TryGetJdkInfo (home, logger, @"%LocalAppData%\Android\*jdk*"); + if (jdk == null) + continue; + yield return jdk; + } + } + protected static IEnumerable GetWindowsRegistryJdks ( Action logger, string parentKey, diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Jdks/MicrosoftOpenJdkLocations.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Jdks/MicrosoftOpenJdkLocations.cs index 157cbd5bc96..d9ee1736fad 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Jdks/MicrosoftOpenJdkLocations.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Jdks/MicrosoftOpenJdkLocations.cs @@ -12,9 +12,11 @@ class MicrosoftOpenJdkLocations : JdkLocations { internal static IEnumerable GetMicrosoftOpenJdks (Action logger) { return GetMacOSSystemJdks ("microsoft-*.jdk", logger) + .Concat (GetMacOSUserFileSystemJdks (logger)) .Concat (GetWindowsFileSystemJdks (Path.Combine ("Android", "openjdk", "jdk-*"), logger)) .Concat (GetWindowsFileSystemJdks (Path.Combine ("Microsoft", "jdk-*"), logger)) .Concat (GetWindowsRegistryJdks (logger, @"SOFTWARE\Microsoft\JDK", "*", @"hotspot\MSI", "Path")) + .Concat (GetWindowsUserFileSystemJdks (logger)) .OrderByDescending (jdk => jdk, JdkInfoVersionComparer.Default); } } diff --git a/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/JdkInfoTests.cs b/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/JdkInfoTests.cs index 9d6aa422f6b..3ec1a50e9aa 100644 --- a/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/JdkInfoTests.cs +++ b/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/JdkInfoTests.cs @@ -271,5 +271,91 @@ public void Version_ThrowsNotSupportedException () Directory.Delete (dir, recursive: true); } } + + [Test] + [TestCase ("jdk-21.0.99")] + [TestCase ("jdk-21.0.8.1-hotspot")] + [TestCase ("microsoft-21.jdk")] + public void GetKnownSystemJdkInfos_DiscoversWindowsUserJdk (string folderName) + { + if (!OS.IsWindows) { + Assert.Ignore ("This test is only valid on Windows."); + return; + } + + var userJdkRoot = Path.Combine (Environment.GetFolderPath (Environment.SpecialFolder.LocalApplicationData), "Android"); + var testJdkDir = Path.Combine (userJdkRoot, folderName); + + try { + CreateFauxJdk (testJdkDir, releaseVersion: "21.0.99", releaseBuildNumber: "1", javaVersion: "21.0.99-1"); + + Action logger = (level, message) => { + Console.WriteLine ($"[{level}] {message}"); + }; + + var jdks = JdkInfo.GetKnownSystemJdkInfos (logger).ToList (); + var foundJdk = jdks.FirstOrDefault (j => j.HomePath == testJdkDir); + + Assert.IsNotNull (foundJdk, $"Expected to find JDK at {testJdkDir} with folder name '{folderName}'"); + Assert.AreEqual (@"%LocalAppData%\Android\*jdk*", foundJdk.Locator, $"Locator should indicate user JDK path for folder '{folderName}'"); + Assert.AreEqual (new Version (21, 0, 99, 1), foundJdk.Version); + } + finally { + if (Directory.Exists (testJdkDir)) + Directory.Delete (testJdkDir, recursive: true); + // Clean up the parent directories if they're empty + if (Directory.Exists (userJdkRoot) && !Directory.EnumerateFileSystemEntries (userJdkRoot).Any ()) + Directory.Delete (userJdkRoot); + } + } + + [Test] + [TestCase ("microsoft-21.jdk", false)] + [TestCase ("jdk-21", true)] + [TestCase ("jdk-21.0.8.1-hotspot", true)] + [TestCase ("temurin-21.jdk", false)] + public void GetKnownSystemJdkInfos_DiscoversMacOSUserJdk (string folderName, bool isFlat) + { + if (!OS.IsMac) { + Assert.Ignore ("This test is only valid on macOS."); + return; + } + + var userJdkRoot = Path.Combine (Environment.GetFolderPath (Environment.SpecialFolder.UserProfile), "Library", "Android"); + string testJdkDir; + string testJdkBundle; + + if (isFlat) { + // Flat structure: release file directly in folder + testJdkDir = Path.Combine (userJdkRoot, folderName); + testJdkBundle = testJdkDir; + } else { + // Bundle structure: Contents/Home inside folder + testJdkBundle = Path.Combine (userJdkRoot, folderName); + testJdkDir = Path.Combine (testJdkBundle, "Contents", "Home"); + } + + try { + CreateFauxJdk (testJdkDir, releaseVersion: "21.0.99", releaseBuildNumber: "1", javaVersion: "21.0.99-1"); + + Action logger = (level, message) => { + Console.WriteLine ($"[{level}] {message}"); + }; + + var jdks = JdkInfo.GetKnownSystemJdkInfos (logger).ToList (); + var foundJdk = jdks.FirstOrDefault (j => j.HomePath == testJdkDir); + + Assert.IsNotNull (foundJdk, $"Expected to find JDK at {testJdkDir} with folder name '{folderName}' (isFlat={isFlat})"); + Assert.AreEqual ("~/Library/Android/*jdk*/", foundJdk.Locator, $"Locator should indicate user JDK path for folder '{folderName}'"); + Assert.AreEqual (new Version (21, 0, 99, 1), foundJdk.Version); + } + finally { + if (Directory.Exists (testJdkBundle)) + Directory.Delete (testJdkBundle, recursive: true); + // Clean up the parent directories if they're empty + if (Directory.Exists (userJdkRoot) && !Directory.EnumerateFileSystemEntries (userJdkRoot).Any ()) + Directory.Delete (userJdkRoot); + } + } } } From 4e5e1339c37fbf5d24ba3c97874ac6c5728e6168 Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Tue, 24 Feb 2026 12:33:53 -0600 Subject: [PATCH 232/308] [build] update to .NET 10 (#286) **Changes:** - Updated .NET target framework from 9.0 to 10.0 across build configuration files - Changed C# language version from specific versions (9.0 and 12.0) to "latest" for consistency - Updated Azure Pipelines to use .NET 10.0.x SDK --- external/xamarin-android-tools/Directory.Build.props | 2 +- external/xamarin-android-tools/azure-pipelines.yaml | 2 +- .../Microsoft.Android.Build.BaseTasks.csproj | 2 +- .../Xamarin.Android.Tools.AndroidSdk.csproj | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/external/xamarin-android-tools/Directory.Build.props b/external/xamarin-android-tools/Directory.Build.props index 7676eaaac7b..2612a3976fc 100644 --- a/external/xamarin-android-tools/Directory.Build.props +++ b/external/xamarin-android-tools/Directory.Build.props @@ -5,7 +5,7 @@ Debug true - 9.0 + 10.0 net$(DotNetTargetFrameworkVersion) true ..\..\product.snk $(VendorPrefix)Microsoft.Android.Build.BaseTasks$(VendorSuffix) - 12.0 + latest diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj index 9d16f700941..4fb13f81f3e 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj @@ -3,7 +3,7 @@ netstandard2.0 $(TargetFrameworks);$(DotNetTargetFramework) - 9.0 + latest enable INTERNAL_NULLABLE_ATTRIBUTES true From 5d585bc8c7746e9783272907149aba856e45d59a Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Tue, 24 Feb 2026 12:45:58 -0600 Subject: [PATCH 233/308] [copilot] create minimal `copilot-instructions.md` (#287) * Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../.github/copilot-instructions.md | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 external/xamarin-android-tools/.github/copilot-instructions.md diff --git a/external/xamarin-android-tools/.github/copilot-instructions.md b/external/xamarin-android-tools/.github/copilot-instructions.md new file mode 100644 index 00000000000..f972e466b77 --- /dev/null +++ b/external/xamarin-android-tools/.github/copilot-instructions.md @@ -0,0 +1,49 @@ +# Copilot Instructions for android-tools + +Shared .NET libraries for Android SDK/JDK discovery and MSBuild task infrastructure, consumed by [dotnet/android](https://github.com/dotnet/android) and IDE extensions. + +## Architecture + +Two independent libraries (neither references the other): + +- **`Xamarin.Android.Tools.AndroidSdk`** — SDK/NDK/JDK path discovery, Android version management, manifest parsing. Multi-targets `netstandard2.0`+`net10.0` (trimming/AOT on modern TFM). Entry: `AndroidSdkInfo` → `AndroidSdkWindows`/`AndroidSdkUnix` via `OS.IsWindows`. +- **`Microsoft.Android.Build.BaseTasks`** — MSBuild task bases and build utilities. `netstandard2.0` only. `RootNamespace` = `Microsoft.Android.Build.Tasks` (differs from project name). + +**Patterns:** +- **Platform polymorphism**: `AndroidSdkBase` → `AndroidSdkWindows`/`AndroidSdkUnix` (Template Method). JDK: vendor classes inherit `JdkLocations` partial, aggregated by priority in `JdkInfo.GetKnownSystemJdkInfos()`. Platform files: `Jdks/JdkLocations.{Windows,MacOS}.cs`, `Sdks/AndroidSdk{Windows,Unix}.cs`. +- **Task base types**: `AndroidTask` (common `Task`-based MSBuild tasks), `AndroidToolTask` (`ToolTask`-based wrappers for external tools), and `AsyncTask` (long-running, UI-safe `Task`-based tasks). All use `UnhandledExceptionLogger` for XA error codes. +- **Incremental builds**: `Files.CopyIf*Changed()` skips unchanged writes. `ObjectPool`/`MemoryStreamPool` reduces GC. `JdkInfo` uses `Lazy` for expensive parsing. + +## Build & Test + +```sh +dotnet build Xamarin.Android.Tools.sln +dotnet test tests/Xamarin.Android.Tools.AndroidSdk-Tests/Xamarin.Android.Tools.AndroidSdk-Tests.csproj +dotnet test tests/Microsoft.Android.Build.BaseTasks-Tests/Microsoft.Android.Build.BaseTasks-Tests.csproj +``` + +Output: `bin\$(Configuration)\` (redistributables), `bin\Test$(Configuration)\` (tests). `$(DotNetTargetFrameworkVersion)` = `10.0` in `Directory.Build.props`. Versioning: `nuget.version` has `major.minor`; patch = git commit count since file changed. + +## Conventions + +- [Mono Coding Guidelines](http://www.mono-project.com/community/contributing/coding-guidelines/): tabs, K&R braces, `PascalCase` public members. +- Nullable enabled in `AndroidSdk`. `NullableAttributes.cs` excluded on `net10.0+`. +- Strong-named via `product.snk`. In the AndroidSdk project, tests use `InternalsVisibleTo` with full public key (`Properties/AssemblyInfo.cs`). +- Assembly names support `$(VendorPrefix)`/`$(VendorSuffix)` for branding forks. +- `.resx` localization in multiple languages via OneLocBuild (`Localize/`). Do not hand-edit satellite `.resx`. + +## Tests + +NUnit 3 (`[TestFixture]`, `[Test]`, `[TestCase]`). Tests create isolated temp dirs with faux JDK/SDK structures (`.bat` on Windows, shell scripts on Unix), cleaned in teardown. `AndroidSdk-Tests`: `[OneTimeSetUp]`/`[OneTimeTearDown]`. `BaseTasks-Tests`: imports `MSBuildReferences.projitems` for MSBuild types. + +## Key Files + +- **SDK discovery**: `AndroidSdkInfo.cs`, `Sdks/AndroidSdk{Windows,Unix}.cs` +- **JDK discovery**: `JdkInfo.cs`, `Jdks/` directory +- **Android versions**: `AndroidVersions.cs` (hardcoded `KnownVersions` table) +- **MSBuild tasks**: `AndroidTask.cs`, `AsyncTask.cs`, `Files.cs` +- **Build config**: `Directory.Build.props`, `Directory.Build.targets`, `nuget.version` + +## Adding Android API Levels + +Update `KnownVersions` in `AndroidVersions.cs` — add tuple with ApiLevel, Id, CodeName, OSVersion, TargetFrameworkVersion, Stable flag. From 3760c788635f46e556d3dcfc62069371e1cb739d Mon Sep 17 00:00:00 2001 From: Rui Marinho Date: Thu, 26 Feb 2026 16:13:15 +0000 Subject: [PATCH 234/308] Add JDK installation support (Microsoft OpenJDK) (#274) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes: https://github.com/dotnet/android-tools/issues/270 Adds JDK installation support to `Xamarin.Android.Tools.AndroidSdk` using the **Microsoft Build of OpenJDK** (https://www.microsoft.com/openjdk). ## New Files - **`JdkInstaller.cs`** — Discovers, downloads, installs, validates, and removes Microsoft OpenJDK - **`DownloadUtils.cs`** — Shared helpers: download with progress, SHA-256 checksum verification, Zip Slip-safe ZIP extraction, tar.gz extraction, checksum file fetching/parsing - **`Models/JdkVersionInfo.cs`** — Version metadata (major, minor, patch, build, platform, architecture, URL, checksum URL) - **`Models/JdkInstallPhase.cs`** — `enum` for progress reporting phases (Downloading, Verifying, Extracting, etc.) - **`Models/JdkInstallProgress.cs`** — `record` combining phase, percentage, and message for `IProgress` callbacks - **`IsExternalInit.cs`** — Enables `init` accessors on netstandard2.0 - **`.github/copilot-instructions.md`** — Repository-specific Copilot context ## Modified Files - **`FileUtil.cs`** — Added `TryDeleteFile`, `TryDeleteDirectory`, `MoveWithRollback`, `CommitMove`, `IsTargetPathWritable`, `IsUnderDirectory`, `GetInstallerExtension`, `GetArchiveExtension` - **`ProcessUtils.cs`** — Added `CreateProcessStartInfo` (uses `ArgumentList` on net5+, `JoinArguments` fallback), `IsElevated` (uses `Environment.IsPrivilegedProcess` on net7+, P/Invoke fallback) - **`.csproj`** — Added `System.Buffers` 4.5.1 (netstandard2.0), `IsExternalInit.cs` exclusion on modern TFMs ## API Surface ```csharp public class JdkInstaller : IDisposable { public JdkInstaller(Action? logger = null); public static int RecommendedMajorVersion { get; } // 21 public static IReadOnlyList SupportedVersions { get; } // [21] public Task> DiscoverAsync(CancellationToken ct = default); public Task InstallAsync(int majorVersion, string targetPath, IProgress? progress = null, CancellationToken ct = default); public bool IsValid(string jdkPath); public bool Remove(string jdkPath); public void Dispose(); } // Helpers used by JdkInstaller (not on JdkInstaller itself): // FileUtil.IsTargetPathWritable(string, Action) // ProcessUtils.IsElevated() ``` ## Key Design Decisions - **Microsoft OpenJDK only** — downloads from `https://aka.ms/download-jdk` with SHA-256 checksum verification - **JDK 21 only** — `RecommendedMajorVersion` = 21, `SupportedVersions` = `[21]`. Array allows future additions - **Owns its `HttpClient`** — no injection; `Dispose()` cleans it up - **Elevated install mode** — when `ProcessUtils.IsElevated()` is true, uses platform-native silent installers (`.msi` via `msiexec /qn`, `.pkg` via `/usr/sbin/installer`). When not elevated, extracts archive to `targetPath` - **Safe directory replacement** — `MoveWithRollback`: rename existing → temp, move new in place, delete temp via `CommitMove`. Avoids Delete/Move race on Windows - **Zip Slip protection** — ZIP extraction validates that all entry paths resolve under the destination directory - **`ArrayPool` download buffer** — `ArrayPool.Shared.Rent` for the 80 KB download buffer, returned in `finally` - **Cross-platform P/Invoke fallbacks** — `IsUserAnAdmin()` (shell32) on Windows, `geteuid()` (libc) on Unix, behind `#if !NET5_0_OR_GREATER` - **`NullProgress` pattern** — avoids null checks throughout install flow - **`netstandard2.0` + `net10.0`** — conditional compilation for `ArgumentList`, `Environment.IsPrivilegedProcess`, `ReadAsStringAsync`/`ReadAsStreamAsync` overloads. Private helpers encapsulate `#if` blocks ## Tests 66 unit tests across 5 test files (all target `net10.0`): | Test File | Count | Coverage | |--------------------------|-------|------------------------------------------------------------------------------------------| | `JdkInstallerTests.cs` | 19 | `IsValid`, `DiscoverAsync`, `InstallAsync`, `IsTargetPathWritable`, `Remove`, properties | | `DownloadUtilsTests.cs` | 20 | `ParseChecksumFile` (12 cases), `VerifyChecksum` (4), `ExtractZipSafe` + Zip Slip (4) | | `FileUtilTests.cs` | 15 | `MoveWithRollback` (4), `IsUnderDirectory` (11 including cross-platform path cases) | | `ProcessUtilsTests.cs` | 7 | `CreateProcessStartInfo` (6), `IsElevated` smoke (1) | | `JdkVersionInfoTests.cs` | 5 | Constructor, defaults, `ToString`, mutable properties | ## Review Feedback Addressed JDK 21 only (removed 17), removed `HttpClient` injection, `IDisposable`, `CancellationToken` pattern, path normalization, validation cleanup, `ArgumentList` for tar, absolute `/usr/bin/tar`, null comparison pattern, one type per file, `record` type, checksum errors throw, test `TearDown` dispose, CI skip, elevated install mode, Zip Slip protection, `ArrayPool` buffer, extracted constants (`BufferSize`, `BytesPerMB`, `WhitespaceChars`), private `ReadAsStreamAsync`/`ReadAsStringAsync` helpers, cross-platform test paths, comprehensive unit test coverage Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Jonathan Peppers --- .../.github/copilot-instructions.md | 13 + .../DownloadUtils.cs | 166 +++++++++ .../FileUtil.cs | 130 +++++++ .../IsExternalInit.cs | 8 + .../JdkInstaller.cs | 336 ++++++++++++++++++ .../Models/Jdk/JdkInstallPhase.cs | 17 + .../Models/Jdk/JdkInstallProgress.cs | 10 + .../Models/Jdk/JdkVersionInfo.cs | 44 +++ .../ProcessUtils.cs | 60 ++++ .../Xamarin.Android.Tools.AndroidSdk.csproj | 6 +- .../DownloadUtilsTests.cs | 221 ++++++++++++ .../FileUtilTests.cs | 151 ++++++++ .../JdkInstallerTests.cs | 266 ++++++++++++++ .../JdkVersionInfoTests.cs | 71 ++++ .../ProcessUtilsTests.cs | 70 ++++ 15 files changed, 1567 insertions(+), 2 deletions(-) create mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/DownloadUtils.cs create mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/IsExternalInit.cs create mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/JdkInstaller.cs create mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Models/Jdk/JdkInstallPhase.cs create mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Models/Jdk/JdkInstallProgress.cs create mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Models/Jdk/JdkVersionInfo.cs create mode 100644 external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/DownloadUtilsTests.cs create mode 100644 external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/FileUtilTests.cs create mode 100644 external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/JdkInstallerTests.cs create mode 100644 external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/JdkVersionInfoTests.cs create mode 100644 external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/ProcessUtilsTests.cs diff --git a/external/xamarin-android-tools/.github/copilot-instructions.md b/external/xamarin-android-tools/.github/copilot-instructions.md index f972e466b77..5a936e49102 100644 --- a/external/xamarin-android-tools/.github/copilot-instructions.md +++ b/external/xamarin-android-tools/.github/copilot-instructions.md @@ -24,8 +24,21 @@ dotnet test tests/Microsoft.Android.Build.BaseTasks-Tests/Microsoft.Android.Buil Output: `bin\$(Configuration)\` (redistributables), `bin\Test$(Configuration)\` (tests). `$(DotNetTargetFrameworkVersion)` = `10.0` in `Directory.Build.props`. Versioning: `nuget.version` has `major.minor`; patch = git commit count since file changed. +## Android Environment Variables + +Per the [official Android docs](https://developer.android.com/tools/variables#envar): + +- **`ANDROID_HOME`** — the canonical variable for the Android SDK root path. Use this everywhere. +- **`ANDROID_SDK_ROOT`** — **deprecated**. Do not introduce new usages. Existing code may still read it for backward compatibility but always prefer `ANDROID_HOME`. +- **`ANDROID_USER_HOME`** — user-level config/AVD storage (defaults to `~/.android`). +- **`ANDROID_EMULATOR_HOME`** — emulator config (defaults to `$ANDROID_USER_HOME`). +- **`ANDROID_AVD_HOME`** — AVD data (defaults to `$ANDROID_USER_HOME/avd`). + +When setting environment variables for SDK tools (e.g. `sdkmanager`, `avdmanager`), set `ANDROID_HOME`. The `EnvironmentVariableNames` class in this repo defines the constants. + ## Conventions +- **One type per file**: each public class, struct, enum, or interface must be in its own `.cs` file named after the type (e.g. `JdkVersionInfo` → `JdkVersionInfo.cs`). Do not combine multiple top-level types in a single file. - [Mono Coding Guidelines](http://www.mono-project.com/community/contributing/coding-guidelines/): tabs, K&R braces, `PascalCase` public members. - Nullable enabled in `AndroidSdk`. `NullableAttributes.cs` excluded on `net10.0+`. - Strong-named via `product.snk`. In the AndroidSdk project, tests use `InternalsVisibleTo` with full public key (`Properties/AssemblyInfo.cs`). diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/DownloadUtils.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/DownloadUtils.cs new file mode 100644 index 00000000000..1a686c217ac --- /dev/null +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/DownloadUtils.cs @@ -0,0 +1,166 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Buffers; +using System.Diagnostics; +using System.IO; +using System.IO.Compression; +using System.Net.Http; +using System.Security.Cryptography; +using System.Threading; +using System.Threading.Tasks; + +namespace Xamarin.Android.Tools +{ + /// + /// Shared helpers for downloading files, verifying checksums, and extracting archives. + /// + static class DownloadUtils + { + const int BufferSize = 81920; + const long BytesPerMB = 1024 * 1024; + static readonly char[] WhitespaceChars = [' ', '\t', '\n', '\r']; + + static Task ReadAsStreamAsync (HttpContent content, CancellationToken cancellationToken) + { +#if NET5_0_OR_GREATER + return content.ReadAsStreamAsync (cancellationToken); +#else + return content.ReadAsStreamAsync (); +#endif + } + + static Task ReadAsStringAsync (HttpContent content, CancellationToken cancellationToken) + { +#if NET5_0_OR_GREATER + return content.ReadAsStringAsync (cancellationToken); +#else + return content.ReadAsStringAsync (); +#endif + } + + /// Downloads a file from the given URL with optional progress reporting. + public static async Task DownloadFileAsync (HttpClient client, string url, string destinationPath, long expectedSize, IProgress<(double percent, string message)>? progress, CancellationToken cancellationToken) + { + using var response = await client.GetAsync (url, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait (false); + response.EnsureSuccessStatusCode (); + + var totalBytes = response.Content.Headers.ContentLength ?? expectedSize; + + using var contentStream = await ReadAsStreamAsync (response.Content, cancellationToken).ConfigureAwait (false); + + var dirPath = Path.GetDirectoryName (destinationPath); + if (!string.IsNullOrEmpty (dirPath)) + Directory.CreateDirectory (dirPath); + + using var fileStream = new FileStream (destinationPath, FileMode.Create, FileAccess.Write, FileShare.None, BufferSize, useAsync: true); + + var buffer = ArrayPool.Shared.Rent (BufferSize); + try { + long totalRead = 0; + int bytesRead; + + while ((bytesRead = await contentStream.ReadAsync (buffer, 0, buffer.Length, cancellationToken).ConfigureAwait (false)) > 0) { + await fileStream.WriteAsync (buffer, 0, bytesRead, cancellationToken).ConfigureAwait (false); + totalRead += bytesRead; + + if (progress is not null && totalBytes > 0) { + var pct = (double) totalRead / totalBytes * 100; + progress.Report ((pct, $"Downloaded {totalRead / BytesPerMB} MB / {totalBytes / BytesPerMB} MB")); + } + } + } + finally { + ArrayPool.Shared.Return (buffer); + } + } + + /// Verifies a file's SHA-256 hash against an expected value. + public static void VerifyChecksum (string filePath, string expectedChecksum) + { + using var sha256 = SHA256.Create (); + using var stream = File.OpenRead (filePath); + + var hash = sha256.ComputeHash (stream); + var actual = BitConverter.ToString (hash).Replace ("-", "").ToLowerInvariant (); + + if (!string.Equals (actual, expectedChecksum, StringComparison.OrdinalIgnoreCase)) + throw new InvalidOperationException ($"Checksum verification failed. Expected: {expectedChecksum}, Actual: {actual}"); + } + + /// Extracts a ZIP archive with Zip Slip protection. + public static void ExtractZipSafe (string archivePath, string destinationPath, CancellationToken cancellationToken) + { + using var archive = ZipFile.OpenRead (archivePath); + var fullExtractRoot = Path.GetFullPath (destinationPath); + + foreach (var entry in archive.Entries) { + cancellationToken.ThrowIfCancellationRequested (); + + if (string.IsNullOrEmpty (entry.Name)) + continue; + + var destinationFile = Path.GetFullPath (Path.Combine (fullExtractRoot, entry.FullName)); + + // Zip Slip protection + if (!FileUtil.IsUnderDirectory (destinationFile, fullExtractRoot)) { + throw new InvalidOperationException ($"Archive entry '{entry.FullName}' would extract outside target directory."); + } + + var entryDir = Path.GetDirectoryName (destinationFile); + if (!string.IsNullOrEmpty (entryDir)) + Directory.CreateDirectory (entryDir); + + entry.ExtractToFile (destinationFile, overwrite: true); + } + } + + /// Extracts a tar.gz archive using the system tar command. + public static async Task ExtractTarGzAsync (string archivePath, string destinationPath, Action logger, CancellationToken cancellationToken) + { + var psi = ProcessUtils.CreateProcessStartInfo ("/usr/bin/tar", "-xzf", archivePath, "-C", destinationPath); + + using var stdout = new StringWriter (); + using var stderr = new StringWriter (); + var exitCode = await ProcessUtils.StartProcess (psi, stdout: stdout, stderr: stderr, cancellationToken).ConfigureAwait (false); + + if (exitCode != 0) { + var errorOutput = stderr.ToString (); + logger (TraceLevel.Error, $"tar extraction failed (exit code {exitCode}): {errorOutput}"); + throw new IOException ($"Failed to extract archive '{archivePath}': {errorOutput}"); + } + } + + /// Fetches a SHA-256 checksum from a remote URL, returning null on failure. + public static async Task FetchChecksumAsync (HttpClient httpClient, string checksumUrl, string label, Action logger, CancellationToken cancellationToken) + { + try { + using var response = await httpClient.GetAsync (checksumUrl, cancellationToken).ConfigureAwait (false); + response.EnsureSuccessStatusCode (); + var content = await ReadAsStringAsync (response.Content, cancellationToken).ConfigureAwait (false); + var checksum = ParseChecksumFile (content); + logger (TraceLevel.Verbose, $"{label}: checksum={checksum}"); + return checksum; + } + catch (OperationCanceledException) { + throw; + } + catch (Exception ex) { + logger (TraceLevel.Warning, $"Could not fetch checksum for {label}: {ex.Message}"); + return null; + } + } + + /// Parses "hash filename" or just "hash" from .sha256sum.txt content. + public static string? ParseChecksumFile (string content) + { + if (string.IsNullOrWhiteSpace (content)) + return null; + + var trimmed = content.Trim (); + var end = trimmed.IndexOfAny (WhitespaceChars); + return end >= 0 ? trimmed.Substring (0, end) : trimmed; + } + } +} diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/FileUtil.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/FileUtil.cs index 1b8457542bf..69012b88c40 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/FileUtil.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/FileUtil.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using System.IO; using System.Runtime.InteropServices; @@ -51,6 +52,135 @@ public static void SystemRename (string sourceFile, string destFile) } } + /// Deletes a file if it exists, logging any failure instead of throwing. + internal static void TryDeleteFile (string path, Action logger) + { + if (!File.Exists (path)) + return; + try { File.Delete (path); } + catch (Exception ex) { logger (TraceLevel.Warning, $"Could not delete '{path}': {ex.Message}"); } + } + + /// Recursively deletes a directory if it exists, logging any failure instead of throwing. + internal static void TryDeleteDirectory (string path, string label, Action logger) + { + if (!Directory.Exists (path)) + return; + try { Directory.Delete (path, recursive: true); } + catch (Exception ex) { logger (TraceLevel.Warning, $"Could not clean up {label} at '{path}': {ex.Message}"); } + } + + /// Moves a directory to the target path, backing up any existing directory and restoring on failure. + internal static void MoveWithRollback (string sourcePath, string targetPath, Action logger) + { + string? backupPath = null; + if (Directory.Exists (targetPath)) { + backupPath = targetPath + $".old-{Guid.NewGuid ():N}"; + Directory.Move (targetPath, backupPath); + } + + var parentDir = Path.GetDirectoryName (targetPath); + if (!string.IsNullOrEmpty (parentDir)) + Directory.CreateDirectory (parentDir); + + try { + Directory.Move (sourcePath, targetPath); + } + catch (Exception ex) { + logger (TraceLevel.Error, $"Failed to move to '{targetPath}': {ex.Message}"); + if (backupPath is not null && Directory.Exists (backupPath)) { + try { + if (Directory.Exists (targetPath)) + Directory.Delete (targetPath, recursive: true); + Directory.Move (backupPath, targetPath); + logger (TraceLevel.Warning, $"Restored previous directory from backup '{backupPath}'."); + } + catch (Exception restoreEx) { + logger (TraceLevel.Error, $"Failed to restore from backup: {restoreEx.Message}"); + } + } + throw; + } + + // Delete backup only after move and caller validation succeed + if (backupPath is not null) + TryDeleteDirectory (backupPath, "old backup", logger); + } + + /// Deletes a backup created by MoveWithRollback. Call after validation succeeds. + internal static void CommitMove (string targetPath, Action logger) + { + // Find and clean up any leftover backup directories + var parentDir = Path.GetDirectoryName (targetPath); + if (string.IsNullOrEmpty (parentDir) || !Directory.Exists (parentDir)) + return; + + var dirName = Path.GetFileName (targetPath); + foreach (var dir in Directory.GetDirectories (parentDir, $"{dirName}.old-*")) { + TryDeleteDirectory (dir, "old backup", logger); + } + } + + /// Checks if the target path is writable by probing write access on the nearest existing ancestor. + /// + /// Follows the same pattern as dotnet/sdk WorkloadInstallerFactory.CanWriteToDotnetRoot: + /// probe with File.Create + DeleteOnClose, only catch UnauthorizedAccessException. + /// See https://github.com/dotnet/sdk/blob/db01067a9c4b67dc1806956393ec63b032032166/src/Cli/dotnet/Commands/Workload/Install/WorkloadInstallerFactory.cs + /// + internal static bool IsTargetPathWritable (string targetPath, Action logger) + { + if (string.IsNullOrEmpty (targetPath)) + return false; + + try { + targetPath = Path.GetFullPath (targetPath); + } + catch { + return false; + } + + try { + // Walk up to the nearest existing ancestor directory + var testDir = targetPath; + while (!string.IsNullOrEmpty (testDir) && !Directory.Exists (testDir)) + testDir = Path.GetDirectoryName (testDir); + + if (string.IsNullOrEmpty (testDir)) + return false; + + var testFile = Path.Combine (testDir, Path.GetRandomFileName ()); + using (File.Create (testFile, 1, FileOptions.DeleteOnClose)) { } + return true; + } + catch (UnauthorizedAccessException) { + logger (TraceLevel.Warning, $"Target path '{targetPath}' is not writable."); + return false; + } + } + + /// Checks if a path is under a given directory. + internal static bool IsUnderDirectory (string path, string directory) + { + if (string.IsNullOrEmpty (directory) || string.IsNullOrEmpty (path)) + return false; + if (path.Equals (directory, StringComparison.OrdinalIgnoreCase)) + return true; + return path.StartsWith (directory + Path.DirectorySeparatorChar, StringComparison.OrdinalIgnoreCase); + } + + // Returns .msi (Windows), .pkg (macOS), or null (Linux) + internal static string? GetInstallerExtension () + { + if (OS.IsWindows) return ".msi"; + if (OS.IsMac) return ".pkg"; + return null; + } + + internal static string GetArchiveExtension () + { + return OS.IsWindows ? ".zip" : ".tar.gz"; + } + [DllImport ("libc", SetLastError=true)] static extern int rename (string old, string @new); } diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/IsExternalInit.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/IsExternalInit.cs new file mode 100644 index 00000000000..4f2414e663d --- /dev/null +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/IsExternalInit.cs @@ -0,0 +1,8 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System.Runtime.CompilerServices +{ + // Polyfill for netstandard2.0 to support C# 9+ records and init-only properties. + internal static class IsExternalInit { } +} diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/JdkInstaller.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/JdkInstaller.cs new file mode 100644 index 00000000000..c982542539a --- /dev/null +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/JdkInstaller.cs @@ -0,0 +1,336 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Runtime.InteropServices; +using System.Threading; +using System.Threading.Tasks; + +namespace Xamarin.Android.Tools +{ + /// + /// Provides JDK installation capabilities using the Microsoft Build of OpenJDK. + /// Downloads from https://aka.ms/download-jdk with SHA-256 verification. + /// See https://www.microsoft.com/openjdk for more information. + /// + public class JdkInstaller : IDisposable + { + const string DownloadUrlBase = "https://aka.ms/download-jdk"; + + /// Gets the recommended JDK major version for .NET Android development. + public static int RecommendedMajorVersion => 21; + + /// Gets the supported JDK major versions available for installation. + public static IReadOnlyList SupportedVersions { get; } = [ RecommendedMajorVersion ]; + + static readonly IProgress NullProgress = new Progress (); + + readonly HttpClient httpClient = new(); + readonly Action logger; + + public JdkInstaller (Action? logger = null) + { + this.logger = logger ?? AndroidSdkInfo.DefaultConsoleLogger; + } + + public void Dispose () => httpClient.Dispose (); + + /// Discovers available Microsoft OpenJDK versions for the current platform. + public async Task> DiscoverAsync (CancellationToken cancellationToken = default) + { + var results = new List (); + + foreach (var version in SupportedVersions) { + cancellationToken.ThrowIfCancellationRequested (); + try { + var info = BuildVersionInfo (version); + + // Verify the download URL is valid with a HEAD request + using var request = new HttpRequestMessage (HttpMethod.Head, info.DownloadUrl); + using var response = await httpClient.SendAsync (request, cancellationToken).ConfigureAwait (false); + + if (!response.IsSuccessStatusCode) { + logger (TraceLevel.Warning, $"JDK {version} not available: HEAD {info.DownloadUrl} returned {(int) response.StatusCode}"); + continue; + } + + if (response.Content.Headers.ContentLength.HasValue) + info.Size = response.Content.Headers.ContentLength.Value; + + if (response.RequestMessage?.RequestUri is not null) + info.ResolvedUrl = response.RequestMessage.RequestUri.ToString (); + + info.Checksum = await DownloadUtils.FetchChecksumAsync (httpClient, info.ChecksumUrl, $"JDK {version}", logger, cancellationToken).ConfigureAwait (false); + if (string.IsNullOrEmpty (info.Checksum)) + logger (TraceLevel.Warning, $"Could not fetch checksum for JDK {version}, integrity verification will be skipped."); + + results.Add (info); + logger (TraceLevel.Info, $"Discovered {info.DisplayName} (size={info.Size})"); + } + catch (OperationCanceledException) { + throw; + } + catch (Exception ex) { + logger (TraceLevel.Warning, $"Failed to discover JDK {version}: {ex.Message}"); + logger (TraceLevel.Verbose, ex.ToString ()); + } + } + + return results.AsReadOnly (); + } + + /// + /// Downloads and installs a Microsoft OpenJDK. + /// When running elevated, uses the platform installer (.msi/.pkg) which chooses its own install location; + /// is ignored in that case. When non-elevated, extracts to . + /// + public async Task InstallAsync (int majorVersion, string targetPath, IProgress? progress = null, CancellationToken cancellationToken = default) + { + progress ??= NullProgress; + + if (!SupportedVersions.Contains (majorVersion)) + throw new ArgumentException ($"JDK version {majorVersion} is not supported. Supported versions: {string.Join (", ", SupportedVersions)}", nameof (majorVersion)); + + if (string.IsNullOrEmpty (targetPath)) + throw new ArgumentNullException (nameof (targetPath)); + + // When elevated and a platform installer is available, use it and let the installer handle paths + if (ProcessUtils.IsElevated () && FileUtil.GetInstallerExtension () is not null) { + logger (TraceLevel.Info, "Running elevated — using platform installer (.msi/.pkg)."); + await InstallWithPlatformInstallerAsync (majorVersion, progress, cancellationToken).ConfigureAwait (false); + return; + } + + if (!FileUtil.IsTargetPathWritable (targetPath, logger)) { + logger (TraceLevel.Error, $"Target path '{targetPath}' is not writable or is in a restricted location."); + throw new ArgumentException ($"Target path '{targetPath}' is not writable or is in a restricted location.", nameof (targetPath)); + } + + var versionInfo = BuildVersionInfo (majorVersion); + + // Fetch checksum - required for supply-chain integrity + var checksum = await DownloadUtils.FetchChecksumAsync (httpClient, versionInfo.ChecksumUrl, $"JDK {majorVersion}", logger, cancellationToken).ConfigureAwait (false); + if (string.IsNullOrEmpty (checksum)) + throw new InvalidOperationException ($"Failed to fetch SHA-256 checksum for JDK {majorVersion}. Cannot verify download integrity."); + versionInfo.Checksum = checksum; + + var tempArchivePath = Path.Combine (Path.GetTempPath (), $"microsoft-jdk-{majorVersion}-{Guid.NewGuid ()}{FileUtil.GetArchiveExtension ()}"); + + try { + // Download + logger (TraceLevel.Info, $"Downloading Microsoft OpenJDK {majorVersion} from {versionInfo.DownloadUrl}"); + progress.Report (new JdkInstallProgress (JdkInstallPhase.Downloading, 0, $"Downloading Microsoft OpenJDK {majorVersion}...")); + await DownloadUtils.DownloadFileAsync (httpClient, versionInfo.DownloadUrl, tempArchivePath, versionInfo.Size, + new Progress<(double pct, string msg)> (p => progress.Report (new JdkInstallProgress (JdkInstallPhase.Downloading, p.pct, p.msg))), + cancellationToken).ConfigureAwait (false); + logger (TraceLevel.Info, $"Download complete: {tempArchivePath}"); + + // Verify checksum + progress.Report (new JdkInstallProgress (JdkInstallPhase.Verifying, 0, "Verifying SHA-256 checksum...")); + DownloadUtils.VerifyChecksum (tempArchivePath, versionInfo.Checksum!); + logger (TraceLevel.Info, "Checksum verified."); + progress.Report (new JdkInstallProgress (JdkInstallPhase.Verifying, 100, "Checksum verified.")); + + // Extract + logger (TraceLevel.Info, $"Extracting JDK to {targetPath}"); + progress.Report (new JdkInstallProgress (JdkInstallPhase.Extracting, 0, "Extracting JDK...")); + await ExtractArchiveAsync (tempArchivePath, targetPath, cancellationToken).ConfigureAwait (false); + logger (TraceLevel.Info, "Extraction complete."); + progress.Report (new JdkInstallProgress (JdkInstallPhase.Extracting, 100, "Extraction complete.")); + + // Validate + progress.Report (new JdkInstallProgress (JdkInstallPhase.Validating, 0, "Validating installation...")); + if (!IsValid (targetPath)) { + logger (TraceLevel.Error, $"JDK installation at '{targetPath}' failed validation."); + FileUtil.TryDeleteDirectory (targetPath, "invalid installation", logger); + throw new InvalidOperationException ($"JDK installation at '{targetPath}' failed validation. The extracted files may be corrupted."); + } + + // Validation passed — commit the move by cleaning up any backup + FileUtil.CommitMove (targetPath, logger); + logger (TraceLevel.Info, $"Microsoft OpenJDK {majorVersion} installed successfully at {targetPath}"); + progress.Report (new JdkInstallProgress (JdkInstallPhase.Validating, 100, "Validation complete.")); + + progress.Report (new JdkInstallProgress (JdkInstallPhase.Complete, 100, $"Microsoft OpenJDK {majorVersion} installed successfully.")); + } + catch (OperationCanceledException) { + throw; + } + catch (Exception ex) when (ex is not ArgumentException and not ArgumentNullException) { + logger (TraceLevel.Error, $"JDK installation failed: {ex.Message}"); + logger (TraceLevel.Verbose, ex.ToString ()); + throw; + } + finally { + FileUtil.TryDeleteFile (tempArchivePath, logger); + } + } + + /// Validates whether the path contains a valid JDK installation. + public bool IsValid (string jdkPath) + { + if (string.IsNullOrEmpty (jdkPath) || !Directory.Exists (jdkPath)) + return false; + + try { + var jdk = new JdkInfo (jdkPath, logger: logger); + return jdk.Version is not null; + } + catch (Exception ex) { + logger (TraceLevel.Warning, $"JDK validation failed for '{jdkPath}': {ex.Message}"); + logger (TraceLevel.Verbose, ex.ToString ()); + return false; + } + } + + async Task InstallWithPlatformInstallerAsync (int majorVersion, IProgress progress, CancellationToken cancellationToken) + { + var installerExt = FileUtil.GetInstallerExtension ()!; + var info = BuildVersionInfo (majorVersion, installerExt); + + // Fetch checksum before download for supply-chain integrity + var checksum = await DownloadUtils.FetchChecksumAsync (httpClient, info.ChecksumUrl, $"JDK {majorVersion} installer", logger, cancellationToken).ConfigureAwait (false); + if (string.IsNullOrEmpty (checksum)) + throw new InvalidOperationException ($"Failed to fetch SHA-256 checksum for JDK {majorVersion} installer. Cannot verify download integrity."); + info.Checksum = checksum; + + var tempInstallerPath = Path.Combine (Path.GetTempPath (), $"microsoft-jdk-{majorVersion}-{Guid.NewGuid ()}{installerExt}"); + + try { + // Download installer + logger (TraceLevel.Info, $"Downloading installer from {info.DownloadUrl}"); + progress.Report (new JdkInstallProgress (JdkInstallPhase.Downloading, 0, $"Downloading Microsoft OpenJDK {majorVersion} installer...")); + await DownloadUtils.DownloadFileAsync (httpClient, info.DownloadUrl, tempInstallerPath, info.Size, + new Progress<(double pct, string msg)> (p => progress.Report (new JdkInstallProgress (JdkInstallPhase.Downloading, p.pct, p.msg))), + cancellationToken).ConfigureAwait (false); + + progress.Report (new JdkInstallProgress (JdkInstallPhase.Verifying, 0, "Verifying SHA-256 checksum...")); + DownloadUtils.VerifyChecksum (tempInstallerPath, info.Checksum!); + progress.Report (new JdkInstallProgress (JdkInstallPhase.Verifying, 100, "Checksum verified.")); + + // Run the installer silently + progress.Report (new JdkInstallProgress (JdkInstallPhase.Extracting, 0, "Running platform installer...")); + await RunPlatformInstallerAsync (tempInstallerPath, cancellationToken).ConfigureAwait (false); + + logger (TraceLevel.Info, $"Microsoft OpenJDK {majorVersion} installed successfully via platform installer."); + progress.Report (new JdkInstallProgress (JdkInstallPhase.Complete, 100, $"Microsoft OpenJDK {majorVersion} installed successfully.")); + } + finally { + FileUtil.TryDeleteFile (tempInstallerPath, logger); + } + } + + async Task RunPlatformInstallerAsync (string installerPath, CancellationToken cancellationToken) + { + var psi = OS.IsWindows + ? ProcessUtils.CreateProcessStartInfo ("msiexec", "/i", installerPath, "/quiet", "/norestart") + : ProcessUtils.CreateProcessStartInfo ("/usr/sbin/installer", "-pkg", installerPath, "-target", "/"); + + using var stdout = new StringWriter (); + using var stderr = new StringWriter (); + var exitCode = await ProcessUtils.StartProcess (psi, stdout: stdout, stderr: stderr, cancellationToken).ConfigureAwait (false); + + if (exitCode != 0) { + var errorOutput = stderr.ToString (); + logger (TraceLevel.Error, $"Installer failed (exit code {exitCode}): {errorOutput}"); + throw new InvalidOperationException ($"Platform installer failed with exit code {exitCode}: {errorOutput}"); + } + } + + /// Removes a JDK installation at the specified path. + public bool Remove (string jdkPath) + { + if (string.IsNullOrEmpty (jdkPath) || !Directory.Exists (jdkPath)) + return false; + + try { + Directory.Delete (jdkPath, recursive: true); + logger (TraceLevel.Info, $"Removed JDK at '{jdkPath}'."); + return true; + } + catch (Exception ex) { + logger (TraceLevel.Error, $"Failed to remove JDK at '{jdkPath}': {ex.Message}"); + logger (TraceLevel.Verbose, ex.ToString ()); + return false; + } + } + + static JdkVersionInfo BuildVersionInfo (int majorVersion, string? extensionOverride = null) + { + var os = GetMicrosoftOpenJDKOSName (); + var arch = GetArchitectureName (); + var ext = extensionOverride ?? FileUtil.GetArchiveExtension (); + + var filename = $"microsoft-jdk-{majorVersion}-{os}-{arch}{ext}"; + var downloadUrl = $"{DownloadUrlBase}/{filename}"; + var checksumUrl = $"{downloadUrl}.sha256sum.txt"; + var displayName = extensionOverride is not null + ? $"Microsoft OpenJDK {majorVersion} ({ext})" + : $"Microsoft OpenJDK {majorVersion}"; + + return new JdkVersionInfo ( + majorVersion: majorVersion, + displayName: displayName, + downloadUrl: downloadUrl, + checksumUrl: checksumUrl + ); + } + + async Task ExtractArchiveAsync (string archivePath, string targetPath, CancellationToken cancellationToken) + { + var targetParent = Path.GetDirectoryName (targetPath); + if (string.IsNullOrEmpty (targetParent)) + targetParent = Path.GetTempPath (); + else + Directory.CreateDirectory (targetParent); + var tempExtractPath = Path.Combine (targetParent, $".jdk-extract-{Guid.NewGuid ()}"); + + try { + Directory.CreateDirectory (tempExtractPath); + + if (OS.IsWindows) + DownloadUtils.ExtractZipSafe (archivePath, tempExtractPath, cancellationToken); + else + await DownloadUtils.ExtractTarGzAsync (archivePath, tempExtractPath, logger, cancellationToken).ConfigureAwait (false); + + // Find the actual JDK root (archives contain a single top-level directory) + var extractedDirs = Directory.GetDirectories (tempExtractPath); + var jdkRoot = extractedDirs.Length == 1 ? extractedDirs [0] : tempExtractPath; + + // On macOS, the JDK is inside Contents/Home + if (OS.IsMac) { + var contentsHome = Path.Combine (jdkRoot, "Contents", "Home"); + if (Directory.Exists (contentsHome)) + jdkRoot = contentsHome; + } + + FileUtil.MoveWithRollback (jdkRoot, targetPath, logger); + } + finally { + FileUtil.TryDeleteDirectory (tempExtractPath, "temp extract directory", logger); + } + } + + static string GetMicrosoftOpenJDKOSName () + { + if (OS.IsMac) return "macOS"; + if (OS.IsWindows) return "windows"; + if (OS.IsLinux) return "linux"; + throw new PlatformNotSupportedException ("Unsupported platform"); + } + + static string GetArchitectureName () + { + return RuntimeInformation.OSArchitecture switch { + Architecture.X64 => "x64", + Architecture.Arm64 => "aarch64", + _ => throw new PlatformNotSupportedException ($"Unsupported architecture: {RuntimeInformation.OSArchitecture}"), + }; + } + } +} diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Models/Jdk/JdkInstallPhase.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Models/Jdk/JdkInstallPhase.cs new file mode 100644 index 00000000000..fff2af6164c --- /dev/null +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Models/Jdk/JdkInstallPhase.cs @@ -0,0 +1,17 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Xamarin.Android.Tools +{ + /// + /// Phases of JDK installation. + /// + public enum JdkInstallPhase + { + Downloading, + Verifying, + Extracting, + Validating, + Complete + } +} diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Models/Jdk/JdkInstallProgress.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Models/Jdk/JdkInstallProgress.cs new file mode 100644 index 00000000000..16a3e01683b --- /dev/null +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Models/Jdk/JdkInstallProgress.cs @@ -0,0 +1,10 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Xamarin.Android.Tools +{ + /// + /// Progress information for JDK installation. + /// + public record JdkInstallProgress (JdkInstallPhase Phase, double PercentComplete, string? Message = null); +} diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Models/Jdk/JdkVersionInfo.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Models/Jdk/JdkVersionInfo.cs new file mode 100644 index 00000000000..0003cc7e37a --- /dev/null +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Models/Jdk/JdkVersionInfo.cs @@ -0,0 +1,44 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Xamarin.Android.Tools +{ + /// + /// Represents information about an available JDK version from Microsoft OpenJDK. + /// + public class JdkVersionInfo + { + /// Major version number (e.g. 17, 21). + public int MajorVersion { get; } + + /// Display name for the version (e.g. "Microsoft OpenJDK 17"). + public string DisplayName { get; } + + /// Download URL for the current platform. + public string DownloadUrl { get; } + + /// URL for the SHA-256 checksum file. + public string ChecksumUrl { get; } + + /// Expected file size in bytes, or 0 if unknown. + public long Size { get; internal set; } + + /// SHA-256 checksum for download verification, if fetched. + public string? Checksum { get; internal set; } + + /// The actual download URL after following redirects (reveals the specific version). + public string? ResolvedUrl { get; internal set; } + + public JdkVersionInfo (int majorVersion, string displayName, string downloadUrl, string checksumUrl, long size = 0, string? checksum = null) + { + MajorVersion = majorVersion; + DisplayName = displayName; + DownloadUrl = downloadUrl; + ChecksumUrl = checksumUrl; + Size = size; + Checksum = checksum; + } + + public override string ToString () => DisplayName; + } +} diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/ProcessUtils.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/ProcessUtils.cs index 00074a72412..86dce45b638 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/ProcessUtils.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/ProcessUtils.cs @@ -2,9 +2,14 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; +using System.Text; using System.Threading; using System.Threading.Tasks; +#if !NET5_0_OR_GREATER +using System.Runtime.InteropServices; +#endif + namespace Xamarin.Android.Tools { public static class ProcessUtils @@ -159,6 +164,40 @@ internal static void Exec (ProcessStartInfo processStartInfo, DataReceivedEventH } } + /// + /// Creates a with the given filename and arguments. + /// On .NET 5+ uses to avoid shell-escaping issues; + /// on older frameworks falls back to a single string. + /// + public static ProcessStartInfo CreateProcessStartInfo (string fileName, params string[] args) + { + var psi = new ProcessStartInfo { + FileName = fileName, + UseShellExecute = false, + CreateNoWindow = true, + }; +#if NET5_0_OR_GREATER + foreach (var arg in args) + psi.ArgumentList.Add (arg); +#else + psi.Arguments = JoinArguments (args); +#endif + return psi; + } + +#if !NET5_0_OR_GREATER + static string JoinArguments (string[] args) + { + var sb = new StringBuilder (); + for (int i = 0; i < args.Length; i++) { + if (i > 0) + sb.Append (' '); + sb.Append ('"').Append (args [i]).Append ('"'); + } + return sb.ToString (); + } +#endif + internal static IEnumerable FindExecutablesInPath (string executable) { var path = Environment.GetEnvironmentVariable ("PATH") ?? ""; @@ -198,6 +237,27 @@ internal static IEnumerable ExecutableFiles (string executable) yield return Path.ChangeExtension (executable, ext); yield return executable; } + + /// Checks if running as Administrator (Windows) or root (macOS/Linux). + public static bool IsElevated () + { +#if NET5_0_OR_GREATER + return Environment.IsPrivilegedProcess; +#else + if (OS.IsWindows) + return IsUserAnAdmin (); + return geteuid () == 0; +#endif + } + +#if !NET5_0_OR_GREATER + [DllImport ("shell32.dll")] + [return: MarshalAs (UnmanagedType.Bool)] + static extern bool IsUserAnAdmin (); + + [DllImport ("libc", SetLastError = true)] + static extern uint geteuid (); +#endif } } diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj index 4fb13f81f3e..13032f2d9d6 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj @@ -27,8 +27,9 @@ true - - + + + @@ -37,6 +38,7 @@ runtime; build; native; contentfiles; analyzers + diff --git a/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/DownloadUtilsTests.cs b/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/DownloadUtilsTests.cs new file mode 100644 index 00000000000..995870df252 --- /dev/null +++ b/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/DownloadUtilsTests.cs @@ -0,0 +1,221 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.IO; +using System.IO.Compression; +using System.Security.Cryptography; +using System.Threading; + +using NUnit.Framework; + +namespace Xamarin.Android.Tools.Tests +{ + [TestFixture] + public class DownloadUtilsTests + { + string tempDir = null!; + + [SetUp] + public void SetUp () + { + tempDir = Path.Combine (Path.GetTempPath (), $"DownloadUtilsTests-{Guid.NewGuid ():N}"); + Directory.CreateDirectory (tempDir); + } + + [TearDown] + public void TearDown () + { + if (Directory.Exists (tempDir)) + Directory.Delete (tempDir, recursive: true); + } + [Test] + public void ParseChecksumFile_Null_ReturnsNull () + { + Assert.IsNull (DownloadUtils.ParseChecksumFile (null!)); + } + + [Test] + public void ParseChecksumFile_Empty_ReturnsNull () + { + Assert.IsNull (DownloadUtils.ParseChecksumFile ("")); + } + + [Test] + public void ParseChecksumFile_WhitespaceOnly_ReturnsNull () + { + Assert.IsNull (DownloadUtils.ParseChecksumFile (" \n\t ")); + } + + [Test] + public void ParseChecksumFile_HashOnly () + { + Assert.AreEqual ("abc123def456", DownloadUtils.ParseChecksumFile ("abc123def456")); + } + + [Test] + public void ParseChecksumFile_HashOnly_WithTrailingNewline () + { + Assert.AreEqual ("abc123def456", DownloadUtils.ParseChecksumFile ("abc123def456\n")); + } + + [Test] + public void ParseChecksumFile_HashAndFilename () + { + // Standard sha256sum format: " " + Assert.AreEqual ("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + DownloadUtils.ParseChecksumFile ("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 microsoft-jdk-21-linux-x64.tar.gz")); + } + + [Test] + public void ParseChecksumFile_HashAndFilename_WithTab () + { + Assert.AreEqual ("abc123", DownloadUtils.ParseChecksumFile ("abc123\tfilename.zip")); + } + + [Test] + public void ParseChecksumFile_MultipleLines_ReturnsFirstHash () + { + var content = "abc123 file1.zip\ndef456 file2.zip\n"; + Assert.AreEqual ("abc123", DownloadUtils.ParseChecksumFile (content)); + } + + [Test] + public void ParseChecksumFile_LeadingAndTrailingWhitespace () + { + Assert.AreEqual ("abc123", DownloadUtils.ParseChecksumFile (" abc123 filename.zip \n")); + } + + [TestCase ("abc123\r\n")] + [TestCase ("abc123\r")] + [TestCase ("abc123\n")] + public void ParseChecksumFile_VariousLineEndings (string content) + { + Assert.AreEqual ("abc123", DownloadUtils.ParseChecksumFile (content)); + } + + // --- VerifyChecksum tests --- + + [Test] + public void VerifyChecksum_MatchingHash_DoesNotThrow () + { + var filePath = Path.Combine (tempDir, "test.bin"); + var content = new byte [] { 0x48, 0x65, 0x6c, 0x6c, 0x6f }; // "Hello" + File.WriteAllBytes (filePath, content); + + using var sha = SHA256.Create (); + var expected = BitConverter.ToString (sha.ComputeHash (content)).Replace ("-", "").ToLowerInvariant (); + + Assert.DoesNotThrow (() => DownloadUtils.VerifyChecksum (filePath, expected)); + } + + [Test] + public void VerifyChecksum_MismatchedHash_Throws () + { + var filePath = Path.Combine (tempDir, "test.bin"); + File.WriteAllBytes (filePath, new byte [] { 1, 2, 3 }); + + var ex = Assert.Throws (() => + DownloadUtils.VerifyChecksum (filePath, "0000000000000000000000000000000000000000000000000000000000000000")); + Assert.That (ex!.Message, Does.Contain ("Checksum verification failed")); + } + + [Test] + public void VerifyChecksum_CaseInsensitive () + { + var filePath = Path.Combine (tempDir, "test.bin"); + var content = new byte [] { 0xFF }; + File.WriteAllBytes (filePath, content); + + using var sha = SHA256.Create (); + var upperHash = BitConverter.ToString (sha.ComputeHash (content)).Replace ("-", "").ToUpperInvariant (); + + Assert.DoesNotThrow (() => DownloadUtils.VerifyChecksum (filePath, upperHash)); + } + + [Test] + public void VerifyChecksum_NonExistentFile_Throws () + { + var filePath = Path.Combine (tempDir, "nonexistent.bin"); + Assert.Throws (() => + DownloadUtils.VerifyChecksum (filePath, "abc123")); + } + + // --- ExtractZipSafe tests --- + + [Test] + public void ExtractZipSafe_ValidZip_ExtractsFiles () + { + var zipPath = Path.Combine (tempDir, "test.zip"); + var extractPath = Path.Combine (tempDir, "extracted"); + Directory.CreateDirectory (extractPath); + + using (var archive = ZipFile.Open (zipPath, ZipArchiveMode.Create)) { + var entry = archive.CreateEntry ("subdir/hello.txt"); + using var writer = new StreamWriter (entry.Open ()); + writer.Write ("hello world"); + } + + DownloadUtils.ExtractZipSafe (zipPath, extractPath, CancellationToken.None); + + var extractedFile = Path.Combine (extractPath, "subdir", "hello.txt"); + Assert.IsTrue (File.Exists (extractedFile), "Extracted file should exist"); + Assert.AreEqual ("hello world", File.ReadAllText (extractedFile)); + } + + [Test] + public void ExtractZipSafe_ZipSlip_Throws () + { + var zipPath = Path.Combine (tempDir, "evil.zip"); + var extractPath = Path.Combine (tempDir, "extracted"); + Directory.CreateDirectory (extractPath); + + using (var archive = ZipFile.Open (zipPath, ZipArchiveMode.Create)) { + // Create an entry with a path traversal + var entry = archive.CreateEntry ("../evil.txt"); + using var writer = new StreamWriter (entry.Open ()); + writer.Write ("malicious"); + } + + var ex = Assert.Throws (() => + DownloadUtils.ExtractZipSafe (zipPath, extractPath, CancellationToken.None)); + Assert.That (ex!.Message, Does.Contain ("outside target directory")); + } + + [Test] + public void ExtractZipSafe_EmptyZip_NoFilesExtracted () + { + var zipPath = Path.Combine (tempDir, "empty.zip"); + var extractPath = Path.Combine (tempDir, "extracted"); + Directory.CreateDirectory (extractPath); + + using (ZipFile.Open (zipPath, ZipArchiveMode.Create)) { } + + DownloadUtils.ExtractZipSafe (zipPath, extractPath, CancellationToken.None); + + Assert.AreEqual (0, Directory.GetFiles (extractPath, "*", SearchOption.AllDirectories).Length); + } + + [Test] + public void ExtractZipSafe_CancellationToken_Throws () + { + var zipPath = Path.Combine (tempDir, "test.zip"); + var extractPath = Path.Combine (tempDir, "extracted"); + Directory.CreateDirectory (extractPath); + + using (var archive = ZipFile.Open (zipPath, ZipArchiveMode.Create)) { + for (int i = 0; i < 10; i++) { + var entry = archive.CreateEntry ($"file{i}.txt"); + using var writer = new StreamWriter (entry.Open ()); + writer.Write ($"content {i}"); + } + } + + using var cts = new CancellationTokenSource (); + cts.Cancel (); + + Assert.Throws (() => + DownloadUtils.ExtractZipSafe (zipPath, extractPath, cts.Token)); + } + } +} diff --git a/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/FileUtilTests.cs b/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/FileUtilTests.cs new file mode 100644 index 00000000000..c94d2cc29a7 --- /dev/null +++ b/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/FileUtilTests.cs @@ -0,0 +1,151 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Diagnostics; +using System.IO; + +using NUnit.Framework; + +namespace Xamarin.Android.Tools.Tests +{ + [TestFixture] + public class FileUtilTests + { + string tempDir = null!; + Action logger = null!; + + [SetUp] + public void SetUp () + { + tempDir = Path.Combine (Path.GetTempPath (), $"FileUtilTests-{Guid.NewGuid ():N}"); + Directory.CreateDirectory (tempDir); + logger = (level, msg) => TestContext.WriteLine ($"[{level}] {msg}"); + } + + [TearDown] + public void TearDown () + { + if (Directory.Exists (tempDir)) + Directory.Delete (tempDir, recursive: true); + } + + [Test] + public void MoveWithRollback_NewTarget_Succeeds () + { + var source = Path.Combine (tempDir, "source"); + var target = Path.Combine (tempDir, "target"); + Directory.CreateDirectory (source); + File.WriteAllText (Path.Combine (source, "file.txt"), "hello"); + + FileUtil.MoveWithRollback (source, target, logger); + + Assert.IsFalse (Directory.Exists (source), "Source should no longer exist"); + Assert.IsTrue (Directory.Exists (target), "Target should exist"); + Assert.AreEqual ("hello", File.ReadAllText (Path.Combine (target, "file.txt"))); + } + + [Test] + public void MoveWithRollback_ExistingTarget_BacksUpAndReplaces () + { + var source = Path.Combine (tempDir, "source"); + var target = Path.Combine (tempDir, "target"); + Directory.CreateDirectory (source); + File.WriteAllText (Path.Combine (source, "new.txt"), "new"); + + Directory.CreateDirectory (target); + File.WriteAllText (Path.Combine (target, "old.txt"), "old"); + + FileUtil.MoveWithRollback (source, target, logger); + + Assert.IsFalse (Directory.Exists (source), "Source should no longer exist"); + Assert.IsTrue (File.Exists (Path.Combine (target, "new.txt")), "New file should exist"); + Assert.IsFalse (File.Exists (Path.Combine (target, "old.txt")), "Old file should be gone"); + } + + [Test] + public void MoveWithRollback_SourceDoesNotExist_RestoresBackup () + { + var source = Path.Combine (tempDir, "nonexistent"); + var target = Path.Combine (tempDir, "target"); + + // Create an existing target that should be backed up and restored + Directory.CreateDirectory (target); + File.WriteAllText (Path.Combine (target, "original.txt"), "preserve me"); + + Assert.Throws (() => FileUtil.MoveWithRollback (source, target, logger)); + + // The original target should be restored from backup + Assert.IsTrue (Directory.Exists (target), "Target should be restored"); + Assert.AreEqual ("preserve me", File.ReadAllText (Path.Combine (target, "original.txt"))); + } + + [Test] + public void MoveWithRollback_SourceDoesNotExist_NoExistingTarget_Throws () + { + var source = Path.Combine (tempDir, "nonexistent"); + var target = Path.Combine (tempDir, "also-nonexistent"); + + Assert.Throws (() => FileUtil.MoveWithRollback (source, target, logger)); + Assert.IsFalse (Directory.Exists (target)); + } + + [Test] + public void IsUnderDirectory_ChildPath_ReturnsTrue () + { + var parent = Path.Combine ($"{Path.DirectorySeparatorChar}opt", "programs"); + var child = Path.Combine (parent, "java", "jdk-21"); + Assert.IsTrue (FileUtil.IsUnderDirectory (child, parent)); + } + + [Test] + public void IsUnderDirectory_ExactMatch_ReturnsTrue () + { + var dir = Path.Combine ($"{Path.DirectorySeparatorChar}opt", "programs"); + Assert.IsTrue (FileUtil.IsUnderDirectory (dir, dir)); + } + + [Test] + public void IsUnderDirectory_SiblingPath_ReturnsFalse () + { + Assert.IsFalse (FileUtil.IsUnderDirectory ( + Path.Combine ($"{Path.DirectorySeparatorChar}opt", "data", "java"), + Path.Combine ($"{Path.DirectorySeparatorChar}opt", "programs"))); + } + + [Test] + public void IsUnderDirectory_DifferentRoot_ReturnsFalse () + { + Assert.IsFalse (FileUtil.IsUnderDirectory ( + Path.Combine ($"{Path.DirectorySeparatorChar}other", "java"), + Path.Combine ($"{Path.DirectorySeparatorChar}opt", "programs"))); + } + + [TestCase (null, "/dir")] + [TestCase ("/dir", null)] + [TestCase ("", "/dir")] + [TestCase ("/dir", "")] + [TestCase (null, null)] + public void IsUnderDirectory_NullOrEmpty_ReturnsFalse (string path, string directory) + { + Assert.IsFalse (FileUtil.IsUnderDirectory (path!, directory!)); + } + + [Test] + public void IsUnderDirectory_CaseInsensitive () + { + var parent = Path.Combine ($"{Path.DirectorySeparatorChar}opt", "Programs"); + var child = Path.Combine ($"{Path.DirectorySeparatorChar}opt", "PROGRAMS", "java"); + Assert.IsTrue (FileUtil.IsUnderDirectory (child, parent)); + } + + [Test] + public void IsUnderDirectory_PartialDirNameMatch_ReturnsFalse () + { + var parent = Path.Combine ($"{Path.DirectorySeparatorChar}opt", "programs"); + Assert.IsFalse (FileUtil.IsUnderDirectory ( + Path.Combine ($"{Path.DirectorySeparatorChar}opt", "programs-extra", "java"), + parent)); + } + } +} diff --git a/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/JdkInstallerTests.cs b/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/JdkInstallerTests.cs new file mode 100644 index 00000000000..2e1530564ab --- /dev/null +++ b/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/JdkInstallerTests.cs @@ -0,0 +1,266 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +using NUnit.Framework; + +namespace Xamarin.Android.Tools.Tests +{ + [TestFixture] + public class JdkInstallerTests + { + JdkInstaller installer; + + [SetUp] + public void SetUp () + { + installer = new JdkInstaller (logger: (level, message) => { + TestContext.WriteLine ($"[{level}] {message}"); + }); + } + + [TearDown] + public void TearDown () + { + installer?.Dispose (); + installer = null!; + } + + [Test] + public void IsValid_NullPath_ReturnsFalse () + { + Assert.IsFalse (installer.IsValid (null!)); + } + + [Test] + public void IsValid_EmptyPath_ReturnsFalse () + { + Assert.IsFalse (installer.IsValid ("")); + } + + [Test] + public void IsValid_NonExistentPath_ReturnsFalse () + { + Assert.IsFalse (installer.IsValid (Path.Combine (Path.GetTempPath (), Guid.NewGuid ().ToString ()))); + } + + [Test] + public void IsValid_EmptyDirectory_ReturnsFalse () + { + var dir = Path.Combine (Path.GetTempPath (), Guid.NewGuid ().ToString ()); + Directory.CreateDirectory (dir); + try { + Assert.IsFalse (installer.IsValid (dir)); + } + finally { + Directory.Delete (dir, recursive: true); + } + } + + [Test] + public void IsValid_FauxJdk_ReturnsTrue () + { + var dir = Path.Combine (Path.GetTempPath (), $"jdk-test-{Guid.NewGuid ()}"); + try { + JdkInfoTests.CreateFauxJdk (dir, releaseVersion: "17.0.1", releaseBuildNumber: "1", javaVersion: "17.0.1-1"); + Assert.IsTrue (installer.IsValid (dir)); + } + finally { + if (Directory.Exists (dir)) + Directory.Delete (dir, recursive: true); + } + } + + [Test] + public void IsValid_SystemJdk_ReturnsTrue () + { + // Find first known JDK on the system + var jdk = JdkInfo.GetKnownSystemJdkInfos ().FirstOrDefault (); + if (jdk is null) { + Assert.Ignore ("No system JDK found to validate."); + return; + } + Assert.IsTrue (installer.IsValid (jdk.HomePath)); + } + + [Test] + public async Task DiscoverAsync_ReturnsVersions () + { + IReadOnlyList versions; + try { + using (var cts = new CancellationTokenSource (TimeSpan.FromSeconds (15))) { + versions = await installer.DiscoverAsync (cts.Token); + } + } + catch (Exception ex) when (ex is System.Net.Http.HttpRequestException || ex is TaskCanceledException || ex is OperationCanceledException) { + Assert.Ignore ($"Network unavailable: {ex.Message}"); + return; + } + + // We should get at least one version (if network is available) + Assert.IsNotNull (versions); + if (versions.Count == 0) { + Assert.Ignore ("No versions returned (network may be restricted)."); + return; + } + + // Verify structure of returned info + foreach (var v in versions) { + Assert.Greater (v.MajorVersion, 0, "MajorVersion should be positive"); + Assert.IsNotEmpty (v.DisplayName, "DisplayName should not be empty"); + Assert.IsNotEmpty (v.DownloadUrl, "DownloadUrl should not be empty"); + Assert.That (v.DownloadUrl, Does.Contain ("aka.ms/download-jdk"), "DownloadUrl should use Microsoft OpenJDK"); + } + } + + [Test] + public async Task DiscoverAsync_ContainsExpectedMajorVersions () + { + IReadOnlyList versions; + try { + versions = await installer.DiscoverAsync (); + } + catch (Exception ex) when (ex is System.Net.Http.HttpRequestException || ex is TaskCanceledException) { + Assert.Ignore ($"Network unavailable: {ex.Message}"); + return; + } + + if (versions.Count == 0) { + Assert.Ignore ("No versions returned."); + return; + } + + var majorVersions = versions.Select (v => v.MajorVersion).Distinct ().ToList (); + Assert.That (majorVersions, Does.Contain (21), "Should contain JDK 21"); + } + + [Test] + public async Task DiscoverAsync_CancellationToken_Cancels () + { + using var cts = new CancellationTokenSource (); + cts.Cancel (); + + Assert.ThrowsAsync ( + async () => await installer.DiscoverAsync (cts.Token)); + } + + [Test] + public void InstallAsync_InvalidVersion_Throws () + { + Assert.ThrowsAsync ( + async () => await installer.InstallAsync (8, Path.GetTempPath ())); + } + + [Test] + public void InstallAsync_NullPath_Throws () + { + Assert.ThrowsAsync ( + async () => await installer.InstallAsync (21, null!)); + } + + [Test] + public async Task InstallAsync_ReportsProgress () + { + // This test actually downloads and installs a JDK, so it may be slow. + // Skip if running in CI or if network is unavailable. + if (Environment.GetEnvironmentVariable ("CI") is not null || + Environment.GetEnvironmentVariable ("TF_BUILD") is not null) { + Assert.Ignore ("Skipping download test in CI environment."); + return; + } + + var targetPath = Path.Combine (Path.GetTempPath (), $"jdk-install-test-{Guid.NewGuid ()}"); + var reportedPhases = new List (); + var progress = new Progress (p => { + reportedPhases.Add (p.Phase); + }); + + try { + using var cts = new CancellationTokenSource (TimeSpan.FromMinutes (10)); + await installer.InstallAsync (21, targetPath, progress, cts.Token); + + // Verify installation + Assert.IsTrue (installer.IsValid (targetPath), "Installed JDK should be valid"); + Assert.IsTrue (reportedPhases.Contains (JdkInstallPhase.Downloading), "Should report Downloading phase"); + Assert.IsTrue (reportedPhases.Contains (JdkInstallPhase.Extracting), "Should report Extracting phase"); + Assert.IsTrue (reportedPhases.Contains (JdkInstallPhase.Complete), "Should report Complete phase"); + + // Verify we can create a JdkInfo from it + var jdkInfo = new JdkInfo (targetPath); + Assert.IsNotNull (jdkInfo.Version); + Assert.AreEqual (21, jdkInfo.Version!.Major); + } + catch (Exception ex) when (ex is System.Net.Http.HttpRequestException || ex is TaskCanceledException) { + Assert.Ignore ($"Network unavailable: {ex.Message}"); + } + finally { + if (Directory.Exists (targetPath)) + Directory.Delete (targetPath, recursive: true); + } + } + + [Test] + public void Constructor_DefaultLogger_DoesNotThrow () + { + using var defaultInstaller = new JdkInstaller (); + Assert.IsNotNull (defaultInstaller); + } + + [Test] + public void RecommendedMajorVersion_Is21 () + { + Assert.AreEqual (21, JdkInstaller.RecommendedMajorVersion); + } + + [Test] + public void SupportedVersions_ContainsExpected () + { + Assert.That (JdkInstaller.SupportedVersions, Does.Contain (21)); + } + + [Test] + public void IsTargetPathWritable_TempDir_ReturnsTrue () + { + var dir = Path.Combine (Path.GetTempPath (), $"jdk-write-test-{Guid.NewGuid ()}"); + try { + Assert.IsTrue (FileUtil.IsTargetPathWritable (dir, (level, msg) => TestContext.WriteLine ($"[{level}] {msg}"))); + } + finally { + if (Directory.Exists (dir)) + Directory.Delete (dir, recursive: true); + } + } + + [Test] + public void IsTargetPathWritable_NullOrEmpty_ReturnsFalse () + { + var logger = new Action ((level, msg) => TestContext.WriteLine ($"[{level}] {msg}")); + Assert.IsFalse (FileUtil.IsTargetPathWritable (null!, logger)); + Assert.IsFalse (FileUtil.IsTargetPathWritable ("", logger)); + } + + [Test] + public void Remove_NonExistentPath_ReturnsFalse () + { + Assert.IsFalse (installer.Remove (Path.Combine (Path.GetTempPath (), Guid.NewGuid ().ToString ()))); + } + + [Test] + public void Remove_ExistingDirectory_RemovesIt () + { + var dir = Path.Combine (Path.GetTempPath (), $"jdk-remove-test-{Guid.NewGuid ()}"); + Directory.CreateDirectory (dir); + File.WriteAllText (Path.Combine (dir, "test.txt"), "test"); + + Assert.IsTrue (installer.Remove (dir)); + Assert.IsFalse (Directory.Exists (dir)); + } + } +} diff --git a/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/JdkVersionInfoTests.cs b/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/JdkVersionInfoTests.cs new file mode 100644 index 00000000000..b37f486ef40 --- /dev/null +++ b/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/JdkVersionInfoTests.cs @@ -0,0 +1,71 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using NUnit.Framework; + +namespace Xamarin.Android.Tools.Tests +{ + [TestFixture] + public class JdkVersionInfoTests + { + [Test] + public void Constructor_SetsAllProperties () + { + var info = new JdkVersionInfo ( + majorVersion: 21, + displayName: "Microsoft OpenJDK 21", + downloadUrl: "https://example.com/jdk-21.zip", + checksumUrl: "https://example.com/jdk-21.zip.sha256sum.txt", + size: 123456789, + checksum: "abc123"); + + Assert.AreEqual (21, info.MajorVersion); + Assert.AreEqual ("Microsoft OpenJDK 21", info.DisplayName); + Assert.AreEqual ("https://example.com/jdk-21.zip", info.DownloadUrl); + Assert.AreEqual ("https://example.com/jdk-21.zip.sha256sum.txt", info.ChecksumUrl); + Assert.AreEqual (123456789, info.Size); + Assert.AreEqual ("abc123", info.Checksum); + } + + [Test] + public void Constructor_DefaultSizeAndChecksum () + { + var info = new JdkVersionInfo ( + majorVersion: 17, + displayName: "Microsoft OpenJDK 17", + downloadUrl: "https://example.com/jdk-17.zip", + checksumUrl: "https://example.com/jdk-17.zip.sha256sum.txt"); + + Assert.AreEqual (0, info.Size); + Assert.IsNull (info.Checksum); + } + + [Test] + public void ToString_ReturnsDisplayName () + { + var info = new JdkVersionInfo (21, "Microsoft OpenJDK 21", "https://example.com/dl", "https://example.com/cs"); + Assert.AreEqual ("Microsoft OpenJDK 21", info.ToString ()); + } + + [Test] + public void MutableProperties_CanBeSet () + { + var info = new JdkVersionInfo (21, "Test", "https://example.com/dl", "https://example.com/cs"); + + info.Size = 999; + info.Checksum = "deadbeef"; + info.ResolvedUrl = "https://resolved.example.com/jdk-21.0.5.zip"; + + Assert.AreEqual (999, info.Size); + Assert.AreEqual ("deadbeef", info.Checksum); + Assert.AreEqual ("https://resolved.example.com/jdk-21.0.5.zip", info.ResolvedUrl); + } + + [Test] + public void ResolvedUrl_DefaultsToNull () + { + var info = new JdkVersionInfo (21, "Test", "https://example.com/dl", "https://example.com/cs"); + Assert.IsNull (info.ResolvedUrl); + } + } +} diff --git a/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/ProcessUtilsTests.cs b/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/ProcessUtilsTests.cs new file mode 100644 index 00000000000..a9e7be31d8a --- /dev/null +++ b/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/ProcessUtilsTests.cs @@ -0,0 +1,70 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics; + +using NUnit.Framework; + +namespace Xamarin.Android.Tools.Tests +{ + [TestFixture] + public class ProcessUtilsTests + { + [Test] + public void CreateProcessStartInfo_SetsFileName () + { + var psi = ProcessUtils.CreateProcessStartInfo ("myapp"); + Assert.AreEqual ("myapp", psi.FileName); + } + + [Test] + public void CreateProcessStartInfo_SetsShellAndWindow () + { + var psi = ProcessUtils.CreateProcessStartInfo ("myapp"); + Assert.IsFalse (psi.UseShellExecute, "UseShellExecute should be false"); + Assert.IsTrue (psi.CreateNoWindow, "CreateNoWindow should be true"); + } + + [Test] + public void CreateProcessStartInfo_NoArgs () + { + var psi = ProcessUtils.CreateProcessStartInfo ("myapp"); + Assert.AreEqual (0, psi.ArgumentList.Count); + } + + [Test] + public void CreateProcessStartInfo_SingleArg () + { + var psi = ProcessUtils.CreateProcessStartInfo ("myapp", "--version"); + Assert.AreEqual (1, psi.ArgumentList.Count); + Assert.AreEqual ("--version", psi.ArgumentList [0]); + } + + [Test] + public void CreateProcessStartInfo_MultipleArgs () + { + var psi = ProcessUtils.CreateProcessStartInfo ("tar", "-xzf", "archive.tar.gz", "-C", "/tmp/output"); + Assert.AreEqual (4, psi.ArgumentList.Count); + Assert.AreEqual ("-xzf", psi.ArgumentList [0]); + Assert.AreEqual ("archive.tar.gz", psi.ArgumentList [1]); + Assert.AreEqual ("-C", psi.ArgumentList [2]); + Assert.AreEqual ("/tmp/output", psi.ArgumentList [3]); + } + + [Test] + public void CreateProcessStartInfo_ArgWithSpaces () + { + var psi = ProcessUtils.CreateProcessStartInfo ("cmd", "/c", "path with spaces"); + Assert.AreEqual (2, psi.ArgumentList.Count); + Assert.AreEqual ("path with spaces", psi.ArgumentList [1]); + } + + [Test] + public void IsElevated_DoesNotThrow () + { + // Smoke test: just verify it returns without crashing + bool result = ProcessUtils.IsElevated (); + Assert.That (result, Is.TypeOf ()); + } + } +} From 266d4c868399fe901e450af8a4b489ae7fca4dbd Mon Sep 17 00:00:00 2001 From: Rui Marinho Date: Fri, 27 Feb 2026 18:47:43 +0000 Subject: [PATCH 235/308] Add `EnvironmentVariableNames` constants for env var management (#290) Replace hardcoded environment variable strings with centralized constants in EnvironmentVariableNames.cs across AndroidSdkBase, JdkInfo, AndroidSdkWindows, and ProcessUtils. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Jonathan Peppers --- .../EnvironmentVariableNames.cs | 44 +++++++++++++++++++ .../JdkInfo.cs | 4 +- .../ProcessUtils.cs | 4 +- .../Sdks/AndroidSdkBase.cs | 4 +- .../Sdks/AndroidSdkWindows.cs | 8 ++-- 5 files changed, 55 insertions(+), 9 deletions(-) create mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/EnvironmentVariableNames.cs diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/EnvironmentVariableNames.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/EnvironmentVariableNames.cs new file mode 100644 index 00000000000..272a17b1db6 --- /dev/null +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/EnvironmentVariableNames.cs @@ -0,0 +1,44 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Xamarin.Android.Tools +{ + /// + /// Constants for environment variable names used by Android SDK tooling. + /// See https://developer.android.com/tools/variables#envar + /// + internal static class EnvironmentVariableNames + { + /// + /// The preferred variable for the Android SDK root directory. + /// + public const string AndroidHome = "ANDROID_HOME"; + + /// + /// Deprecated — use instead. + /// Retained for reading existing environment configurations. + /// + [System.Obsolete ("ANDROID_SDK_ROOT is deprecated. Use ANDROID_HOME instead.")] + public const string AndroidSdkRoot = "ANDROID_SDK_ROOT"; + + /// + /// The JDK installation directory. + /// + public const string JavaHome = "JAVA_HOME"; + + /// + /// Internal/override JDK path. Takes precedence over JAVA_HOME when set. + /// + public const string JiJavaHome = "JI_JAVA_HOME"; + + /// + /// Executable search paths. + /// + public const string Path = "PATH"; + + /// + /// Executable file extensions (Windows). + /// + public const string PathExt = "PATHEXT"; + } +} diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/JdkInfo.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/JdkInfo.cs index 4969874b2c9..0da0880a02b 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/JdkInfo.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/JdkInfo.cs @@ -328,7 +328,7 @@ public static IEnumerable GetKnownSystemJdkInfos (Action GetKnownSystemJdkInfos (Action FindExecutablesInPath (string executable) { - var path = Environment.GetEnvironmentVariable ("PATH") ?? ""; + var path = Environment.GetEnvironmentVariable (EnvironmentVariableNames.Path) ?? ""; var pathDirs = path.Split (new char[] { Path.PathSeparator }, StringSplitOptions.RemoveEmptyEntries); foreach (var dir in pathDirs) { diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkBase.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkBase.cs index b714290f61c..f05668f6e87 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkBase.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkBase.cs @@ -16,7 +16,9 @@ abstract class AndroidSdkBase static readonly char[] SourcePropertiesKeyValueSplit = new char[] { '=' }; // Per https://developer.android.com/studio/command-line/variables#envar - protected static readonly string[] AndroidSdkEnvVars = {"ANDROID_HOME", "ANDROID_SDK_ROOT"}; + #pragma warning disable CS0618 // ANDROID_SDK_ROOT is obsolete but still needed for compat + protected static readonly string[] AndroidSdkEnvVars = {EnvironmentVariableNames.AndroidHome, EnvironmentVariableNames.AndroidSdkRoot}; + #pragma warning restore CS0618 string[]? allAndroidSdks; diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkWindows.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkWindows.cs index ad1e970620c..ee6b8ab750f 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkWindows.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Sdks/AndroidSdkWindows.cs @@ -185,16 +185,16 @@ public override void Initialize (string? androidSdkPath = null, string? androidN var jdkPath = JavaSdkPath; if (!string.IsNullOrEmpty (jdkPath)) { - var cur = Environment.GetEnvironmentVariable ("JAVA_HOME"); + var cur = Environment.GetEnvironmentVariable (EnvironmentVariableNames.JavaHome); if (!string.IsNullOrEmpty (cur)) - Environment.SetEnvironmentVariable ("JAVA_HOME", jdkPath); + Environment.SetEnvironmentVariable (EnvironmentVariableNames.JavaHome, jdkPath); var javaBinPath = this.JavaBinPath; if (!string.IsNullOrEmpty (javaBinPath)) { - var environmentPath = Environment.GetEnvironmentVariable ("PATH") ?? ""; + var environmentPath = Environment.GetEnvironmentVariable (EnvironmentVariableNames.Path) ?? ""; if (!environmentPath.Contains (javaBinPath)) { var processPath = string.Concat (javaBinPath, Path.PathSeparator, environmentPath); - Environment.SetEnvironmentVariable ("PATH", processPath); + Environment.SetEnvironmentVariable (EnvironmentVariableNames.Path, processPath); } } } From 000ef79b0b6fb040f126b8f4ad13c729040d0a8c Mon Sep 17 00:00:00 2001 From: Rui Marinho Date: Mon, 2 Mar 2026 22:53:04 +0000 Subject: [PATCH 236/308] Add SDK bootstrap and sdkmanager wrapper (#275) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes: https://github.com/dotnet/android-tools/issues/271 Adds `SdkManager` class and `sdkmanager` CLI wrapper to `Xamarin.Android.Tools.AndroidSdk`, enabling programmatic Android SDK bootstrap and package management. ## Architecture **Two-phase approach**: 1. **Bootstrap**: Download command-line tools from the Google manifest feed, SHA-1 verify, extract to `cmdline-tools//` 2. **Manage**: Use the extracted `sdkmanager` CLI for all package operations (list, install, uninstall, update, licenses) `SdkManager` is split into focused partial classes for maintainability: | File | Responsibility | |------|---------------| | `SdkManager.cs` | Core class, properties, constructor, `IDisposable` | | `SdkManager.Manifest.cs` | Manifest download and `XmlReader`-based parsing | | `SdkManager.Bootstrap.cs` | SDK bootstrap/download/extract flow | | `SdkManager.Packages.cs` | Package list/install/uninstall/update + output parsing | | `SdkManager.Licenses.cs` | License accept/parse/check, `GetPendingLicensesAsync` | | `SdkManager.Process.cs` | Process execution via `ProcessUtils`, environment config | ## New Files **Models** (`Models/Sdk/`): - `ChecksumType.cs` — Enum for hash algorithms (SHA-256, SHA-1) - `SdkBootstrapProgress.cs` — Progress reporting during bootstrap - `SdkLicense.cs` — License data with `Id` and `Text` - `SdkManifestComponent.cs` — Parsed manifest entry (URL, checksum, platform) - `SdkPackage.cs` — Installed/available SDK package info **Utilities** (extended existing files): - `DownloadUtils.cs` — Added SHA-1 support, `ComputeHashString` shared helper, `ChecksumType` enum - `FileUtil.cs` — Added `ExtractAndInstall` (safe extraction with rollback) and `IsUnderDirectory` ## Key Features - **Manifest-driven bootstrap**: `XmlReader`-based parsing with platform/architecture filtering - **`IDisposable`** with `ThrowIfDisposed` guard on all public methods - **`ANDROID_HOME` only**: Sets env var per Android docs; deprecated `ANDROID_SDK_ROOT` not used - **`ANDROID_USER_HOME`**: Sets `~/.android` so sdkmanager writes to user-writable location - **Version-based cmdline-tools directory**: Installs to `cmdline-tools//` (not `latest/`) - **Elevated execution** (Windows): Detects admin-protected paths, re-launches via UAC; read-only ops skip elevation - **Zip Slip protection**: Entry-by-entry extraction with path traversal validation - **Rollback on failure**: Backup existing installation, restore on extract/move failure - **Cross-device fallback**: `Directory.Move` falls back to recursive copy on `IOException` - **SHA-1 checksum verification** of downloaded archives - **p/invoke `chmod`** with process fallback for Unix executable permissions (throws on failure) - **`ArrayPool` buffers** (`NET5_0_OR_GREATER`) for download operations - **`ProcessUtils`** for all process execution — no direct `System.Diagnostics.Process` usage - **`ProcessStartInfo.ArgumentList`** via `ProcessUtils.CreateProcessStartInfo` (falls back to `Arguments` on netstandard2.0) - **License presentation API**: `GetPendingLicensesAsync()` returns `SdkLicense` objects for IDE/CLI display; `AcceptLicensesAsync(IEnumerable)` accepts specific licenses by ID - **`netstandard2.0` + `net10.0`** compatible, full `CancellationToken` support - **`EnvironmentVariableNames`** constants used throughout (no raw strings) - **No empty catch blocks** — all exceptions are logged ## Coding Conventions Updated `.github/copilot-instructions.md` updated with conventions established during review: - One type per file - Minimal public API (`internal` by default, `InternalsVisibleTo` for tests) - Use `ProcessUtils` for process execution - Use `FileUtil` for file operations - File-scoped namespaces for new files - `netstandard2.0` awareness ## Tests `SdkManagerTests.cs` covers: - Manifest XML parsing (components, platform filtering) - Package list output parsing (installed, available, updates) - License output parsing (single, multiple, none) - Argument validation (null/empty packages → `ArgumentException`) - Missing sdkmanager → `InvalidOperationException` (Install, Uninstall, List, Update, AcceptLicenses) - Bootstrap null path → `ArgumentNullException` - Network-dependent tests (manifest fetch, `HasAcceptedLicenses`) with `Assert.Ignore` on failure Co-authored-by: Jonathan Peppers --- .../.github/copilot-instructions.md | 9 + external/xamarin-android-tools/.gitignore | 1 + .../DownloadUtils.cs | 31 +- .../FileUtil.cs | 86 +++ .../Models/Sdk/ChecksumType.cs | 10 + .../Models/Sdk/SdkBootstrapPhase.cs | 21 + .../Models/Sdk/SdkBootstrapProgress.cs | 7 + .../Models/Sdk/SdkLicense.cs | 6 + .../Models/Sdk/SdkManifestComponent.cs | 18 + .../Models/Sdk/SdkPackage.cs | 8 + .../ProcessUtils.cs | 10 +- .../SdkManager.Bootstrap.cs | 85 +++ .../SdkManager.Licenses.cs | 214 ++++++++ .../SdkManager.Manifest.cs | 144 +++++ .../SdkManager.Packages.cs | 142 +++++ .../SdkManager.Process.cs | 110 ++++ .../SdkManager.cs | 89 +++ .../SdkManagerTests.cs | 515 ++++++++++++++++++ 18 files changed, 1497 insertions(+), 9 deletions(-) create mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Models/Sdk/ChecksumType.cs create mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Models/Sdk/SdkBootstrapPhase.cs create mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Models/Sdk/SdkBootstrapProgress.cs create mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Models/Sdk/SdkLicense.cs create mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Models/Sdk/SdkManifestComponent.cs create mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Models/Sdk/SdkPackage.cs create mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/SdkManager.Bootstrap.cs create mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/SdkManager.Licenses.cs create mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/SdkManager.Manifest.cs create mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/SdkManager.Packages.cs create mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/SdkManager.Process.cs create mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/SdkManager.cs create mode 100644 external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/SdkManagerTests.cs diff --git a/external/xamarin-android-tools/.github/copilot-instructions.md b/external/xamarin-android-tools/.github/copilot-instructions.md index 5a936e49102..582d2a8f6d0 100644 --- a/external/xamarin-android-tools/.github/copilot-instructions.md +++ b/external/xamarin-android-tools/.github/copilot-instructions.md @@ -39,6 +39,15 @@ When setting environment variables for SDK tools (e.g. `sdkmanager`, `avdmanager ## Conventions - **One type per file**: each public class, struct, enum, or interface must be in its own `.cs` file named after the type (e.g. `JdkVersionInfo` → `JdkVersionInfo.cs`). Do not combine multiple top-level types in a single file. +- **Minimal public API**: prefer `internal` for new methods/classes unless they are consumed by external projects (dotnet/android, IDE extensions). Use `InternalsVisibleTo` for test access. +- **Use `ProcessUtils`**: never use `System.Diagnostics.Process` directly. Use the existing helpers such as `ProcessUtils.CreateProcessStartInfo()`, `ProcessUtils.StartProcess()`, and `ProcessUtils.ExecuteToolAsync()` for launching external tools. This ensures consistent logging, timeout handling, and cancellation. +- **Process arguments**: use `ProcessUtils.CreateProcessStartInfo()` and pass arguments as separate strings instead of building a single arguments string yourself. `ProcessUtils` will use `ProcessStartInfo.ArgumentList` when available and fall back to `Arguments` on `netstandard2.0`. +- **Use `FileUtil`**: file operations like extraction, downloads, checksum verification, and path checks belong in `FileUtil.cs`. Don't duplicate file helpers in domain classes. +- **Concise XML docs**: omit `` tags for self-explanatory methods. Only add doc comments when the behavior is non-obvious. Avoid restating the method name. +- **`netstandard2.0` awareness**: many modern .NET APIs are unavailable or have fewer overloads on `netstandard2.0`. When unsure about API availability, search mslearn to check documentation for the target framework. +- **Format your code**: always match the existing file indentation (tabs, not spaces — see `.editorconfig`). Only format code you add or modify; never reformat existing lines. +- **File-scoped namespaces**: all new files should use file-scoped namespaces (`namespace Foo;` instead of `namespace Foo { ... }`). +- **Static `HttpClient`**: `HttpClient` instances must be `static` to avoid socket exhaustion. See [HttpClient guidelines](https://learn.microsoft.com/dotnet/fundamentals/networking/http/httpclient-guidelines#recommended-use). Do not create per-instance `HttpClient` fields or dispose them in `IDisposable`. - [Mono Coding Guidelines](http://www.mono-project.com/community/contributing/coding-guidelines/): tabs, K&R braces, `PascalCase` public members. - Nullable enabled in `AndroidSdk`. `NullableAttributes.cs` excluded on `net10.0+`. - Strong-named via `product.snk`. In the AndroidSdk project, tests use `InternalsVisibleTo` with full public key (`Properties/AssemblyInfo.cs`). diff --git a/external/xamarin-android-tools/.gitignore b/external/xamarin-android-tools/.gitignore index 541b07af084..aae837edfc5 100644 --- a/external/xamarin-android-tools/.gitignore +++ b/external/xamarin-android-tools/.gitignore @@ -14,3 +14,4 @@ packages/ TestResult-*.xml **/TestResults/*.trx .vs/ +*.nupkg diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/DownloadUtils.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/DownloadUtils.cs index 1a686c217ac..2fc56cb4a41 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/DownloadUtils.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/DownloadUtils.cs @@ -31,7 +31,7 @@ static Task ReadAsStreamAsync (HttpContent content, CancellationToken ca #endif } - static Task ReadAsStringAsync (HttpContent content, CancellationToken cancellationToken) + internal static Task ReadAsStringAsync (HttpContent content, CancellationToken cancellationToken) { #if NET5_0_OR_GREATER return content.ReadAsStringAsync (cancellationToken); @@ -76,19 +76,38 @@ public static async Task DownloadFileAsync (HttpClient client, string url, strin } } - /// Verifies a file's SHA-256 hash against an expected value. - public static void VerifyChecksum (string filePath, string expectedChecksum) + /// Verifies a file's hash against an expected value using the specified algorithm. + public static void VerifyChecksum (string filePath, string expectedChecksum, ChecksumType checksumType = ChecksumType.Sha256) { - using var sha256 = SHA256.Create (); + using var hasher = CreateHashAlgorithm (checksumType); using var stream = File.OpenRead (filePath); - var hash = sha256.ComputeHash (stream); - var actual = BitConverter.ToString (hash).Replace ("-", "").ToLowerInvariant (); + var actual = ComputeHashString (hasher, stream); if (!string.Equals (actual, expectedChecksum, StringComparison.OrdinalIgnoreCase)) throw new InvalidOperationException ($"Checksum verification failed. Expected: {expectedChecksum}, Actual: {actual}"); } + /// Computes the hash of the given bytes and returns it as a lowercase hex string. + internal static string ComputeHashString (ChecksumType checksumType, byte[] data) + { + using var hasher = CreateHashAlgorithm (checksumType); + var hash = hasher.ComputeHash (data); + return BitConverter.ToString (hash).Replace ("-", "").ToLowerInvariant (); + } + + static HashAlgorithm CreateHashAlgorithm (ChecksumType checksumType) => checksumType switch { + ChecksumType.Sha256 => (HashAlgorithm) SHA256.Create (), + ChecksumType.Sha1 => SHA1.Create (), + _ => throw new NotSupportedException ($"Unsupported checksum type: '{checksumType}'."), + }; + + static string ComputeHashString (HashAlgorithm hasher, Stream stream) + { + var hash = hasher.ComputeHash (stream); + return BitConverter.ToString (hash).Replace ("-", "").ToLowerInvariant (); + } + /// Extracts a ZIP archive with Zip Slip protection. public static void ExtractZipSafe (string archivePath, string destinationPath, CancellationToken cancellationToken) { diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/FileUtil.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/FileUtil.cs index 69012b88c40..f324fbd6be4 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/FileUtil.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/FileUtil.cs @@ -2,6 +2,8 @@ using System.Diagnostics; using System.IO; using System.Runtime.InteropServices; +using System.Threading; +using System.Threading.Tasks; namespace Xamarin.Android.Tools { @@ -107,6 +109,31 @@ internal static void MoveWithRollback (string sourcePath, string targetPath, Act TryDeleteDirectory (backupPath, "old backup", logger); } + /// + /// Extracts a zip archive to a temp directory, locates the expected top-level folder, + /// and moves it to the target path with rollback support. + /// + internal static void ExtractAndInstall (string archivePath, string targetPath, string expectedTopDir, Action logger, CancellationToken cancellationToken) + { + var tempExtractDir = Path.Combine (Path.GetTempPath (), $"extract-{Guid.NewGuid ()}"); + try { + Directory.CreateDirectory (tempExtractDir); + DownloadUtils.ExtractZipSafe (archivePath, tempExtractDir, cancellationToken); + + var extractedDir = Path.Combine (tempExtractDir, expectedTopDir); + if (!Directory.Exists (extractedDir)) { + var dirs = Directory.GetDirectories (tempExtractDir); + extractedDir = dirs.Length == 1 ? dirs[0] : tempExtractDir; + } + + MoveWithRollback (extractedDir, targetPath, logger); + logger (TraceLevel.Info, $"Installed to '{targetPath}'."); + } + finally { + TryDeleteDirectory (tempExtractDir, "temp extract dir", logger); + } + } + /// Deletes a backup created by MoveWithRollback. Call after validation succeeds. internal static void CommitMove (string targetPath, Action logger) { @@ -181,8 +208,67 @@ internal static string GetArchiveExtension () return OS.IsWindows ? ".zip" : ".tar.gz"; } + + /// + /// Sets Unix file permissions. Uses File.SetUnixFileMode on net7.0+ (see + /// https://learn.microsoft.com/dotnet/api/system.io.file.setunixfilemode), + /// falls back to libc P/Invoke on netstandard2.0. + /// + internal static bool Chmod (string path, int mode) + { + if (OS.IsWindows) + return true; // No-op on Windows + + try { +#if NET7_0_OR_GREATER + // Managed API avoids P/Invoke overhead and works on all .NET 7+ Unix platforms. + // See https://learn.microsoft.com/dotnet/api/system.io.file.setunixfilemode + if (!OperatingSystem.IsWindows ()) { + File.SetUnixFileMode (path, (UnixFileMode) mode); + return true; + } + return true; +#else + return chmod (path, mode) == 0; +#endif + } + catch { + return false; + } + } + + /// + /// Sets executable permissions on all files in the bin/ subdirectory. + /// Uses File.SetUnixFileMode on net7.0+, falls back to chmod process on netstandard2.0. + /// + internal static async Task SetExecutablePermissionsAsync (string directory, Action logger, CancellationToken cancellationToken = default) + { + var binDir = Path.Combine (directory, "bin"); + if (!Directory.Exists (binDir)) + return; + + foreach (var file in Directory.GetFiles (binDir)) { + cancellationToken.ThrowIfCancellationRequested (); + if (!Chmod (file, 0x1ED)) { // 0755 C# does not have octal literals + // Managed chmod failed, fall back to process + var psi = ProcessUtils.CreateProcessStartInfo ("chmod", "+x", file); + int exitCode = await ProcessUtils.StartProcess (psi, null, null, cancellationToken) + .ConfigureAwait (false); + if (exitCode != 0) { + logger (TraceLevel.Error, $"Failed to set executable permission on '{file}' (exit code {exitCode})."); + throw new InvalidOperationException ($"chmod failed for '{file}' with exit code {exitCode}."); + } + } + } + } + [DllImport ("libc", SetLastError=true)] static extern int rename (string old, string @new); + +#if !NET7_0_OR_GREATER + [DllImport ("libc", SetLastError = true)] + static extern int chmod (string pathname, int mode); +#endif } } diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Models/Sdk/ChecksumType.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Models/Sdk/ChecksumType.cs new file mode 100644 index 00000000000..411c1901b25 --- /dev/null +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Models/Sdk/ChecksumType.cs @@ -0,0 +1,10 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Xamarin.Android.Tools; + +public enum ChecksumType +{ + Sha1, + Sha256, +} diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Models/Sdk/SdkBootstrapPhase.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Models/Sdk/SdkBootstrapPhase.cs new file mode 100644 index 00000000000..1ad780faff7 --- /dev/null +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Models/Sdk/SdkBootstrapPhase.cs @@ -0,0 +1,21 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Xamarin.Android.Tools; + +/// +/// Phases of the SDK bootstrap operation. +/// +public enum SdkBootstrapPhase +{ + /// Reading the manifest feed. + ReadingManifest, + /// Downloading the command-line tools archive. + Downloading, + /// Verifying the downloaded archive checksum. + Verifying, + /// Extracting the archive. + Extracting, + /// Bootstrap completed successfully. + Complete +} diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Models/Sdk/SdkBootstrapProgress.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Models/Sdk/SdkBootstrapProgress.cs new file mode 100644 index 00000000000..6771ed9dc48 --- /dev/null +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Models/Sdk/SdkBootstrapProgress.cs @@ -0,0 +1,7 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Xamarin.Android.Tools; + +/// Progress information for SDK bootstrap operations. +public record SdkBootstrapProgress (SdkBootstrapPhase Phase, int PercentComplete = -1, string Message = ""); diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Models/Sdk/SdkLicense.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Models/Sdk/SdkLicense.cs new file mode 100644 index 00000000000..3ec5064cae9 --- /dev/null +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Models/Sdk/SdkLicense.cs @@ -0,0 +1,6 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Xamarin.Android.Tools; + +public record SdkLicense (string Id, string Text); diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Models/Sdk/SdkManifestComponent.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Models/Sdk/SdkManifestComponent.cs new file mode 100644 index 00000000000..04fece9131c --- /dev/null +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Models/Sdk/SdkManifestComponent.cs @@ -0,0 +1,18 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Xamarin.Android.Tools; + +internal record SdkManifestComponent +{ + public string ElementName { get; set; } = ""; + public string Revision { get; set; } = ""; + public string? Path { get; set; } + public string? FilesystemPath { get; set; } + public string? Description { get; set; } + public string? DownloadUrl { get; set; } + public long Size { get; set; } + public string? Checksum { get; set; } + public ChecksumType ChecksumType { get; set; } = ChecksumType.Sha1; + public bool IsObsolete { get; set; } +} diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Models/Sdk/SdkPackage.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Models/Sdk/SdkPackage.cs new file mode 100644 index 00000000000..d9beee796ab --- /dev/null +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Models/Sdk/SdkPackage.cs @@ -0,0 +1,8 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Xamarin.Android.Tools; + +/// Information about an SDK package as reported by the sdkmanager CLI. +public record SdkPackage (string Path, string? Version = null, string? Description = null, bool IsInstalled = false); + diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/ProcessUtils.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/ProcessUtils.cs index a89f95d83be..28113d7272e 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/ProcessUtils.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/ProcessUtils.cs @@ -24,13 +24,18 @@ static ProcessUtils () ExecutableFileExtensions = pathExts; } - public static async Task StartProcess (ProcessStartInfo psi, TextWriter? stdout, TextWriter? stderr, CancellationToken cancellationToken, Action? onStarted = null) + public static async Task StartProcess (ProcessStartInfo psi, TextWriter? stdout, TextWriter? stderr, CancellationToken cancellationToken, IDictionary? environmentVariables = null, Action? onStarted = null) { cancellationToken.ThrowIfCancellationRequested (); psi.UseShellExecute = false; psi.RedirectStandardOutput |= stdout != null; psi.RedirectStandardError |= stderr != null; + if (environmentVariables != null) { + foreach (var kvp in environmentVariables) + psi.EnvironmentVariables[kvp.Key] = kvp.Value; + } + var process = new Process { StartInfo = psi, EnableRaisingEvents = true, @@ -105,7 +110,7 @@ public static Task ExecuteToolAsync (string exe, Func { @@ -260,4 +265,3 @@ public static bool IsElevated () #endif } } - diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/SdkManager.Bootstrap.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/SdkManager.Bootstrap.cs new file mode 100644 index 00000000000..39286a9ac19 --- /dev/null +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/SdkManager.Bootstrap.cs @@ -0,0 +1,85 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Xamarin.Android.Tools; + +public partial class SdkManager +{ + static readonly IProgress NullProgress = new Progress (); + + /// + /// Downloads command-line tools from the manifest feed and extracts them to + /// <targetPath>/cmdline-tools/<version>/. + /// + public async Task BootstrapAsync (string targetPath, IProgress? progress = null, CancellationToken cancellationToken = default) + { + ThrowIfDisposed (); + if (string.IsNullOrEmpty (targetPath)) + throw new ArgumentNullException (nameof (targetPath)); + + progress ??= NullProgress; + + var cmdlineTools = await FindLatestCmdlineToolsAsync (progress, cancellationToken).ConfigureAwait (false); + var tempArchivePath = Path.GetTempFileName (); + + try { + await DownloadAndVerifyAsync (cmdlineTools, tempArchivePath, progress, cancellationToken).ConfigureAwait (false); + + var versionDir = Path.Combine (targetPath, "cmdline-tools", cmdlineTools.Revision); + Directory.CreateDirectory (Path.Combine (targetPath, "cmdline-tools")); + + progress.Report (new SdkBootstrapProgress (SdkBootstrapPhase.Extracting, Message: "Extracting cmdline-tools...")); + FileUtil.ExtractAndInstall (tempArchivePath, versionDir, "cmdline-tools", logger, cancellationToken); + + if (!OS.IsWindows) + await FileUtil.SetExecutablePermissionsAsync (versionDir, logger, cancellationToken).ConfigureAwait (false); + + AndroidSdkPath = targetPath; + progress.Report (new SdkBootstrapProgress (SdkBootstrapPhase.Complete, 100, "Bootstrap complete.")); + logger (TraceLevel.Info, "Android SDK bootstrap complete."); + } + finally { + FileUtil.TryDeleteFile (tempArchivePath, logger); + } + } + + async Task FindLatestCmdlineToolsAsync (IProgress progress, CancellationToken cancellationToken) + { + progress.Report (new SdkBootstrapProgress (SdkBootstrapPhase.ReadingManifest, Message: "Reading manifest feed...")); + logger (TraceLevel.Info, $"Reading manifest from {ManifestFeedUrl}..."); + + var components = await GetManifestComponentsAsync (cancellationToken).ConfigureAwait (false); + var cmdlineTools = components + .Where (c => string.Equals (c.ElementName, "cmdline-tools", StringComparison.OrdinalIgnoreCase) && !c.IsObsolete) + .OrderByDescending (c => Version.TryParse (c.Revision, out var v) ? v : new Version (0, 0)) + .FirstOrDefault (); + + if (cmdlineTools is null || string.IsNullOrEmpty (cmdlineTools.DownloadUrl)) + throw new InvalidOperationException ("Could not find command-line tools in the Android manifest feed."); + + logger (TraceLevel.Info, $"Found cmdline-tools {cmdlineTools.Revision}: {cmdlineTools.DownloadUrl}"); + return cmdlineTools; + } + + async Task DownloadAndVerifyAsync (SdkManifestComponent component, string archivePath, IProgress progress, CancellationToken cancellationToken) + { + progress.Report (new SdkBootstrapProgress (SdkBootstrapPhase.Downloading, Message: $"Downloading cmdline-tools {component.Revision}...")); + await DownloadFileAsync (component.DownloadUrl!, archivePath, component.Size, progress, cancellationToken).ConfigureAwait (false); + + if (!string.IsNullOrEmpty (component.Checksum)) { + progress.Report (new SdkBootstrapProgress (SdkBootstrapPhase.Verifying, Message: "Verifying checksum...")); + DownloadUtils.VerifyChecksum (archivePath, component.Checksum!, component.ChecksumType); + logger (TraceLevel.Info, "Checksum verification passed."); + } + else { + logger (TraceLevel.Warning, "No checksum available; skipping verification."); + } + } +} diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/SdkManager.Licenses.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/SdkManager.Licenses.cs new file mode 100644 index 00000000000..f1d053c225b --- /dev/null +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/SdkManager.Licenses.cs @@ -0,0 +1,214 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace Xamarin.Android.Tools; + +public partial class SdkManager +{ + /// + /// Accepts all SDK licenses using sdkmanager --licenses. + /// + /// Cancellation token. + public async Task AcceptLicensesAsync (CancellationToken cancellationToken = default) + { + var sdkManagerPath = RequireSdkManagerPath (); + logger (TraceLevel.Info, "Accepting SDK licenses..."); + var (exitCode, stdout, stderr) = await RunSdkManagerAsync ( + sdkManagerPath, new[] { "--licenses" }, acceptLicenses: true, cancellationToken: cancellationToken).ConfigureAwait (false); + + logger (TraceLevel.Verbose, $"sdkmanager --licenses exited with code {exitCode}."); + + if (exitCode != 0) { + string output = (stdout ?? string.Empty) + Environment.NewLine + (stderr ?? string.Empty); + + bool alreadyAccepted = + output.IndexOf ("all sdk package licenses accepted", StringComparison.OrdinalIgnoreCase) >= 0 || + output.IndexOf ("licenses have already been accepted", StringComparison.OrdinalIgnoreCase) >= 0 || + output.IndexOf ("no sdk licenses to review", StringComparison.OrdinalIgnoreCase) >= 0 || + output.IndexOf ("no licenses to review", StringComparison.OrdinalIgnoreCase) >= 0; + + if (!alreadyAccepted) { + logger (TraceLevel.Error, $"sdkmanager --licenses failed with exit code {exitCode}. stderr: {stderr}"); + throw new InvalidOperationException ($"sdkmanager --licenses failed with exit code {exitCode}. stderr: {stderr}"); + } + + logger (TraceLevel.Info, "SDK licenses are already accepted."); + return; + } + + logger (TraceLevel.Info, "License acceptance complete."); + } + + /// + /// Gets pending licenses that need to be accepted, along with their full text. + /// This allows IDEs and CLI tools to present licenses to the user before accepting. + /// + /// Cancellation token. + /// A list of pending licenses with their ID and full text content. + public async Task> GetPendingLicensesAsync (CancellationToken cancellationToken = default) + { + var sdkManagerPath = RequireSdkManagerPath (); + + logger (TraceLevel.Verbose, "Checking for pending licenses..."); + + var envVars = GetEnvironmentVariables (); + + // Run --licenses without auto-accept to get the license text + var psi = ProcessUtils.CreateProcessStartInfo (sdkManagerPath, "--licenses"); + psi.RedirectStandardInput = true; + + using var stdout = new StringWriter (); + using var stderr = new StringWriter (); + + // Send 'n' to decline all licenses so we just get the text + Action onStarted = process => { + Task.Run (async () => { + try { + while (!process.HasExited && !cancellationToken.IsCancellationRequested) { + process.StandardInput.WriteLine ("n"); + await Task.Delay (StdinPollDelayMs, cancellationToken).ConfigureAwait (false); + } + } + catch (Exception ex) { + // Process may have exited - expected behavior when process completes + logger (TraceLevel.Verbose, $"License check loop ended: {ex.GetType ().Name}"); + } + }, cancellationToken); + }; + + try { + await ProcessUtils.StartProcess (psi, stdout, stderr, cancellationToken, envVars, onStarted).ConfigureAwait (false); + } + catch (OperationCanceledException) { + throw; + } + catch (Exception ex) { + // sdkmanager may exit with non-zero when declining licenses - that's expected + logger (TraceLevel.Verbose, $"License check exited non-zero (expected): {ex.GetType ().Name}"); + } + + return ParseLicenseOutput (stdout.ToString ()); + } + + /// + /// Accepts specific licenses by ID. + /// + /// The license IDs to accept (e.g., "android-sdk-license"). + /// Cancellation token. + public async Task AcceptLicensesAsync (IEnumerable licenseIds, CancellationToken cancellationToken = default) + { + ThrowIfDisposed (); + if (licenseIds is null || !licenseIds.Any ()) + return; + + if (string.IsNullOrEmpty (AndroidSdkPath)) + throw new InvalidOperationException ("AndroidSdkPath must be set before accepting individual licenses."); + + // Accept licenses by writing the hash to the licenses directory + var licensesDir = Path.Combine (AndroidSdkPath!, "licenses"); + Directory.CreateDirectory (licensesDir); + + // Get pending licenses to find their hashes + var pendingLicenses = await GetPendingLicensesAsync (cancellationToken).ConfigureAwait (false); + var licenseIdSet = new HashSet (licenseIds, StringComparer.OrdinalIgnoreCase); + + foreach (var license in pendingLicenses) { + if (licenseIdSet.Contains (license.Id)) { + var licensePath = Path.Combine (licensesDir, license.Id); + // Compute hash of license text and write it + var hash = ComputeLicenseHash (license.Text); + File.WriteAllText (licensePath, $"\n{hash}"); + logger (TraceLevel.Info, $"Accepted license: {license.Id}"); + } + } + } + + /// + /// Checks whether SDK licenses have been accepted by checking the licenses directory. + /// + /// true if at least one license file exists; otherwise false. + public bool AreLicensesAccepted () + { + if (string.IsNullOrEmpty (AndroidSdkPath)) + return false; + + var licensesPath = Path.Combine (AndroidSdkPath, "licenses"); + if (!Directory.Exists (licensesPath)) + return false; + + return Directory.EnumerateFiles (licensesPath).Any (); + } + + /// + /// Parses the output of sdkmanager --licenses to extract license information. + /// + internal static IReadOnlyList ParseLicenseOutput (string output) + { + var licenses = new List (); + var lines = output.Split (new[] { '\n' }, StringSplitOptions.None); + + string? currentLicenseId = null; + var currentLicenseText = new StringBuilder (); + bool inLicenseText = false; + + foreach (var rawLine in lines) { + var line = rawLine.TrimEnd ('\r'); + + // License header: "License android-sdk-license:" + if (line.StartsWith ("License ", StringComparison.OrdinalIgnoreCase) && line.TrimEnd ().EndsWith (":")) { + // Save previous license if any + if (currentLicenseId is not null && currentLicenseText.Length > 0) { + licenses.Add (new SdkLicense (currentLicenseId, currentLicenseText.ToString ().Trim ())); + } + + var trimmedLine = line.TrimEnd (); + currentLicenseId = trimmedLine.Substring (8, trimmedLine.Length - 9).Trim (); + currentLicenseText.Clear (); + inLicenseText = true; + continue; + } + + // End of license text when we see the accept prompt + if (line.Contains ("Accept?") || line.Contains ("(y/N)")) { + if (currentLicenseId is not null && currentLicenseText.Length > 0) { + licenses.Add (new SdkLicense (currentLicenseId, currentLicenseText.ToString ().Trim ())); + } + currentLicenseId = null; + currentLicenseText.Clear (); + inLicenseText = false; + continue; + } + + // Accumulate license text + if (inLicenseText && currentLicenseId is not null) { + // Skip separator lines + if (!line.TrimStart ().StartsWith ("-------", StringComparison.Ordinal)) { + currentLicenseText.AppendLine (line); + } + } + } + + // Add last license if not yet added + if (currentLicenseId is not null && currentLicenseText.Length > 0) { + licenses.Add (new SdkLicense (currentLicenseId, currentLicenseText.ToString ().Trim ())); + } + + return licenses.AsReadOnly (); + } + + static string ComputeLicenseHash (string licenseText) + { + var bytes = Encoding.UTF8.GetBytes (licenseText.Replace ("\r\n", "\n").Trim ()); + return DownloadUtils.ComputeHashString (ChecksumType.Sha1, bytes); + } + +} diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/SdkManager.Manifest.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/SdkManager.Manifest.cs new file mode 100644 index 00000000000..fd96cf8bc64 --- /dev/null +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/SdkManager.Manifest.cs @@ -0,0 +1,144 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Runtime.InteropServices; +using System.Threading; +using System.Threading.Tasks; +using System.Xml; + +namespace Xamarin.Android.Tools; + +public partial class SdkManager +{ + // --- Manifest Parsing --- + + /// + /// Downloads and parses the Android manifest feed to discover available components. + /// + /// Cancellation token. + /// A list of manifest components available for the current platform. + internal async Task> GetManifestComponentsAsync (CancellationToken cancellationToken = default) + { + ThrowIfDisposed (); + logger (TraceLevel.Info, $"Downloading manifest from {ManifestFeedUrl}..."); + // netstandard2.0 GetStringAsync has no CancellationToken overload; use GetAsync instead + using var response = await httpClient.GetAsync (ManifestFeedUrl, cancellationToken).ConfigureAwait (false); + response.EnsureSuccessStatusCode (); + var xml = await DownloadUtils.ReadAsStringAsync (response.Content, cancellationToken).ConfigureAwait (false); + return ParseManifest (xml); + } + + /// + /// Parses the Android manifest XML and returns components for the current platform. + /// Uses XmlReader for better performance than XDocument/XElement. + /// + internal IReadOnlyList ParseManifest (string xml) + { + var hostOs = GetManifestHostOs (); + var hostArch = GetManifestHostArch (); + var components = new List (); + + using var stringReader = new StringReader (xml); + using var reader = XmlReader.Create (stringReader, new XmlReaderSettings { IgnoreWhitespace = true }); + + while (reader.Read ()) { + if (reader.NodeType != XmlNodeType.Element) + continue; + + // Skip root element + if (reader.Depth == 0) + continue; + + var elementName = reader.LocalName; + var revision = reader.GetAttribute ("revision"); + if (string.IsNullOrEmpty (revision)) + continue; + + var component = new SdkManifestComponent { + ElementName = elementName, + Revision = revision!, + Path = reader.GetAttribute ("path"), + FilesystemPath = reader.GetAttribute ("filesystem-path"), + Description = reader.GetAttribute ("description"), + IsObsolete = string.Equals (reader.GetAttribute ("obsolete"), "True", StringComparison.OrdinalIgnoreCase), + }; + + // Read child elements to find matching URL + if (!reader.IsEmptyElement) { + var componentDepth = reader.Depth; + while (reader.Read () && reader.Depth > componentDepth) { + if (reader.NodeType == XmlNodeType.Element && reader.LocalName == "urls") { + var urlsDepth = reader.Depth; + while (reader.Read () && reader.Depth > urlsDepth) { + if (reader.NodeType == XmlNodeType.Element && reader.LocalName == "url") { + var urlHostOs = reader.GetAttribute ("host-os"); + var urlHostArch = reader.GetAttribute ("host-arch"); + + if (!MatchesPlatform (urlHostOs, hostOs)) + continue; + + if (!string.IsNullOrEmpty (urlHostArch) && !string.Equals (urlHostArch, hostArch, StringComparison.OrdinalIgnoreCase)) + continue; + + var checksumTypeStr = reader.GetAttribute ("checksum-type"); + if (string.Equals (checksumTypeStr, "sha-256", StringComparison.OrdinalIgnoreCase)) + component.ChecksumType = ChecksumType.Sha256; + else + component.ChecksumType = ChecksumType.Sha1; + component.Checksum = reader.GetAttribute ("checksum"); + + var sizeStr = reader.GetAttribute ("size"); + if (long.TryParse (sizeStr, out var size)) + component.Size = size; + + // Read the URL text content + component.DownloadUrl = reader.ReadElementContentAsString ()?.Trim (); + break; + } + } + } + } + } + + if (!string.IsNullOrEmpty (component.DownloadUrl)) + components.Add (component); + } + + logger (TraceLevel.Verbose, $"Parsed {components.Count} components from manifest."); + return components.AsReadOnly (); + } + + static bool MatchesPlatform (string? urlHostOs, string hostOs) + { + if (string.IsNullOrEmpty (urlHostOs)) + return true; // No filter means any platform + return string.Equals (urlHostOs, hostOs, StringComparison.OrdinalIgnoreCase); + } + + static string GetManifestHostOs () + { + if (OS.IsWindows) return "windows"; + if (OS.IsMac) return "macosx"; + if (OS.IsLinux) return "linux"; + throw new PlatformNotSupportedException ($"Unsupported operating system for Android SDK manifest."); + } + + static string GetManifestHostArch () + { + var arch = RuntimeInformation.OSArchitecture; + switch (arch) { + case Architecture.Arm64: + return "aarch64"; + case Architecture.X64: + return "x64"; + case Architecture.X86: + return "x86"; + default: + throw new PlatformNotSupportedException ($"Unsupported architecture '{arch}' for Android SDK manifest."); + } + } +} diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/SdkManager.Packages.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/SdkManager.Packages.cs new file mode 100644 index 00000000000..0a548d985f1 --- /dev/null +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/SdkManager.Packages.cs @@ -0,0 +1,142 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Xamarin.Android.Tools; + +public partial class SdkManager +{ + public string? FindSdkManagerPath () + { + if (string.IsNullOrEmpty (AndroidSdkPath)) + return null; + + var ext = OS.IsWindows ? ".bat" : string.Empty; + var cmdlineToolsDir = Path.Combine (AndroidSdkPath, "cmdline-tools"); + + if (Directory.Exists (cmdlineToolsDir)) { + try { + // Versioned dirs sorted descending, then "latest" as fallback + var searchDirs = Directory.GetDirectories (cmdlineToolsDir) + .Select (Path.GetFileName) + .Where (n => n != "latest" && !string.IsNullOrEmpty (n)) + .OrderByDescending (n => Version.TryParse (n, out var v) ? v : new Version (0, 0)) + .Append ("latest"); + + foreach (var dir in searchDirs) { + var toolPath = Path.Combine (cmdlineToolsDir, dir!, "bin", "sdkmanager" + ext); + if (File.Exists (toolPath)) + return toolPath; + } + } catch (Exception ex) { + logger (TraceLevel.Verbose, $"Error enumerating cmdline-tools directories: {ex.Message}"); + } + } + + // Legacy fallback: tools/bin/sdkmanager + var legacyPath = Path.Combine (AndroidSdkPath, "tools", "bin", "sdkmanager" + ext); + return File.Exists (legacyPath) ? legacyPath : null; + } + + public async Task<(IReadOnlyList Installed, IReadOnlyList Available)> ListAsync (CancellationToken cancellationToken = default) + { + var sdkManagerPath = RequireSdkManagerPath (); + logger (TraceLevel.Info, "Running sdkmanager --list..."); + var (exitCode, stdout, stderr) = await RunSdkManagerAsync (sdkManagerPath, new[] { "--list" }, cancellationToken: cancellationToken).ConfigureAwait (false); + ThrowOnSdkManagerFailure (exitCode, "sdkmanager --list", stderr); + return ParseSdkManagerList (stdout); + } + + public async Task InstallAsync (IEnumerable packages, bool acceptLicenses = true, CancellationToken cancellationToken = default) + { + var packageArray = ValidatePackages (packages); + var sdkManagerPath = RequireSdkManagerPath (); + logger (TraceLevel.Info, $"Installing packages: {string.Join (", ", packageArray)}"); + + // Install one at a time to work around sdkmanager shell script quoting issues + foreach (var package in packageArray) { + logger (TraceLevel.Info, $"Installing package: {package}"); + var (exitCode, _, stderr) = await RunSdkManagerAsync ( + sdkManagerPath, new[] { package }, acceptLicenses, cancellationToken).ConfigureAwait (false); + ThrowOnSdkManagerFailure (exitCode, $"Package installation ({package})", stderr); + } + logger (TraceLevel.Info, "Packages installed successfully."); + } + + public async Task UninstallAsync (IEnumerable packages, CancellationToken cancellationToken = default) + { + var packageArray = ValidatePackages (packages); + var sdkManagerPath = RequireSdkManagerPath (); + logger (TraceLevel.Info, $"Uninstalling packages: {string.Join (", ", packageArray)}"); + + var args = new[] { "--uninstall" }.Concat (packageArray).ToArray (); + var (exitCode, _, stderr) = await RunSdkManagerAsync ( + sdkManagerPath, args, cancellationToken: cancellationToken).ConfigureAwait (false); + ThrowOnSdkManagerFailure (exitCode, "Package uninstall", stderr); + logger (TraceLevel.Info, "Packages uninstalled successfully."); + } + + public async Task UpdateAsync (CancellationToken cancellationToken = default) + { + var sdkManagerPath = RequireSdkManagerPath (); + logger (TraceLevel.Info, "Updating all installed packages..."); + var (exitCode, _, stderr) = await RunSdkManagerAsync ( + sdkManagerPath, new[] { "--update" }, acceptLicenses: true, cancellationToken: cancellationToken).ConfigureAwait (false); + ThrowOnSdkManagerFailure (exitCode, "Package update", stderr); + logger (TraceLevel.Info, "All packages updated successfully."); + } + + static string[] ValidatePackages (IEnumerable packages) + { + if (packages is null) + throw new ArgumentException ("At least one package must be specified.", nameof (packages)); + + var array = packages.ToArray (); + if (array.Length == 0) + throw new ArgumentException ("At least one package must be specified.", nameof (packages)); + + return array; + } + + internal static (IReadOnlyList Installed, IReadOnlyList Available) ParseSdkManagerList (string output) + { + var installed = new List (); + var available = new List (); + List? target = null; + + foreach (var line in output.Split (new[] { '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries)) { + var trimmed = line.Trim (); + + if (trimmed.Contains ("Installed packages:")) { target = installed; continue; } + if (trimmed.Contains ("Available Packages:")) { target = available; continue; } + if (trimmed.Contains ("Available Updates:")) { target = null; continue; } + + if (target is null || trimmed.StartsWith ("Path", StringComparison.Ordinal) || trimmed.StartsWith ("---", StringComparison.Ordinal)) + continue; + + var parts = trimmed.Split ('|'); + if (parts.Length < 2) + continue; + + var path = parts[0].Trim (); + if (string.IsNullOrEmpty (path)) + continue; + + target.Add (new SdkPackage ( + path, + Version: parts[1].Trim (), + Description: parts.Length > 2 ? parts[2].Trim () : null, + IsInstalled: target == installed + )); + } + + return (installed.AsReadOnly (), available.AsReadOnly ()); + } +} diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/SdkManager.Process.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/SdkManager.Process.cs new file mode 100644 index 00000000000..96d70ef87f4 --- /dev/null +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/SdkManager.Process.cs @@ -0,0 +1,110 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +namespace Xamarin.Android.Tools; +public partial class SdkManager +{ + async Task<(int ExitCode, string Stdout, string Stderr)> RunSdkManagerAsync ( + string sdkManagerPath, string[] arguments, bool acceptLicenses = false, CancellationToken cancellationToken = default) + { + var argumentsStr = string.Join (" ", arguments); + + // On macOS/Linux the sdkmanager shell script uses 'save()'/'eval' which + // concatenates individually-quoted arguments. Pass as a single Arguments + // string so the script receives them correctly. + var psi = OS.IsWindows + ? ProcessUtils.CreateProcessStartInfo (sdkManagerPath, arguments) + : new ProcessStartInfo { + FileName = sdkManagerPath, + Arguments = argumentsStr, + UseShellExecute = false, + CreateNoWindow = true, + }; + psi.RedirectStandardInput = acceptLicenses; + + var envVars = GetEnvironmentVariables (); + + using var stdout = new StringWriter (); + using var stderr = new StringWriter (); + + Action? onStarted = null; + if (acceptLicenses) { + onStarted = process => { + // Feed "y\n" continuously for license prompts + Task.Run (async () => { + try { + while (!process.HasExited && !cancellationToken.IsCancellationRequested) { + process.StandardInput.WriteLine ("y"); + await Task.Delay (StdinPollDelayMs, cancellationToken).ConfigureAwait (false); + } + } + catch (Exception ex) { + // Process may have exited or cancellation requested - expected behavior + logger (TraceLevel.Verbose, $"Auto-accept loop ended: {ex.GetType ().Name}"); + } + }, cancellationToken); + }; + } + + logger (TraceLevel.Verbose, $"Running: {sdkManagerPath} {argumentsStr}"); + int exitCode; + try { + exitCode = await ProcessUtils.StartProcess (psi, stdout, stderr, cancellationToken, envVars, onStarted).ConfigureAwait (false); + } + catch (OperationCanceledException) { + throw; + } + catch (Exception ex) { + logger (TraceLevel.Error, $"Failed to run sdkmanager: {ex.Message}"); + logger (TraceLevel.Verbose, ex.ToString ()); + throw; + } + + var stdoutStr = stdout.ToString (); + var stderrStr = stderr.ToString (); + + if (exitCode != 0) { + logger (TraceLevel.Warning, $"sdkmanager exited with code {exitCode}"); + logger (TraceLevel.Verbose, $"stdout: {stdoutStr}"); + logger (TraceLevel.Verbose, $"stderr: {stderrStr}"); + } + + return (exitCode, stdoutStr, stderrStr); + } + + async Task DownloadFileAsync (string url, string destinationPath, long expectedSize, IProgress progress, CancellationToken cancellationToken) + { + logger (TraceLevel.Info, $"Downloading {url}..."); + + var downloadProgress = new Progress<(double percent, string message)> (p => { + progress.Report (new SdkBootstrapProgress (SdkBootstrapPhase.Downloading, (int) p.percent, p.message)); + }); + + await DownloadUtils.DownloadFileAsync (httpClient, url, destinationPath, expectedSize, downloadProgress, cancellationToken).ConfigureAwait (false); + logger (TraceLevel.Info, $"Download complete: {destinationPath}"); + } + + Dictionary GetEnvironmentVariables () + { + var env = new Dictionary { + ["ANDROID_USER_HOME"] = Path.Combine ( + Environment.GetFolderPath (Environment.SpecialFolder.UserProfile), ".android") + }; + + if (!string.IsNullOrEmpty (AndroidSdkPath)) + env[EnvironmentVariableNames.AndroidHome] = AndroidSdkPath!; + + if (!string.IsNullOrEmpty (JavaSdkPath)) + env[EnvironmentVariableNames.JavaHome] = JavaSdkPath!; + + return env; + } + +} diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/SdkManager.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/SdkManager.cs new file mode 100644 index 00000000000..cf8e3a345cb --- /dev/null +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/SdkManager.cs @@ -0,0 +1,89 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Diagnostics; +using System.Net.Http; + +namespace Xamarin.Android.Tools; +/// +/// Provides Android SDK bootstrap and management capabilities using the sdkmanager CLI. +/// +/// +/// +/// Downloads the Android command-line tools from the Xamarin Android manifest feed, +/// extracts them to cmdline-tools/<version>/, then uses the included sdkmanager +/// to install, uninstall, list, and update SDK packages. +/// +/// +/// The manifest feed URL defaults to https://aka.ms/AndroidManifestFeed/d18-0 +/// but can be configured via the property. +/// +/// +public partial class SdkManager : IDisposable +{ + /// Default manifest feed URL (Xamarin/Microsoft). + public const string DefaultManifestFeedUrl = "https://aka.ms/AndroidManifestFeed/d18-0"; + + const int StdinPollDelayMs = 500; + + static readonly HttpClient httpClient = new HttpClient (); + readonly Action logger; + bool disposed; + + /// + /// Gets or sets the manifest feed URL used to discover command-line tools. + /// Defaults to . + /// + public string ManifestFeedUrl { get; set; } = DefaultManifestFeedUrl; + + /// + /// Gets or sets the Android SDK root path. Used to locate and invoke sdkmanager. + /// + public string? AndroidSdkPath { get; set; } + + /// + /// Gets or sets the Java SDK (JDK) home path. Set as JAVA_HOME when invoking sdkmanager. + /// + public string? JavaSdkPath { get; set; } + + /// + /// Creates a new instance. + /// + /// Optional logger callback. Defaults to . + public SdkManager (Action? logger = null) + { + this.logger = logger ?? AndroidSdkInfo.DefaultConsoleLogger; + } + + /// + /// Disposes the . + /// + public void Dispose () + { + if (disposed) + return; + disposed = true; + } + + void ThrowIfDisposed () + { + if (disposed) + throw new ObjectDisposedException (nameof (SdkManager)); + } + + string RequireSdkManagerPath () + { + ThrowIfDisposed (); + return FindSdkManagerPath () + ?? throw new InvalidOperationException ("sdkmanager not found. Run BootstrapAsync first."); + } + + void ThrowOnSdkManagerFailure (int exitCode, string operation, string stderr) + { + if (exitCode == 0) + return; + logger (TraceLevel.Error, $"{operation} failed (exit code {exitCode}): {stderr}"); + throw new InvalidOperationException ($"{operation} failed: {stderr}"); + } +} diff --git a/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/SdkManagerTests.cs b/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/SdkManagerTests.cs new file mode 100644 index 00000000000..ab931c07a4f --- /dev/null +++ b/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/SdkManagerTests.cs @@ -0,0 +1,515 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +using NUnit.Framework; + +namespace Xamarin.Android.Tools.Tests; +[TestFixture] +public class SdkManagerTests +{ + SdkManager manager; + + [SetUp] + public void SetUp () + { + manager = new SdkManager (logger: (level, message) => { + TestContext.WriteLine ($"[{level}] {message}"); + }); + } + + [TearDown] + public void TearDown () + { + manager?.Dispose (); + manager = null; + } + + [Test] + public void ParseManifest_CmdlineTools_ReturnsComponents () + { + var xml = @" + + + + https://dl.google.com/android/repository/commandlinetools-linux-13114758_latest.zip + https://dl.google.com/android/repository/commandlinetools-mac-13114758_latest.zip + https://dl.google.com/android/repository/commandlinetools-win-13114758_latest.zip + + + + + https://dl.google.com/android/repository/platform-tools_r36.0.0-linux.zip + https://dl.google.com/android/repository/platform-tools_r36.0.0-darwin.zip + https://dl.google.com/android/repository/platform-tools_r36.0.0-win.zip + + +"; + + var components = manager.ParseManifest (xml); + + Assert.IsNotNull (components); + Assert.IsTrue (components.Count >= 2, $"Expected at least 2 components, got {components.Count}"); + + var cmdline = components.FirstOrDefault (c => c.ElementName == "cmdline-tools"); + Assert.IsNotNull (cmdline, "Should find cmdline-tools component"); + Assert.AreEqual ("19.0", cmdline!.Revision); + Assert.IsNotEmpty (cmdline.DownloadUrl!); + Assert.IsNotEmpty (cmdline.Checksum!); + Assert.AreEqual (ChecksumType.Sha1, cmdline.ChecksumType); + Assert.Greater (cmdline.Size, 0); + + var platformTools = components.FirstOrDefault (c => c.ElementName == "platform-tools"); + Assert.IsNotNull (platformTools, "Should find platform-tools component"); + Assert.AreEqual ("36.0.0", platformTools!.Revision); + } + + [Test] + public void ParseManifest_ObsoleteComponents_AreIncluded () + { + var xml = @" + + + + https://example.com/old.zip + https://example.com/old-mac.zip + https://example.com/old-win.zip + + +"; + + var components = manager.ParseManifest (xml); + var obsolete = components.FirstOrDefault (c => c.Revision == "9.0"); + Assert.IsNotNull (obsolete); + Assert.IsTrue (obsolete!.IsObsolete); + } + + [Test] + public void ParseManifest_EmptyXml_ReturnsEmpty () + { + var xml = @""; + var components = manager.ParseManifest (xml); + Assert.IsNotNull (components); + Assert.AreEqual (0, components.Count); + } + + [Test] + public void ParseManifest_MultipleVersions_AllReturned () + { + var xml = @" + + + + https://example.com/19.zip + https://example.com/19-mac.zip + https://example.com/19-win.zip + + + + + https://example.com/17.zip + https://example.com/17-mac.zip + https://example.com/17-win.zip + + +"; + + var components = manager.ParseManifest (xml); + var cmdlineTools = components.Where (c => c.ElementName == "cmdline-tools").ToList (); + Assert.AreEqual (2, cmdlineTools.Count, "Should find both cmdline-tools versions"); + } + + [Test] + public void ParseManifest_JdkElements_Parsed () + { + var xml = @" + + + + https://aka.ms/download-jdk/microsoft-jdk-21.0.9-windows-x64.zip + https://aka.ms/download-jdk/microsoft-jdk-21.0.9-macOS-x64.tar.gz + https://aka.ms/download-jdk/microsoft-jdk-21.0.9-macOS-aarch64.tar.gz + https://aka.ms/download-jdk/microsoft-jdk-21.0.9-linux-x64.tar.gz + + +"; + + var components = manager.ParseManifest (xml); + var jdk = components.FirstOrDefault (c => c.ElementName == "jdk"); + Assert.IsNotNull (jdk, "Should find jdk component"); + Assert.AreEqual ("21.0.9", jdk!.Revision); + Assert.IsNotEmpty (jdk.DownloadUrl!); + } + + [Test] + public void ParseSdkManagerList_ParsesInstalledAndAvailable () + { + var output = @"Installed packages: + Path | Version | Description | Location + ------- | ------- | ------- | ------- + build-tools;35.0.0 | 35.0.0 | Android SDK Build-Tools 35 | build-tools/35.0.0 + emulator | 35.3.10 | Android Emulator | emulator + platform-tools | 36.0.0 | Android SDK Platform-Tools | platform-tools + +Available Packages: + Path | Version | Description + ------- | ------- | ------- + build-tools;36.0.0 | 36.0.0 | Android SDK Build-Tools 36 + platforms;android-35 | 5 | Android SDK Platform 35 + system-images;android-35;google_apis;arm64-v8a | 14 | Google APIs ARM 64 v8a System Image + +Available Updates: + Path | Installed | Available + platform-tools | 35.0.2 | 36.0.0 +"; + + var (installed, available) = SdkManager.ParseSdkManagerList (output); + + Assert.AreEqual (3, installed.Count, "Should have 3 installed packages"); + Assert.AreEqual (3, available.Count, "Should have 3 available packages"); + + var platformTools = installed.FirstOrDefault (p => p.Path == "platform-tools"); + Assert.IsNotNull (platformTools); + Assert.AreEqual ("36.0.0", platformTools!.Version); + Assert.IsTrue (platformTools.IsInstalled); + + var buildTools36 = available.FirstOrDefault (p => p.Path == "build-tools;36.0.0"); + Assert.IsNotNull (buildTools36); + Assert.AreEqual ("36.0.0", buildTools36!.Version); + Assert.IsFalse (buildTools36.IsInstalled); + } + + [Test] + public void ParseSdkManagerList_EmptyOutput_ReturnsEmpty () + { + var (installed, available) = SdkManager.ParseSdkManagerList (""); + Assert.AreEqual (0, installed.Count); + Assert.AreEqual (0, available.Count); + } + + [Test] + public void ParseSdkManagerList_OnlyInstalledSection () + { + var output = @"Installed packages: + Path | Version | Description + ------- | ------- | ------- + platform-tools | 36.0.0 | Android SDK Platform-Tools +"; + + var (installed, available) = SdkManager.ParseSdkManagerList (output); + Assert.AreEqual (1, installed.Count); + Assert.AreEqual (0, available.Count); + Assert.AreEqual ("platform-tools", installed[0].Path); + } + + [Test] + public void FindSdkManagerPath_NullSdkPath_ReturnsNull () + { + manager.AndroidSdkPath = null; + Assert.IsNull (manager.FindSdkManagerPath ()); + } + + [Test] + public void FindSdkManagerPath_CmdlineToolsLatest_Found () + { + var sdkDir = Path.Combine (Path.GetTempPath (), $"sdk-test-{Guid.NewGuid ()}"); + try { + var binDir = Path.Combine (sdkDir, "cmdline-tools", "latest", "bin"); + Directory.CreateDirectory (binDir); + + var sdkManagerName = OS.IsWindows ? "sdkmanager.bat" : "sdkmanager"; + File.WriteAllText (Path.Combine (binDir, sdkManagerName), "#!/bin/sh\necho test"); + + manager.AndroidSdkPath = sdkDir; + var result = manager.FindSdkManagerPath (); + + Assert.IsNotNull (result, "Should find sdkmanager in cmdline-tools/latest/bin"); + Assert.That (result, Does.Contain ("sdkmanager")); + } + finally { + if (Directory.Exists (sdkDir)) + Directory.Delete (sdkDir, recursive: true); + } + } + + [Test] + public void FindSdkManagerPath_VersionedDir_Found () + { + var sdkDir = Path.Combine (Path.GetTempPath (), $"sdk-test-{Guid.NewGuid ()}"); + try { + var binDir = Path.Combine (sdkDir, "cmdline-tools", "12.0", "bin"); + Directory.CreateDirectory (binDir); + + var sdkManagerName = OS.IsWindows ? "sdkmanager.bat" : "sdkmanager"; + File.WriteAllText (Path.Combine (binDir, sdkManagerName), "#!/bin/sh\necho test"); + + manager.AndroidSdkPath = sdkDir; + var result = manager.FindSdkManagerPath (); + + Assert.IsNotNull (result, "Should find sdkmanager in versioned dir"); + } + finally { + if (Directory.Exists (sdkDir)) + Directory.Delete (sdkDir, recursive: true); + } + } + + [Test] + public void FindSdkManagerPath_LegacyToolsDir_Found () + { + var sdkDir = Path.Combine (Path.GetTempPath (), $"sdk-test-{Guid.NewGuid ()}"); + try { + var binDir = Path.Combine (sdkDir, "tools", "bin"); + Directory.CreateDirectory (binDir); + + var sdkManagerName = OS.IsWindows ? "sdkmanager.bat" : "sdkmanager"; + File.WriteAllText (Path.Combine (binDir, sdkManagerName), "#!/bin/sh\necho test"); + + manager.AndroidSdkPath = sdkDir; + var result = manager.FindSdkManagerPath (); + + Assert.IsNotNull (result, "Should find sdkmanager in legacy tools/bin"); + } + finally { + if (Directory.Exists (sdkDir)) + Directory.Delete (sdkDir, recursive: true); + } + } + + [Test] + public void FindSdkManagerPath_NoSdkManager_ReturnsNull () + { + var sdkDir = Path.Combine (Path.GetTempPath (), $"sdk-test-{Guid.NewGuid ()}"); + try { + Directory.CreateDirectory (sdkDir); + manager.AndroidSdkPath = sdkDir; + Assert.IsNull (manager.FindSdkManagerPath ()); + } + finally { + if (Directory.Exists (sdkDir)) + Directory.Delete (sdkDir, recursive: true); + } + } + + [Test] + public void DefaultManifestFeedUrl_IsSet () + { + Assert.AreEqual ("https://aka.ms/AndroidManifestFeed/d18-0", SdkManager.DefaultManifestFeedUrl); + Assert.AreEqual (SdkManager.DefaultManifestFeedUrl, manager.ManifestFeedUrl); + } + + [Test] + public void ManifestFeedUrl_IsConfigurable () + { + manager.ManifestFeedUrl = "https://example.com/manifest.xml"; + Assert.AreEqual ("https://example.com/manifest.xml", manager.ManifestFeedUrl); + } + + [Test] + public void Constructor_DefaultLogger_DoesNotThrow () + { + var defaultManager = new SdkManager (); + Assert.IsNotNull (defaultManager); + } + + // --- AreLicensesAccepted --- + + [Test] + public void AreLicensesAccepted_NullSdkPath_ReturnsFalse () + { + manager.AndroidSdkPath = null; + Assert.IsFalse (manager.AreLicensesAccepted ()); + } + + [Test] + public void AreLicensesAccepted_NoLicensesDir_ReturnsFalse () + { + var sdkDir = Path.Combine (Path.GetTempPath (), $"sdk-test-{Guid.NewGuid ()}"); + try { + Directory.CreateDirectory (sdkDir); + manager.AndroidSdkPath = sdkDir; + Assert.IsFalse (manager.AreLicensesAccepted ()); + } + finally { + if (Directory.Exists (sdkDir)) + Directory.Delete (sdkDir, recursive: true); + } + } + + [Test] + public void AreLicensesAccepted_WithLicenseFiles_ReturnsTrue () + { + var sdkDir = Path.Combine (Path.GetTempPath (), $"sdk-test-{Guid.NewGuid ()}"); + try { + var licensesDir = Path.Combine (sdkDir, "licenses"); + Directory.CreateDirectory (licensesDir); + File.WriteAllText (Path.Combine (licensesDir, "android-sdk-license"), "abc123"); + + manager.AndroidSdkPath = sdkDir; + Assert.IsTrue (manager.AreLicensesAccepted ()); + } + finally { + if (Directory.Exists (sdkDir)) + Directory.Delete (sdkDir, recursive: true); + } + } + + [Test] + public async Task GetManifestComponentsAsync_ReturnsComponents () + { + IReadOnlyList components; + try { + components = await manager.GetManifestComponentsAsync (); + } + catch (Exception ex) when (ex is System.Net.Http.HttpRequestException || ex is TaskCanceledException) { + Assert.Ignore ($"Network unavailable: {ex.Message}"); + return; + } + + Assert.IsNotNull (components); + if (components.Count == 0) { + Assert.Ignore ("No components returned."); + return; + } + + // Should find cmdline-tools + var cmdline = components.FirstOrDefault (c => c.ElementName == "cmdline-tools"); + Assert.IsNotNull (cmdline, "Manifest should contain cmdline-tools"); + Assert.IsNotEmpty (cmdline!.DownloadUrl!); + Assert.IsNotEmpty (cmdline.Checksum!); + + // Should find platform-tools + var platformTools = components.FirstOrDefault (c => c.ElementName == "platform-tools"); + Assert.IsNotNull (platformTools, "Manifest should contain platform-tools"); + } + + [Test] + public async Task BootstrapAsync_NullPath_Throws () + { + Assert.ThrowsAsync ( + async () => await manager.BootstrapAsync (null!)); + } + + [Test] + public void InstallAsync_NoSdkManager_Throws () + { + manager.AndroidSdkPath = Path.Combine (Path.GetTempPath (), "nonexistent"); + Assert.ThrowsAsync ( + async () => await manager.InstallAsync (new[] { "platform-tools" })); + } + + [Test] + public void InstallAsync_EmptyPackages_Throws () + { + Assert.ThrowsAsync ( + async () => await manager.InstallAsync (new string[0])); + } + + [Test] + public void InstallAsync_NullPackages_Throws () + { + Assert.ThrowsAsync ( + async () => await manager.InstallAsync (null!)); + } + + [Test] + public void UninstallAsync_NoSdkManager_Throws () + { + manager.AndroidSdkPath = Path.Combine (Path.GetTempPath (), "nonexistent"); + Assert.ThrowsAsync ( + async () => await manager.UninstallAsync (new[] { "platform-tools" })); + } + + [Test] + public void UninstallAsync_EmptyPackages_Throws () + { + Assert.ThrowsAsync ( + async () => await manager.UninstallAsync (new string[0])); + } + + [Test] + public void ListAsync_NoSdkManager_Throws () + { + manager.AndroidSdkPath = Path.Combine (Path.GetTempPath (), "nonexistent"); + Assert.ThrowsAsync ( + async () => await manager.ListAsync ()); + } + + [Test] + public void UpdateAsync_NoSdkManager_Throws () + { + manager.AndroidSdkPath = Path.Combine (Path.GetTempPath (), "nonexistent"); + Assert.ThrowsAsync ( + async () => await manager.UpdateAsync ()); + } + + [Test] + public void AcceptLicensesAsync_NoSdkManager_Throws () + { + manager.AndroidSdkPath = Path.Combine (Path.GetTempPath (), "nonexistent"); + Assert.ThrowsAsync ( + async () => await manager.AcceptLicensesAsync ()); + } + + // --- License Parsing --- + + [Test] + public void ParseLicenseOutput_SingleLicense_Parsed () + { + var output = @" +License android-sdk-license: +--------------------------------------- +Terms and Conditions + +This is the license text. + +--------------------------------------- +Accept? (y/N): "; + + var licenses = SdkManager.ParseLicenseOutput (output); + + Assert.AreEqual (1, licenses.Count, "Should parse one license"); + Assert.AreEqual ("android-sdk-license", licenses[0].Id); + Assert.That (licenses[0].Text, Does.Contain ("Terms and Conditions")); + Assert.That (licenses[0].Text, Does.Contain ("This is the license text")); + } + + [Test] + public void ParseLicenseOutput_MultipleLicenses_Parsed () + { + var output = @" +License android-sdk-license: +--------------------------------------- +SDK License Text +--------------------------------------- +Accept? (y/N): n +License android-sdk-preview-license: +--------------------------------------- +Preview License Text +--------------------------------------- +Accept? (y/N): "; + + var licenses = SdkManager.ParseLicenseOutput (output); + + Assert.AreEqual (2, licenses.Count, "Should parse two licenses"); + Assert.AreEqual ("android-sdk-license", licenses[0].Id); + Assert.AreEqual ("android-sdk-preview-license", licenses[1].Id); + } + + [Test] + public void ParseLicenseOutput_NoLicenses_ReturnsEmpty () + { + var output = "All SDK package licenses accepted."; + + var licenses = SdkManager.ParseLicenseOutput (output); + + Assert.AreEqual (0, licenses.Count, "Should return empty list"); + } +} From 8a9607cabbc10edf5639c0888b7c734e25b093e9 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Tue, 3 Mar 2026 06:20:51 -0800 Subject: [PATCH 237/308] Make `HttpClient` static in `JdkInstaller` (#291) Context: https://learn.microsoft.com/en-us/dotnet/fundamentals/networking/http/httpclient-guidelines#recommended-use * Make HttpClient static in JdkInstaller per Microsoft guidelines Co-authored-by: Jonathan Peppers --- .../src/Xamarin.Android.Tools.AndroidSdk/JdkInstaller.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/JdkInstaller.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/JdkInstaller.cs index c982542539a..74b344dffe4 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/JdkInstaller.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/JdkInstaller.cs @@ -30,7 +30,7 @@ public class JdkInstaller : IDisposable static readonly IProgress NullProgress = new Progress (); - readonly HttpClient httpClient = new(); + static readonly HttpClient httpClient = new(); readonly Action logger; public JdkInstaller (Action? logger = null) @@ -38,7 +38,7 @@ public JdkInstaller (Action? logger = null) this.logger = logger ?? AndroidSdkInfo.DefaultConsoleLogger; } - public void Dispose () => httpClient.Dispose (); + public void Dispose () { } /// Discovers available Microsoft OpenJDK versions for the current platform. public async Task> DiscoverAsync (CancellationToken cancellationToken = default) From 5c44cac77f40bc23c7c0667f3d1e5e13dbaa6385 Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Wed, 4 Mar 2026 16:55:02 -0600 Subject: [PATCH 238/308] [skills] create a skill for reviewing PRs (#292) This skill lets you say: review this PR: https://github.com/dotnet/android-tools/pull/284 Some example code reviews: * https://github.com/dotnet/android-tools/pull/283#pullrequestreview-3891677845 * https://github.com/dotnet/android-tools/pull/284#pullrequestreview-3891766754 This is built off a combination of previous code reviews, saved in `docs/CODE_REVIEW_POSTMORTEM.md`, and the review rules in `references/review-rules.md`. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../skills/android-tools-reviewer/SKILL.md | 86 +++ .../references/review-rules.md | 171 ++++++ .../scripts/submit_review.cs | 141 +++++ .../docs/CODE_REVIEW_POSTMORTEM.md | 560 ++++++++++++++++++ 4 files changed, 958 insertions(+) create mode 100644 external/xamarin-android-tools/.github/skills/android-tools-reviewer/SKILL.md create mode 100644 external/xamarin-android-tools/.github/skills/android-tools-reviewer/references/review-rules.md create mode 100644 external/xamarin-android-tools/.github/skills/android-tools-reviewer/scripts/submit_review.cs create mode 100644 external/xamarin-android-tools/docs/CODE_REVIEW_POSTMORTEM.md diff --git a/external/xamarin-android-tools/.github/skills/android-tools-reviewer/SKILL.md b/external/xamarin-android-tools/.github/skills/android-tools-reviewer/SKILL.md new file mode 100644 index 00000000000..188c40488ca --- /dev/null +++ b/external/xamarin-android-tools/.github/skills/android-tools-reviewer/SKILL.md @@ -0,0 +1,86 @@ +--- +name: android-tools-reviewer +description: >- + Review pull requests for dotnet/android-tools using lessons from past code reviews. + Trigger when the user says "review this" with a GitHub PR URL, asks to review a PR, + or wants code review feedback. Fetches the diff, checks it against established rules + (netstandard2.0, async, security, error handling, patterns, performance), and posts + a batched 🤖-prefixed review via gh CLI. +--- + +# Android Tools PR Reviewer + +Review PRs against guidelines distilled from past reviews by senior maintainers. + +## Workflow + +### 1. Parse the PR URL + +Extract `owner`, `repo`, `pr_number` from the URL. +Formats: `https://github.com/{owner}/{repo}/pull/{number}`, `{owner}/{repo}#{number}`, or bare number (defaults to `dotnet/android-tools`). + +### 2. Fetch PR data + +``` +gh pr view {number} --repo {owner}/{repo} --json title,body,files +gh pr diff {number} --repo {owner}/{repo} +``` + +### 3. Load review rules + +Read `references/review-rules.md` from this skill's directory. + +### 4. Analyze the diff + +For each changed file, check against the review rules. Record issues as: + +```json +{ "path": "src/Example.cs", "line": 42, "side": "RIGHT", "body": "..." } +``` + +Constraints: +- Only comment on added/modified lines in the diff — the API rejects out-of-range lines. +- `line` = line number in the NEW file (right side). Double-check against the diff. +- High signal only: 3 important comments > 15 nitpicks. Skip style/formatting unless it violates a specific rule. +- One issue per comment. + +### 5. Build the review JSON + +Write a temp JSON file: + +```json +{ + "event": "COMMENT", + "body": "## 🤖 AI Review Summary\n\nFound **N issues**: ...\n\n- **Category**: description (`file:line`)\n\n👍 Positive callouts.\n\n---\n_Review generated by android-tools-reviewer from [review guidelines](../../../docs/CODE_REVIEW_POSTMORTEM.md) by @jonathanpeppers._", + "comments": [ + { + "path": "src/Example.cs", + "line": 42, + "side": "RIGHT", + "body": "🤖 **Error handling** — Every `catch` should capture the `Exception` and log it.\n\n_Rule: No empty catch blocks (Postmortem `#11`)_" + } + ] +} +``` + +If no issues found, submit with empty `comments` and a positive summary. + +### 6. Submit as a single batch + +```powershell +dotnet run {skill-dir}/scripts/submit_review.cs {owner} {repo} {number} {path-to-json} +``` + +The script validates structure (required fields, 🤖 prefix, positive line numbers) then calls `gh api`. Delete the temp file after success. + +## Comment format + +``` +🤖 **{Category}** — {What's wrong and what to do instead.} + +_{Rule: Brief name (Postmortem `#N`)}_ +``` + +Always wrap `#N` in backticks so GitHub doesn't auto-link to issues. + +**Categories:** Target framework · Async pattern · Resource management · Error handling · Security · Code organization · Naming · Performance · Pattern · YAGNI · API design · Code duplication · Documentation diff --git a/external/xamarin-android-tools/.github/skills/android-tools-reviewer/references/review-rules.md b/external/xamarin-android-tools/.github/skills/android-tools-reviewer/references/review-rules.md new file mode 100644 index 00000000000..4b2583f7b75 --- /dev/null +++ b/external/xamarin-android-tools/.github/skills/android-tools-reviewer/references/review-rules.md @@ -0,0 +1,171 @@ +# Android Tools Review Rules + +Distilled from [CODE_REVIEW_POSTMORTEM.md](../../../docs/CODE_REVIEW_POSTMORTEM.md) — 51 findings +from reviews by @jonathanpeppers on PRs #274, #275, #281–#284. + +--- + +## 1. Target Framework Compatibility + +This library multi-targets `netstandard2.0` + `net10.0` and runs inside Visual Studio on .NET +Framework. Every API call must work on both targets. + +| Check | What to look for | +|-------|-----------------| +| **netstandard2.0 API surface** | Methods/overloads that only exist on net5+. Common traps: `HttpContent.ReadAsStringAsync(CancellationToken)`, `ProcessStartInfo.ArgumentList`, `Environment.IsPrivilegedProcess`, `ArrayPool` (needs `System.Buffers` package on ns2.0). When unsure, check MS Learn docs. | +| **C# language features** | `init` accessors, `required` keyword, file-scoped types, raw string literals — may need polyfills or `#if` guards. | +| **Conditional compilation** | New API usage should be behind `#if NET5_0_OR_GREATER` (or similar) with a fallback for netstandard2.0. | + +**Postmortem refs:** #3, #4, #30 + +--- + +## 2. Async & Cancellation Patterns + +| Check | What to look for | +|-------|-----------------| +| **CancellationToken propagation** | Every `async` method that accepts a `CancellationToken` must pass it to ALL downstream async calls (`GetAsync`, `ReadAsStreamAsync`, `SendAsync`, etc.). A token that's accepted but never used is a broken contract. | +| **OperationCanceledException** | Catch-all blocks (`catch (Exception)`) must NOT swallow `OperationCanceledException`. Either catch it explicitly first and rethrow, or use a type filter. | +| **GetStringAsync** | On netstandard2.0, `GetStringAsync(url)` doesn't accept a `CancellationToken`. Use `GetAsync(url, ct)` + `ReadAsStringAsync()` instead. | + +**Postmortem refs:** #5, #37 + +--- + +## 3. Resource Management + +| Check | What to look for | +|-------|-----------------| +| **HttpClient must be static** | `HttpClient` instances should be `static readonly` fields, not per-instance. Creating/disposing `HttpClient` leads to socket exhaustion via `TIME_WAIT` accumulation. See [Microsoft guidelines](https://learn.microsoft.com/dotnet/fundamentals/networking/http/httpclient-guidelines). | +| **No HttpClient injection (YAGNI)** | Don't add `HttpClient` constructor parameters "for testability" unless a caller actually needs it today. The AI tends to over-engineer this. | +| **ArrayPool for large buffers** | Buffers ≥ 1KB (especially 80KB+ download buffers) should use `ArrayPool.Shared.Rent()` with `try/finally` return. Large allocations go to the LOH and are expensive to GC. | +| **IDisposable** | Classes that own unmanaged resources or expensive managed resources must implement `IDisposable` with a dispose guard (`ThrowIfDisposed`). | + +**Postmortem refs:** #6, #7, #13 + +--- + +## 4. Error Handling + +| Check | What to look for | +|-------|-----------------| +| **No empty catch blocks** | Every `catch` must capture the `Exception` and log it (or rethrow). No silent swallowing. Even "expected" exceptions should be logged for diagnostics. | +| **Validate parameters** | Enum parameters and string-typed "mode" values must be validated — throw `ArgumentException` or `NotSupportedException` for unexpected values. Don't silently accept garbage. | +| **Fail fast on critical ops** | If an operation like `chmod` or checksum verification fails, throw immediately. Silently continuing leads to confusing downstream failures ("permission denied" when the real problem was chmod). | +| **Mandatory verification** | Checksum/hash verification must NOT be optional. If the checksum can't be fetched, the operation must fail — not proceed unverified. | + +**Postmortem refs:** #11, #20, #22, #35 + +--- + +## 5. Security + +| Check | What to look for | +|-------|-----------------| +| **Zip Slip protection** | Archive extraction must validate that every entry path, after `Path.GetFullPath()`, resolves under the destination directory. Never use `ZipFile.ExtractToDirectory()` for untrusted archives without entry-by-entry validation. | +| **Command injection** | Arguments passed to `Process.Start` or written to `.cmd`/`.sh` scripts must be sanitized. Use `ProcessUtils.CreateProcessStartInfo()` with separate argument strings — it uses `ArgumentList` on net5+ (no shell parsing). Never interpolate user/external input into command strings. | +| **Path traversal** | `StartsWith()` checks on paths must normalize with `Path.GetFullPath()` first. A path like `C:\Program Files\..\Users\evil` bypasses naive prefix checks. Also check for directory boundary issues (`C:\Program FilesX` matching `C:\Program Files`). | +| **Elevation** | Don't auto-elevate. Don't include `IsElevated()` helpers that silently re-launch elevated. The calling tool (VS, VS Code) should handle elevation prompts. The library should error if it lacks permissions. | + +**Postmortem refs:** #17, #34, #39 + +--- + +## 6. Code Organization + +| Check | What to look for | +|-------|-----------------| +| **One type per file** | Each public class, struct, enum, or interface must be in its own `.cs` file named after the type. No multiple top-level types in a single file. | +| **File-scoped namespaces** | New files should use `namespace Foo;` (not `namespace Foo { ... }`). Don't reformat existing files. | +| **No #region directives** | `#region` hides code and makes reviews harder. Remove them. | +| **Use `record` for data types** | Immutable data-carrier types (progress, version info, license info) should be `record` types. They get value equality, `ToString()`, and deconstruction for free. | +| **Remove unused code** | Dead methods, speculative helpers, and code "for later" should be removed. Ship only what's needed. | + +**Postmortem refs:** #9, #12, #25, #28 + +--- + +## 7. Naming & Constants + +| Check | What to look for | +|-------|-----------------| +| **Avoid ambiguous names** | Types that could collide with Android concepts (e.g., `ManifestComponent` vs `AndroidManifest.xml`) need disambiguating prefixes (e.g., `SdkManifestComponent`). | +| **No magic numbers** | Literal values like buffer sizes (`81920`), divisors (`1048576`), permission masks (`0x1ED` = 0755) should be named constants. | +| **Environment variable constants** | Use `EnvironmentVariableNames.AndroidHome` — not raw `"ANDROID_HOME"` strings. Typos in env var names produce silent, hard-to-debug failures. | +| **ANDROID_SDK_ROOT is deprecated** | Per [Android docs](https://developer.android.com/tools/variables#envar), use `ANDROID_HOME` everywhere. Do not introduce new references to `ANDROID_SDK_ROOT`. | + +**Postmortem refs:** #10, #14, #18, #19 + +--- + +## 8. Performance + +| Check | What to look for | +|-------|-----------------| +| **XmlReader over LINQ XML** | For forward-only XML parsing (manifests, config files), use `XmlReader` — it's streaming and allocation-free. `XElement`/`XDocument` builds a full DOM tree. | +| **p/invoke over process spawn** | For single syscalls like `chmod`, use `[DllImport("libc")]` instead of spawning a child process. Process creation is orders of magnitude more expensive. | +| **Avoid intermediate collections** | Don't create two lists and `AddRange()` one to the other. Build a single list, or use LINQ to chain. | +| **Cache reusable arrays** | Char arrays for `string.Split()` (like whitespace chars) should be `static readonly` fields, not allocated on each call. | + +**Postmortem refs:** #8, #14, #21, #31 + +--- + +## 9. Patterns & Conventions + +| Check | What to look for | +|-------|-----------------| +| **Use `ProcessUtils`** | All process creation must go through `ProcessUtils.CreateProcessStartInfo()` and `ProcessUtils.StartProcess()`. No direct `new ProcessStartInfo()` or `Process.Start()`. | +| **Use `FileUtil`** | File extraction, downloads, checksum verification, and path operations belong in `FileUtil`. Don't duplicate file helpers in domain classes. | +| **Null-object pattern** | Methods accepting nullable dependencies (`IProgress?`, `ILogger?`, `Action?`) should assign a null-object sentinel early (e.g., `progress ??= NullProgress.Instance`, `logger ??= NullLogger.Instance`) and then use the dependency without `?.` null checks throughout the method. Scattered `logger?.Log(...)` or `progress?.Report(...)` calls are a code smell — they add noise, invite missed spots, and signal a missing null-object type. If no null-object type exists yet, recommend creating one. | +| **Version-based directories** | Install SDK/JDK to versioned paths (`cmdline-tools/19.0/`, not `cmdline-tools/latest/`). Versioned paths are self-documenting and allow side-by-side installs. | +| **Safe directory replacement** | Use move-with-rollback: rename existing → temp, move new in place, validate, delete temp only after validation succeeds. Never delete the backup before confirming the new install works. | +| **Cross-volume moves** | `Directory.Move` is really a rename — it fails across filesystems. Extract archives near the target path (same parent directory), or catch `IOException` and fall back to recursive copy + delete. | + +**Postmortem refs:** #15, #16, #23, #36, #38 + +--- + +## 10. YAGNI & AI-Specific Pitfalls + +These are patterns that AI-generated code consistently gets wrong: + +| Pattern | What to watch for | +|---------|------------------| +| **Reinventing the wheel** | AI creates new infrastructure (e.g., `AndroidToolRunner`) instead of using existing utilities (`ProcessUtils`). ALWAYS check if a similar utility exists before accepting new wrapper code. This is the most expensive AI pattern — hundreds of lines of plausible code that duplicates what's already there. | +| **Over-engineering** | HttpClient injection "for testability", elevation auto-detection, speculative helper classes. If no caller needs it today, remove it. | +| **Swallowed errors** | AI catch blocks love to eat exceptions silently. Check EVERY catch block. Also check that exit codes are checked consistently — if `ListDevicesAsync` checks exit codes, `StopEmulatorAsync` should too. | +| **Ignoring target framework** | AI generates code for the newest .NET. Check every API call against netstandard2.0. | +| **Sloppy structure** | Multiple types in one file, block-scoped namespaces, #region directives, classes where records would do. New helpers marked `public` when `internal` suffices. | +| **Confidently wrong domain facts** | AI once claimed `ANDROID_SDK_ROOT` was the recommended variable (it's deprecated). Always verify domain-specific claims against official docs. | +| **Over-mocking** | Not everything needs to be mocked. Network integration tests with `Assert.Ignore` on failure are fine and catch real API changes that mocks never will. | +| **Docs describe intent not reality** | AI doc comments often describe what the code *should* do, not what it *actually* does. Review doc comments against the implementation. | +| **Unused parameters** | AI adds `CancellationToken` parameters but never observes them, or accepts `additionalArgs` as a string and interpolates it into a command. Unused CancellationToken is a broken contract; string args are injection risks. | + +**Postmortem refs:** #7, #28, #29, #40, #41, #42, #49, #50, #51 + +--- + +## 11. API Design + +| Check | What to look for | +|-------|-----------------| +| **Return `IReadOnlyList` not `List`** | Public methods should return `IReadOnlyList` (or `IReadOnlyCollection`) instead of mutable `List`. Prevents callers from mutating internal state. | +| **New helpers default to `internal`** | New utility methods should be `internal` unless a confirmed external consumer (e.g., `dotnet/android`) needs them. Use `InternalsVisibleTo` for test access. | +| **Structured args, not string interpolation** | Additional arguments to processes should be `IEnumerable`, not a single `string` that gets interpolated. Use `ProcessUtils.CreateProcessStartInfo()` which handles `ArgumentList` safely. | +| **Honor `CancellationToken`** | If a method accepts a `CancellationToken`, it MUST observe it — register a callback to kill processes, check `IsCancellationRequested` in loops, pass it to downstream async calls. Don't just accept it for API completeness. | + +**Postmortem refs:** #46, #47, #49, #50 + +--- + +## 12. Code Sharing & Downstream Coordination + +| Check | What to look for | +|-------|-----------------| +| **Port, don't rewrite** | If `dotnet/android` (or another downstream consumer) already has working logic for the same task, port it rather than writing new code. The existing code has real-world edge cases already handled. | +| **Draft downstream PR before merging** | Shared library changes should be accompanied by a draft PR in the consuming repo that proves the API actually works. Merge the library first, update the submodule pointer, then merge the consumer. | +| **Don't redirect stdout/stderr without draining** | Background processes with `RedirectStandardOutput = true` must have async readers draining the output. Otherwise the OS pipe buffer fills and the child process deadlocks. For fire-and-forget processes, set `Redirect* = false`. | +| **Check exit codes consistently** | If one operation (`ListDevicesAsync`) checks the process exit code, ALL similar operations (`StopEmulatorAsync`, `WaitForDeviceAsync`) must too. Inconsistent error checking creates a false sense of safety. | + +**Postmortem refs:** #42, #43, #44, #45, #48 diff --git a/external/xamarin-android-tools/.github/skills/android-tools-reviewer/scripts/submit_review.cs b/external/xamarin-android-tools/.github/skills/android-tools-reviewer/scripts/submit_review.cs new file mode 100644 index 00000000000..66314d1454b --- /dev/null +++ b/external/xamarin-android-tools/.github/skills/android-tools-reviewer/scripts/submit_review.cs @@ -0,0 +1,141 @@ +// Usage: dotnet run submit_review.cs +// +// Validates the review JSON structure, then submits it as a batched PR review +// via `gh api`. Requires the `gh` CLI to be installed and authenticated. + +using System; +using System.Diagnostics; +using System.IO; +using System.Text.Json; + +if (args.Length != 4) +{ + Console.Error.WriteLine ($"Usage: dotnet run submit_review.cs "); + return 1; +} + +var owner = args [0]; +var repo = args [1]; +var prNumber = args [2]; +var jsonPath = args [3]; + +if (!File.Exists (jsonPath)) { + Console.Error.WriteLine ($"❌ File not found: {jsonPath}"); + return 1; +} + +var json = File.ReadAllText (jsonPath); +JsonDocument doc; +try { + doc = JsonDocument.Parse (json); +} catch (JsonException ex) { + Console.Error.WriteLine ($"❌ Invalid JSON: {ex.Message}"); + return 1; +} + +using (doc) { + +var root = doc.RootElement; + +// Validate structure +var errors = new System.Collections.Generic.List (); + +if (!root.TryGetProperty ("event", out var eventProp) || eventProp.ValueKind != JsonValueKind.String) { + errors.Add ("Missing or invalid 'event' field — must be COMMENT, APPROVE, or REQUEST_CHANGES"); +} else { + var ev = eventProp.GetString ()!; + if (ev != "COMMENT" && ev != "APPROVE" && ev != "REQUEST_CHANGES") + errors.Add ($"Invalid event '{ev}' — must be COMMENT, APPROVE, or REQUEST_CHANGES"); +} + +if (!root.TryGetProperty ("body", out var bodyProp) || bodyProp.ValueKind != JsonValueKind.String || string.IsNullOrWhiteSpace (bodyProp.GetString ())) { + errors.Add ("Missing or empty 'body' field (the review summary)"); +} + +if (root.TryGetProperty ("comments", out var commentsProp) && commentsProp.ValueKind == JsonValueKind.Array) { + int i = 0; + foreach (var c in commentsProp.EnumerateArray ()) { + var prefix = $"comments[{i}]"; + + if (!c.TryGetProperty ("path", out var pathProp) || string.IsNullOrEmpty (pathProp.GetString ())) + errors.Add ($"{prefix}: missing 'path'"); + + if (!c.TryGetProperty ("line", out var lineProp) || lineProp.ValueKind != JsonValueKind.Number || lineProp.GetInt32 () < 1) + errors.Add ($"{prefix}: 'line' must be a positive integer"); + + if (!c.TryGetProperty ("body", out var cbody) || string.IsNullOrWhiteSpace (cbody.GetString ())) + errors.Add ($"{prefix}: missing or empty 'body'"); + else if (!cbody.GetString ()!.StartsWith ("🤖")) + errors.Add ($"{prefix}: body must start with 🤖 prefix"); + + if (c.TryGetProperty ("side", out var sideProp) && sideProp.ValueKind == JsonValueKind.String) { + var side = sideProp.GetString ()!; + if (side != "LEFT" && side != "RIGHT") + errors.Add ($"{prefix}: 'side' must be LEFT or RIGHT, got '{side}'"); + } + + i++; + } +} + +if (errors.Count > 0) { + Console.Error.WriteLine ("❌ Review JSON validation failed:"); + foreach (var e in errors) + Console.Error.WriteLine ($" • {e}"); + return 1; +} + +var commentCount = root.TryGetProperty ("comments", out var cp) && cp.ValueKind == JsonValueKind.Array ? cp.GetArrayLength () : 0; +Console.WriteLine ($"✅ Review validated: {commentCount} comment(s)"); +Console.WriteLine ($"📤 Submitting review to {owner}/{repo}#{prNumber}..."); + +// Submit via gh api +var psi = new ProcessStartInfo { + FileName = "gh", + UseShellExecute = false, + RedirectStandardOutput = true, + RedirectStandardError = true, +}; +psi.ArgumentList.Add ("api"); +psi.ArgumentList.Add ($"repos/{owner}/{repo}/pulls/{prNumber}/reviews"); +psi.ArgumentList.Add ("--method"); +psi.ArgumentList.Add ("POST"); +psi.ArgumentList.Add ("--input"); +psi.ArgumentList.Add (jsonPath); + +var process = Process.Start (psi)!; +var stdoutTask = process.StandardOutput.ReadToEndAsync (); +var stderrTask = process.StandardError.ReadToEndAsync (); +process.WaitForExit (); +var stdout = stdoutTask.Result; +var stderr = stderrTask.Result; + +if (process.ExitCode != 0) { + Console.Error.WriteLine ($"❌ gh api failed (exit code {process.ExitCode}):"); + if (!string.IsNullOrEmpty (stderr)) + Console.Error.WriteLine (stderr); + if (!string.IsNullOrEmpty (stdout)) { + try { + using var errDoc = JsonDocument.Parse (stdout); + if (errDoc.RootElement.TryGetProperty ("message", out var msg)) + Console.Error.WriteLine ($" GitHub says: {msg.GetString ()}"); + } catch { + Console.Error.WriteLine (stdout); + } + } + return 1; +} + +try { + using var resp = JsonDocument.Parse (stdout); + if (resp.RootElement.TryGetProperty ("html_url", out var url)) + Console.WriteLine ($"✅ Review posted: {url.GetString ()}"); + else + Console.WriteLine ("✅ Review posted."); +} catch { + Console.WriteLine ("✅ Review posted."); +} + +} // using (doc) + +return 0; diff --git a/external/xamarin-android-tools/docs/CODE_REVIEW_POSTMORTEM.md b/external/xamarin-android-tools/docs/CODE_REVIEW_POSTMORTEM.md new file mode 100644 index 00000000000..b1cad7bee92 --- /dev/null +++ b/external/xamarin-android-tools/docs/CODE_REVIEW_POSTMORTEM.md @@ -0,0 +1,560 @@ +# Code Review Postmortem + +Review feedback from **@jonathanpeppers** (and Copilot reviewer) on Copilot-assisted PRs by @rmarinho, adding new infrastructure to `Xamarin.Android.Tools.AndroidSdk`. + +| PR | Title | Status | +|----|-------|--------| +| [#274](https://github.com/dotnet/android-tools/pull/274) | Add JDK installation support (Microsoft OpenJDK) | Merged 2026-02-26 | +| [#275](https://github.com/dotnet/android-tools/pull/275) | Add SDK bootstrap and sdkmanager wrapper | Merged 2026-03-02 | +| [#281](https://github.com/dotnet/android-tools/pull/281) | Add tool runner base infrastructure | Open | +| [#282](https://github.com/dotnet/android-tools/pull/282) | Add AvdManagerRunner for avdmanager CLI operations | Open | +| [#283](https://github.com/dotnet/android-tools/pull/283) | Add AdbRunner for adb CLI operations | Open | +| [#284](https://github.com/dotnet/android-tools/pull/284) | Add EmulatorRunner for emulator CLI operations | Open | + +--- + +## 1. Use the right source: Microsoft OpenJDK, not Adoptium + +**PR #274** — The initial implementation downloaded JDKs from the Eclipse Adoptium (Temurin) API. Jonathan redirected it to Microsoft's own OpenJDK build at . + +> "Should use instead: https://www.microsoft.com/openjdk" + +**Why it matters:** The library ships inside Microsoft tooling (Visual Studio, .NET Android SDK). Downloading from a third-party distribution introduces supply-chain risk and licensing ambiguity. Using Microsoft's own build keeps the provenance chain tight and ensures the JDK is tested against Microsoft's own CI. + +--- + +## 2. Don't ship support for versions that aren't ready + +**PR #274** — The original code supported both JDK 17 and JDK 21. Jonathan asked to remove 17 and ship only 21, while keeping the `SupportedVersions` array so more can be added later. + +> "I wouldn't support 17 yet, but it would be ok to leave this as an `int[]` so more could be added later." + +**Why it matters:** Supporting a version means testing, documenting, and maintaining it. Shipping JDK 17 support without the infrastructure to validate it invites bugs that only appear in production. Leaving the extensibility point (the array) costs nothing and keeps the door open. + +--- + +## 3. Must target netstandard2.0 — this runs inside Visual Studio + +**PR #274** — The code originally targeted only modern .NET. Jonathan flagged that it must support `netstandard2.0` because the library loads inside Visual Studio (which runs on .NET Framework). + +> "This code needs to be able run on .NET framework — so it needs to support netstandard. It could be running inside Visual Studio." + +**Why it matters:** A library that compiles fine on `net10.0` but uses APIs absent from `netstandard2.0` will crash at runtime inside VS. This is the kind of bug that passes CI (tests run on modern .NET) but fails in the field. Every new API call must be checked against the `netstandard2.0` surface area. + +--- + +## 4. New C# language features may not be available + +**PR #274** (commit `b2a8a7be`) — Jonathan fixed code that used newer C# language features not available when targeting `netstandard2.0` / older compilers. + +**Why it matters:** When Copilot generates code, it tends to use the newest syntax it knows. If the project's `LangVersion` or target framework doesn't support those features, the build breaks. Reviewers must watch for this, especially in multi-target projects. + +--- + +## 5. Thread `CancellationToken` through every async call + +**PR #274** (commit `212ac436`) and **PR #275** (review comment) — Multiple places accepted a `CancellationToken` parameter but never passed it to the underlying HTTP call (`GetStringAsync`, `ReadAsStreamAsync`, etc.). Jonathan fixed this throughout. + +> (PR #275): Copilot reviewer flagged `GetStringAsync` not passing `cancellationToken`. Jonathan's commit wired it through properly. + +**Why it matters:** An un-cancellable async method is a lie — callers think they can cancel but the operation keeps running, holding sockets, memory, and threads. In an IDE (VS), a stuck HTTP call can freeze the UI. Always propagate the token to the lowest-level async operation. + +--- + +## 6. `HttpClient` must be static — socket exhaustion + +**PR #275** (commit `d30a5d8e`) — The `SdkManager` class created a per-instance `HttpClient`. Jonathan changed it to `static readonly` and removed disposal. + +> Commit message: "Per Microsoft guidelines, HttpClient instances should be static and shared across the application lifetime." + +**Why it matters:** Creating and disposing `HttpClient` instances leads to socket exhaustion under load because `TIME_WAIT` sockets accumulate. The [official guidance](https://learn.microsoft.com/dotnet/fundamentals/networking/http/httpclient-guidelines) is to reuse a single static instance. This is a well-known .NET pitfall that AI-generated code consistently gets wrong. + +--- + +## 7. Remove `HttpClient` injection until there's a real need + +**PR #275** (threads 11, 26) — The AI-generated code accepted an optional `HttpClient` parameter for "testability" and "enterprise proxy scenarios." Jonathan pushed back twice, noting the justification was speculative. + +> "Why do we need to pass in an `HttpClient`? Could `SdkManager` just use its own and it would simplify some things?" +> +> "I would still remove the option to pass in a `HttpClient` until you have a reason you actually need it. The AI gave some made-up/theoretical answer on why not to remove it." + +**Why it matters:** YAGNI (You Aren't Gonna Need It). Every public API parameter is a maintenance commitment. AI assistants tend to over-engineer for hypothetical scenarios. If no caller needs to inject an `HttpClient` today, don't add the parameter — it complicates the constructor, the dispose logic, and the ownership semantics. + +--- + +## 8. Use `XmlReader` instead of `XElement` / LINQ to XML + +**PR #275** (thread 12) — Manifest parsing used `XDocument.Parse()` + LINQ queries. Jonathan requested `XmlReader` for lower overhead. + +> "If copilot wrote this, can we make it use `XmlReader` instead? `XElement` is based on System.Linq, so it's slower." + +**Why it matters:** `XElement` builds a full DOM tree in memory. For a manifest that only needs forward-only reading, `XmlReader` is streaming and allocation-free. In a library that may run in constrained IDE processes, avoiding unnecessary allocations matters. + +--- + +## 9. One type per file, and update Copilot instructions + +**PR #275** (threads 9, 25) — Multiple types were crammed into `SdkManager.cs`. Jonathan asked for each to live in its own file and for the convention to be added to `copilot-instructions.md` so AI assistants follow it going forward. + +> "Can we put each type in its own file, `SdkManifestComponent.cs`, and update `copilot-instructions.md` so AIs will always do this?" + +**Why it matters:** One-type-per-file is a core C# convention in this repo. It makes types discoverable via filename, simplifies git blame, and reduces merge conflicts. Encoding the convention in Copilot instructions prevents the same issue from recurring in every AI-assisted PR. + +--- + +## 10. Name types to avoid ambiguity — `SdkManifestComponent`, not `ManifestComponent` + +**PR #275** (thread 9) — The type was originally named `ManifestComponent`, which collides conceptually with `AndroidManifest.xml`. + +> "You may also want to pick a better name other than `Manifest`? There is `AndroidManifest.xml`, so maybe this needs to be `SdkManifestComponent`?" + +**Why it matters:** In a codebase that deals with both Android app manifests and SDK repository manifests, an ambiguous name causes confusion. Prefixing with `Sdk` makes the domain clear at a glance. + +--- + +## 11. No empty catch blocks — always log the exception + +**PR #275** (threads 17, 18) — Multiple `catch` blocks swallowed exceptions silently. + +> "Every `catch` should get the `Exception` and log it, so no empty catch blocks." + +**Why it matters:** Empty catch blocks hide bugs. When something goes wrong in production, there's no diagnostic trail. Even if the exception is expected (e.g., declining a license), logging it provides a breadcrumb for debugging. This was a recurring pattern the AI generated. + +--- + +## 12. Remove `#region` directives + +**PR #275** (thread 19) — Test files had `#region` blocks. + +> "Can we remove all `#region` and update `copilot-instructions.md` so it won't make them in the future?" + +**Why it matters:** `#region` hides code and makes reviews harder — collapsed regions are easy to skip. Modern IDEs make them unnecessary. Banning them in Copilot instructions prevents AI from reintroducing them. + +--- + +## 13. Use `ArrayPool` for download buffers + +**PR #274** (commit `6e167e00`) and **PR #275** (thread 21) — Download code allocated `new byte[81920]` on each call. Jonathan replaced this with `ArrayPool.Shared.Rent()` with `try/finally` return. + +> "Can this use `ArrayPool.Rent()` and return it in a `try-finally` block?" + +**Why it matters:** An 80 KB allocation goes straight to the Large Object Heap (LOH), which is expensive to collect. `ArrayPool` rents from a shared pool, avoiding GC pressure. In a library that may download multiple files, this adds up. + +--- + +## 14. Extract magic numbers into named constants + +**PR #274** (commit `6e167e00`) — The download buffer size (`81920`), bytes-per-MB divisor (`1048576`), and whitespace char array were inline literals. Jonathan extracted them to `const` fields (`BufferSize`, `BytesPerMB`, `WhitespaceChars`). + +**Why it matters:** Magic numbers obscure intent. A reviewer seeing `81920` has to mentally compute "oh, that's 80 KB." A constant named `BufferSize` communicates instantly. It also creates a single point of change. + +--- + +## 15. Centralize process creation in `ProcessUtils` + +**PR #274** (commits `d0f3bea2`, `9ed5ddc8`) and **PR #275** (thread 29) — The code manually constructed `ProcessStartInfo` in multiple places. Jonathan created `ProcessUtils.CreateProcessStartInfo()` that uses `ArgumentList` on modern .NET and falls back to a safe `Arguments` string on `netstandard2.0`. + +> "Can this use: ProcessUtils.cs#L22" + +**Why it matters:** Duplicated process-launch code means duplicated bugs — inconsistent argument escaping, missing `UseShellExecute = false`, forgotten `RedirectStandardOutput`. A centralized helper gets it right once. The `ArgumentList` vs `Arguments` split is particularly tricky to get right across target frameworks. + +--- + +## 16. Use `NullProgress` pattern instead of `progress?.Report()` + +**PR #274** (commit `48ede26d`) and **PR #275** (threads 98–99) — Code was littered with `progress?.Report(...)` null checks. Jonathan replaced these with a `NullProgress` sentinel assigned via `progress ??= NullProgress.Instance`. + +> "Can we set `progress ??=` with a 'null/no-op progress' so you don't have to put `progress?.` checks everywhere?" + +**Why it matters:** Null-propagation operators scattered throughout a method add visual noise and make it easy to miss a spot. The null-object pattern eliminates the entire class of "forgot to null-check" bugs and makes the code cleaner. + +--- + +## 17. `IsElevated()` is unnecessary — let the caller handle elevation + +**PR #274** (commit `e8e9db78`) and **PR #275** (thread 88) — The code had an `IsElevated()` helper with P/Invoke to check admin rights, with logic to re-launch elevated. Jonathan removed it. + +> "Should we even have this? It seems like we should check for elevation (only where required!) and error if we don't have it. Seems like VS Code or developers should run this tool already elevated?" + +**Why it matters:** Auto-elevation is a security anti-pattern — it silently escalates privileges. The calling tool (VS, VS Code, CLI) should prompt the user for elevation. The library should simply fail with a clear error if it lacks permissions. + +--- + +## 18. `ANDROID_SDK_ROOT` is deprecated — use `ANDROID_HOME` + +**PR #275** (thread 31) — The code set both `ANDROID_HOME` and `ANDROID_SDK_ROOT` environment variables. + +> "`ANDROID_SDK_ROOT` is deprecated: https://developer.android.com/tools/variables#envar — I don't think we should add new code using it." + +**Why it matters:** Setting a deprecated variable trains downstream tools and developers to depend on it, prolonging the migration. Following the official Android documentation (`ANDROID_HOME` only) keeps the codebase aligned with the platform's direction. + +--- + +## 19. Create `EnvironmentVariableNames` constants — no raw strings + +**PR #275** (threads 30, 42) — Environment variable names like `"ANDROID_HOME"` were scattered as string literals. + +> "Should we create a `EnvironmentVariableNames.cs` that has important env var names like these?" + +**Why it matters:** String typos in environment variable names produce silent, hard-to-debug failures (the wrong variable is read/set, but no error is thrown). A constants class catches typos at compile time and makes it easy to find all usages via "Find References." + +--- + +## 20. Validate enum/parameter values — don't silently accept garbage + +**PR #275** (thread 22) — The `checksumType` was a string parameter that only handled `"sha1"` but accepted anything without error. + +> "Why is there a `checksumType` parameter if it only does `SHA1`? Should it be checking the value and throwing an exception for unexpected hash types?" + +This also led to making `checksumType` an enum instead of a string (thread 85): + +> "Can `checksumType` be an enum, so no typos can occur? Also would avoid string comparisons." + +**Why it matters:** A function that silently ignores an unsupported value is a correctness bug waiting to happen. If a new checksum type is added to the manifest but the code doesn't support it, downloads would pass "verification" without actually being checked. Throw early, throw loud. + +--- + +## 21. Use p/invoke for `chmod` instead of spawning a process + +**PR #275** (thread 23) — The code ran `chmod +x` by spawning a child process. Jonathan suggested using p/invoke directly. + +> "Is there a p/invoke way to do this instead? Then it wouldn't run a new process." + +**Why it matters:** Spawning a process for a single syscall is expensive — it forks, execs, waits, and produces overhead for logging and error handling. A direct `[DllImport("libc")] chmod()` call is instantaneous and more reliable. It also makes error handling straightforward (check the return value) instead of parsing process exit codes. + +--- + +## 22. If `chmod` fails, throw — don't silently continue + +**PR #275** (thread 32) — After adding p/invoke for `chmod`, the failure path caught the exception and continued. + +> "If this fails, should we just let the exception happen? The problem is you won't be able to use `sdkmanager` if it fails, so seems like it should error?" + +**Why it matters:** If `chmod` fails, the `sdkmanager` binary won't be executable, and every subsequent operation will fail with a confusing "file not found" or "permission denied" error. Failing fast at the point of the real problem (chmod) gives the user an actionable error message. + +--- + +## 23. Use version-based directory names, not `latest` + +**PR #275** (thread 15) — The bootstrap extracted cmdline-tools to a `latest/` directory. + +> "On macOS, `latest` is a symlink to the versioned one, like `latest -> 19.0` or whatever. Could we just use the version number of the package for the directory name and not do anything with `latest` at all?" + +**Why it matters:** `latest` is ambiguous — it doesn't tell you which version is installed, and upgrading is a destructive overwrite with no rollback. A versioned directory (`cmdline-tools/19.0/`) is self-documenting, allows side-by-side versions, and matches how `sdkmanager` itself organizes packages. + +--- + +## 24. License presentation API — don't just auto-accept + +**PR #275** (thread 20) — The original `AcceptLicensesAsync` blindly sent `"y"` to stdin for every license prompt. Jonathan asked for an API to present licenses to the user first. + +> "If we have code here to _accept_ licenses, is there supposed to be an API that can be used to present them to the user? It seems like IDEs will show some UI for these, and CLI tools should prompt at the terminal." + +**Why it matters:** Auto-accepting licenses without user consent may violate legal requirements. IDEs need to display the license text and get explicit user approval. The resulting `GetPendingLicensesAsync()` + `AcceptLicensesAsync(IEnumerable)` API separates presentation from acceptance. + +--- + +## 25. Prefer `record` types for immutable data models + +**PR #275** (threads 78–80) — `SdkBootstrapProgress`, `SdkLicense`, and `SdkManifestComponent` were plain classes. Jonathan asked them to be `record` types. + +> "Can this be a `record`?" + +**Why it matters:** Records provide value equality, `ToString()`, and deconstruction for free. For small data-carrier types, they eliminate boilerplate and make intent clear: this is an immutable data bag, not a stateful object. + +--- + +## 26. Use file-scoped namespaces in all new files + +**PR #275** (threads 95–96) — New files used traditional block-scoped namespaces. + +> "Should we use file-scoped namespaces in all new files?" + +**Why it matters:** File-scoped namespaces (`namespace Foo;`) reduce one level of indentation across the entire file, improving readability. Jonathan also asked this to be added to `copilot-instructions.md` to prevent regression. + +--- + +## 27. Don't duplicate code — unify platform mappings + +**PR #275** (threads 81, 92) — Both `JdkInstaller` and `SdkManager` had independent OS/architecture-to-string mapping logic. + +> "This mapping seems specific to the `sdkmanager`, we already have this here: JdkInstaller.cs#L319-L334. We should either: Unify with `JdkInstaller` or put this code in SDKManager, if it is specific to Android SDK." + +**Why it matters:** Duplicated platform-detection logic means two places to update when a new architecture (e.g., RISC-V) is added, and two places that can diverge silently. Either share the code or make each copy clearly scoped to its specific use case. + +--- + +## 28. Remove unused / speculative code + +**PR #275** (threads 82–84) — An `AndroidEnvironmentHelper` class contained methods that weren't called, duplicated existing functionality, or belonged in a different PR. + +> "Is this method even called? We have other code that does this, we should find it and unify. I'd remove this for now, until it's needed." +> +> "We should completely remove this and use `AndroidVersions` classes directly." + +**Why it matters:** Dead code is worse than no code — it still needs to be read, understood, and maintained. It also misleads future contributors into thinking it's the right way to do things. Ship only what's needed for the current PR. + +--- + +## 29. Don't tell AI to run `dotnet format` globally + +**PR #275** (thread 77) — The Copilot instructions initially told the AI to run `dotnet format`. + +> "I wouldn't tell it to `dotnet-format`, or it's going to make 100s of changes across the repo!" + +**Why it matters:** An AI following "run dotnet format" will reformat every file in the repository, creating massive, unrelated diffs that obscure the actual changes and cause merge conflicts. Format only the files you're changing. + +--- + +## 30. Guide AI with `copilot-instructions.md` for netstandard2.0 awareness + +**PR #275** (thread 94) — Rather than listing specific API incompatibilities, Jonathan suggested a general instruction. + +> "Maybe this should just say: 'many modern .NET APIs are unavailable or have fewer overloads on `netstandard2.0`. When unsure about API availability, search mslearn to check documentation for the target framework.'" + +**Why it matters:** You can't enumerate every API difference between `net10.0` and `netstandard2.0`. Teaching the AI *how to check* (search MS Learn) is more durable than giving it a static list that will inevitably go stale. + +--- + +## 31. Simplify code — merge lists, short-circuit with LINQ + +**PR #275** (thread 93) — Package-list parsing created two separate lists and used `AddRange()`. + +> "This creates two lists and AddRange() one to the other. It's already using System.Linq (slowish), but could it just create 1 list? That would probably make it 'good enough'." + +**Why it matters:** Unnecessary intermediate collections waste memory and make the code harder to follow. Even when absolute performance isn't critical, simpler code is easier to review and less likely to hide bugs. + +--- + +## 32. Write thorough tests — especially for parsing and utilities + +**PR #274** (commit `6e4d1174`: "MOAR Tests!", commit `7c85bd7c`) — Jonathan personally wrote 437 lines of tests across `DownloadUtilsTests.cs`, `FileUtilTests.cs`, `JdkVersionInfoTests.cs`, and `ProcessUtilsTests.cs`. + +**Why it matters:** The AI-generated code came with some tests, but Jonathan significantly expanded coverage — particularly for parsing logic (`ParseChecksumFile`), file utilities (`MoveWithRollback`, `IsUnderDirectory`), and process argument construction. These are exactly the kind of functions where edge cases hide. + +--- + +## 33. Stdin write-then-delay ordering matters + +**PR #275** (threads 90–91) — The license acceptance code had a 500ms initial delay before sending input to `sdkmanager --licenses`, then a 200ms delay between subsequent writes. + +> "Why delay 500ms? Can we remove this line and it sends `n` every 200ms?" +> +> "Seems like we could just put the delay first and remove the 500ms above." + +**Why it matters:** Process stdin interaction is timing-sensitive. Writing before the process is ready for input loses the write; delaying too long slows down the operation. The correct pattern is delay-then-write (wait for the process to be ready, then send), not write-then-delay. + +--- + +## 34. Zip Slip protection — validate archive entry paths + +**PR #274** (Copilot reviewer + Jonathan's guidance) — The original code used `ZipFile.ExtractToDirectory()` which doesn't validate entry paths. A malicious archive could contain entries like `../../etc/passwd` that escape the target directory. This was replaced with entry-by-entry extraction that validates each path resolves under the destination. + +**Why it matters:** Zip Slip is a well-known archive extraction vulnerability ([CVE-2018-1002200](https://snyk.io/research/zip-slip-vulnerability)). Libraries that extract archives from the internet (JDK downloads, SDK packages) are high-value targets. Always validate that `Path.GetFullPath(entryPath)` starts with the destination directory. + +--- + +## 35. Checksum verification must be mandatory, not optional + +**PR #274** (Copilot reviewer, threads 26, 37) — The original code proceeded with installation even if the checksum fetch failed, silently skipping verification. + +> Copilot: "InstallAsync proceeds without checksum verification if the checksum fetch fails. This weakens the supply-chain guarantees." + +This was fixed to throw `InvalidOperationException` when checksum fetch fails, making verification mandatory for both archive extraction and elevated platform-installer paths. + +**Why it matters:** Optional checksum verification is the same as no checksum verification — an attacker who can interfere with the checksum URL gets a free pass. For supply-chain security, the download must fail if integrity cannot be confirmed. + +--- + +## 36. `Directory.Move` fails across volumes — extract near the target + +**PR #274** (Copilot reviewer, thread 25) — The code extracted archives to `Path.GetTempPath()` then used `Directory.Move()` to the target. On systems where `/tmp` is a different filesystem (common on Linux with tmpfs), this fails. + +> Copilot: "Directory.Move fails across volumes/filesystems, so installs will reliably fail when targetPath is on a different drive/mount than the temp directory." + +The fix was to create the temp extraction directory under the same parent as the target path. + +**Why it matters:** This is a classic cross-platform pitfall. `Directory.Move` is really a rename syscall, which only works within the same filesystem. Extracting near the target ensures same-filesystem semantics. PR #275 additionally added a recursive-copy fallback for robustness. + +--- + +## 37. Don't swallow `OperationCanceledException` in catch-all blocks + +**PR #274** (Copilot reviewer, threads 34–35) — `DiscoverAsync` and `FetchChecksumAsync` caught all exceptions, including `OperationCanceledException`, converting cancellation into empty results or null checksums instead of properly propagating it. + +> Copilot: "`DiscoverAsync` catches all exceptions inside the per-version loop, which will also swallow `OperationCanceledException`... That breaks expected cancellation semantics." + +The fix was to explicitly catch and rethrow `OperationCanceledException` before the general `catch (Exception)` block. + +**Why it matters:** Callers who pass a `CancellationToken` expect cancellation to propagate as `OperationCanceledException` or `TaskCanceledException`. Swallowing it means the caller gets wrong results (empty list, null checksum) instead of a proper cancellation signal. This breaks `async`/`await` contracts. + +--- + +## 38. Rollback on extraction failure — don't delete the backup too early + +**PR #274** (Copilot reviewer, threads 15, 21, 36) — The original code deleted the backup of the previous JDK immediately after moving the new one into place. If validation or a later step failed, the user was left with no working JDK. + +> Copilot: "If InstallAsync later fails validation (or any post-extraction step throws), the previous JDK at targetPath has already been permanently deleted, leaving the user with no working JDK." + +The fix was to keep the backup until after validation succeeds, with a `CommitMove` step that only deletes the backup on confirmed success. + +**Why it matters:** Safe replacement of installed software requires a two-phase commit: (1) move new into place, (2) validate, (3) delete backup only on success. Deleting the backup before validation turns a recoverable failure into data loss. + +--- + +## 39. Command injection in elevated `.cmd` scripts + +**PR #275** (Copilot reviewer, thread 67) — The elevated execution path on Windows wrote a `.cmd` script that interpolated package arguments directly into a command line run under `cmd.exe`. Characters like `&`, `|`, `>` in package names could escape the command. + +> Copilot: "The elevated path writes a .cmd script that interpolates `arguments` directly into a command line. Because this runs under `cmd.exe`, special characters... can lead to command injection, which is especially risky when running elevated." + +The fix added `SanitizeCmdArgument()` plus argument validation that rejects dangerous characters. + +**Why it matters:** Any code that generates shell commands from variable inputs is a command injection risk. This is doubly dangerous when the command runs elevated (admin/root). Always sanitize or use `ArgumentList` (which bypasses the shell entirely). + +--- + +## 40. Copilot reviewer was wrong about `ANDROID_HOME` vs `ANDROID_SDK_ROOT` + +**PR #275** (thread 42) — The Copilot reviewer incorrectly stated that `ANDROID_SDK_ROOT` is the recommended variable and `ANDROID_HOME` is deprecated — the exact opposite of the truth per [Android documentation](https://developer.android.com/tools/variables#envar). + +> Copilot: "These docs describe `ANDROID_HOME` as the preferred variable and `ANDROID_SDK_ROOT` as the older one, but Android tooling guidance is the opposite." +> +> @rmarinho: "Copilot is wrong here right?" +> +> @jonathanpeppers: Added guidance to `copilot-instructions.md` to prevent this confusion. + +**Why it matters:** AI reviewers can be confidently wrong about domain-specific facts. In this case, the Copilot reviewer would have introduced a regression by swapping to a deprecated variable. Human reviewers must verify AI claims against authoritative sources, especially for environment/configuration decisions. + +--- + +## 41. Network tests are acceptable — don't over-mock + +**PR #275** (thread 44) — The Copilot reviewer suggested replacing network-calling tests with mocked/faked alternatives for CI reliability. + +> Copilot: "This test performs a real network call to the manifest feed... Prefer a unit test that injects/fakes the HTTP response." +> +> @jonathanpeppers: "Network calls are fine in these tests." + +**Why it matters:** Not every Copilot suggestion should be accepted. Integration tests that hit real endpoints catch real problems (API changes, format changes, certificate issues) that mocks never will. The tests already use `Assert.Ignore` on network failure for CI resilience. Over-mocking creates tests that pass but don't actually verify the system works. + +--- + +## 42. Don't reinvent existing infrastructure — AI ignores what's already there + +**PRs #281–284** — The AI created a brand-new `AndroidToolRunner` class that wrapped `System.Diagnostics.Process` with timeout, cancellation, and output capture — duplicating everything `ProcessUtils` already provided. Jonathan left the same comment on PRs #282, #283, and #284: + +> "This is inventing lots of new code that just wraps System.Diagnostics.Process. Can we just use the existing code for this instead? ProcessUtils.cs. This is adding lots of more lines of code to maintain, and it's like the AI didn't even look at what is already here." + +**Why it matters:** This is the single most expensive AI code review pattern: the AI generates hundreds of lines of plausible-looking code that duplicates existing functionality. It compiles, it passes tests, but it creates a parallel maintenance burden and diverges from established patterns. Reviewers must check whether existing utilities already solve the problem before approving new infrastructure. + +--- + +## 43. Port code from downstream consumers — don't rewrite + +**PRs #283, #284** — Jonathan asked to port the device-listing logic from `dotnet/android`'s `GetAvailableAndroidDevices` MSBuild task and the emulator boot logic from `BootAndroidEmulator`, rather than writing new implementations. + +> (PR #283): "Open a draft dotnet/android PR that updates its submodule to the branch of #283... Review/merge dotnet/android-tools. Bring dotnet/android out of draft, switch to dotnet/android-tools/main. Merge it second." + +**Why it matters:** The downstream consumer (`dotnet/android`) already has battle-tested parsing and boot logic with real-world edge cases handled. Rewriting it means losing those edge cases and creating two implementations that can diverge. Porting preserves the institutional knowledge embedded in the existing code. + +--- + +## 44. Prove code sharing with a draft downstream PR before merging + +**PRs #283, #284** — Jonathan required a specific merge workflow: (1) create the shared library code in `android-tools`, (2) open a draft PR in `dotnet/android` that uses the new APIs via the submodule, (3) merge `android-tools` first, (4) update the submodule pointer and merge `dotnet/android` second. + +**Why it matters:** A shared library that isn't actually consumed by its intended consumer is speculative code. The draft downstream PR proves the API surface actually works, catches design mismatches early, and demonstrates that the shared code reduces (not increases) total code. It also makes the review concrete — the reviewer can see both sides of the change. + +--- + +## 45. Check exit codes consistently across all operations + +**PR #283** (Copilot reviewer, thread 27) — `StopEmulatorAsync` didn't check the adb exit code, while `ListDevicesAsync` and `WaitForDeviceAsync` did. This inconsistency meant stop failures were silent. + +> Copilot: "`StopEmulatorAsync` doesn't check the adb exit code, so it can succeed silently even if `adb -s emu kill` fails." + +Fixed by adding `ThrowIfFailed` consistently. + +**Why it matters:** Inconsistent error handling is worse than no error handling — it creates a false sense of safety. If some operations check exit codes and others don't, developers assume all operations are checked. Apply the same error-handling pattern (like `ThrowIfFailed`) to every process invocation. + +--- + +## 46. New helper methods should default to `internal` + +**PR #283** (Copilot reviewer, thread 29) — `ProcessUtils.ThrowIfFailed` was added as `public` even though it was only used internally. + +> Copilot: "`ThrowIfFailed` is introduced as a new `public` API on `ProcessUtils`, but it's only used internally in this PR. Given the repo convention to keep the public API minimal, consider making this `internal`." + +**Why it matters:** Every `public` method is a compatibility contract. Once external consumers depend on it, you can't change the signature without a breaking change. Default to `internal` and promote to `public` only when a confirmed external consumer needs it. This repo uses `InternalsVisibleTo` for test access. + +--- + +## 47. Return `IReadOnlyList`, not `List`, from public APIs + +**PR #284** (Copilot reviewer) — Public methods returned `Task>`, exposing a mutable concrete collection. + +> Copilot: "Public API returns `Task>`, which exposes a mutable concrete collection. Consider returning `Task>` to avoid leaking mutability." + +**Why it matters:** Returning `List` lets callers mutate the collection, potentially corrupting internal state if the list is cached. `IReadOnlyList` communicates intent ("you can read this, not change it") and allows the implementation to switch to arrays, immutable lists, or other backing stores without breaking callers. + +--- + +## 48. Don't redirect stdout/stderr on background processes without draining + +**PR #284** (Copilot reviewer) — `StartBackground` set `RedirectStandardOutput = true` and `RedirectStandardError = true` but never started async readers to drain the output. + +> Copilot: "`StartBackground` sets `RedirectStandardOutput/RedirectStandardError = true` but does not start any readers. This can cause the emulator process to block once stdout/stderr buffers fill up." + +**Why it matters:** Redirected output goes to an OS pipe buffer (typically 4-64 KB). When the buffer fills, the child process blocks on its next write. For long-running processes like the Android emulator, this means the process silently hangs. Either don't redirect (set to `false`) or immediately start async readers. + +--- + +## 49. `CancellationToken` accepted but never observed is a broken contract + +**PRs #281, #284** (Copilot reviewer) — `AndroidToolRunner.Run()` accepted a `CancellationToken` parameter but never checked it or passed it to any downstream operation. + +> Copilot: "`Run` method accepts a `cancellationToken` parameter but never uses it. The token should be checked before starting the process and should trigger process termination when cancelled." + +**Why it matters:** An unused `CancellationToken` parameter is an API lie. Callers write `await runner.Run(..., cts.Token)` expecting cancellation to work, then wonder why the process keeps running after they cancel. Either honor the token (register a callback to kill the process) or don't accept it. + +--- + +## 50. Take structured argument lists, not interpolated strings + +**PR #284** (Copilot reviewer) — `StartAvd` built a single argument string by interpolating `avdName` and appending `additionalArgs` verbatim, risking argument injection. + +> Copilot: "If `avdName` contains quotes/whitespace (or `additionalArgs` comes from untrusted input), this can break parsing or allow argument injection. Consider... taking additional args as a structured list (`IEnumerable`)." + +**Why it matters:** String interpolation into process arguments is the process-launch equivalent of SQL injection. Use `ProcessUtils.CreateProcessStartInfo()` which populates `ArgumentList` (safe, no shell parsing) on net5+ and falls back to proper escaping on netstandard2.0. Accept `IEnumerable` instead of `string` for additional arguments. + +--- + +## 51. Doc comments must match actual behavior + +**PR #283** (Copilot reviewer, thread 28) — A file-level doc comment said "bootstraps cmdline-tools" but the code only bootstrapped when `ANDROID_HOME` was missing — it never checked for cmdline-tools specifically. + +> Copilot: "The file-level doc comment says 'When the pre-installed SDK lacks cmdline-tools, the tests bootstrap them', but `OneTimeSetUp` currently only bootstraps when `ANDROID_HOME` is missing/invalid." + +**Why it matters:** Inaccurate doc comments are worse than no doc comments — they actively mislead. When AI generates both code and documentation, the docs often describe the *intended* behavior rather than the *actual* behavior. Review doc comments against the code, not against the PR description. + +--- + +## Summary of Themes + +| Theme | Occurrences | Key Lesson | +|-------|------------|------------| +| **AI reinvents the wheel** | AndroidToolRunner vs ProcessUtils, rewriting downstream logic | Check existing code FIRST — AI doesn't look at what's already there | +| **AI over-engineers** | HttpClient injection, IsElevated, speculative code | Remove code until you need it (YAGNI) | +| **AI ignores target framework** | netstandard2.0, new lang features | Always check API availability against the lowest TFM | +| **AI swallows errors** | Empty catch blocks, chmod failure, checksumType, OperationCanceledException, unchecked exit codes | Fail fast with clear errors; apply error handling consistently | +| **AI generates sloppy structure** | One type per file, #region, empty lines, naming, `public` when `internal` suffices | Encode conventions in copilot-instructions.md | +| **API design** | `List` vs `IReadOnlyList`, unused CancellationToken, string args vs structured args | Public APIs are contracts — get them right the first time | +| **Performance awareness** | ArrayPool, XmlReader, p/invoke, list merging, cached arrays | Small allocations add up in library code | +| **Security & correctness** | Zip Slip, command injection, path traversal, mandatory checksums, license consent | Libraries must be correct by default | +| **AI reviewer can be wrong** | ANDROID_HOME vs ANDROID_SDK_ROOT, over-mocking | Always verify AI claims against authoritative docs | +| **AI docs diverge from code** | Doc comments describing intended not actual behavior | Review docs against the code, not the PR description | +| **Rollback & resilience** | Cross-volume moves, backup-before-replace, cross-device fallback, stdout buffer deadlocks | Assume failure at every step; design for recovery | +| **Code sharing workflow** | Port from downstream, draft PR before merge, submodule coordination | Prove shared code works with its consumer before merging | +| **Reviewer wrote code too** | 11 fix-up commits on PR #274, 437 lines of new tests | Good review includes hands-on fixes, not just comments | From b875bd10c02180116d16596a99ea3ae9abf08233 Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Wed, 4 Mar 2026 17:12:23 -0600 Subject: [PATCH 239/308] [skills] new findings for `android-tools-reviewer` skill New findings: - `#52`: Never use null-forgiving operator (!) - `#53`: Add overloads to reduce caller ceremony - `#54`: Delete unused overloads - `#55`: Prefer C# pattern matching - `#56`: Section-separator comments are `#region` in disguise Updated `review-rules.md`: extended `#region` ban to cover banner comments, added null-forgiving `!` as AI anti-pattern, added overload and pattern matching rules to API Design section. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../references/review-rules.md | 15 +++-- .../docs/CODE_REVIEW_POSTMORTEM.md | 60 ++++++++++++++++++- 2 files changed, 66 insertions(+), 9 deletions(-) diff --git a/external/xamarin-android-tools/.github/skills/android-tools-reviewer/references/review-rules.md b/external/xamarin-android-tools/.github/skills/android-tools-reviewer/references/review-rules.md index 4b2583f7b75..4d7d4b940db 100644 --- a/external/xamarin-android-tools/.github/skills/android-tools-reviewer/references/review-rules.md +++ b/external/xamarin-android-tools/.github/skills/android-tools-reviewer/references/review-rules.md @@ -1,6 +1,6 @@ # Android Tools Review Rules -Distilled from [CODE_REVIEW_POSTMORTEM.md](../../../docs/CODE_REVIEW_POSTMORTEM.md) — 51 findings +Distilled from [CODE_REVIEW_POSTMORTEM.md](../../../docs/CODE_REVIEW_POSTMORTEM.md) — 56 findings from reviews by @jonathanpeppers on PRs #274, #275, #281–#284. --- @@ -77,11 +77,11 @@ Framework. Every API call must work on both targets. |-------|-----------------| | **One type per file** | Each public class, struct, enum, or interface must be in its own `.cs` file named after the type. No multiple top-level types in a single file. | | **File-scoped namespaces** | New files should use `namespace Foo;` (not `namespace Foo { ... }`). Don't reformat existing files. | -| **No #region directives** | `#region` hides code and makes reviews harder. Remove them. | +| **No #region directives** | `#region` hides code and makes reviews harder. Remove them. This also applies to banner/section-separator comments (e.g., `// --- Device Tests ---`) — they serve the same purpose as `#region` and signal the file should be split instead. | | **Use `record` for data types** | Immutable data-carrier types (progress, version info, license info) should be `record` types. They get value equality, `ToString()`, and deconstruction for free. | | **Remove unused code** | Dead methods, speculative helpers, and code "for later" should be removed. Ship only what's needed. | -**Postmortem refs:** #9, #12, #25, #28 +**Postmortem refs:** #9, #12, #25, #28, #56 --- @@ -133,7 +133,7 @@ These are patterns that AI-generated code consistently gets wrong: | Pattern | What to watch for | |---------|------------------| | **Reinventing the wheel** | AI creates new infrastructure (e.g., `AndroidToolRunner`) instead of using existing utilities (`ProcessUtils`). ALWAYS check if a similar utility exists before accepting new wrapper code. This is the most expensive AI pattern — hundreds of lines of plausible code that duplicates what's already there. | -| **Over-engineering** | HttpClient injection "for testability", elevation auto-detection, speculative helper classes. If no caller needs it today, remove it. | +| **Over-engineering** | HttpClient injection "for testability", elevation auto-detection, speculative helper classes, unused overloads. If no caller needs it today, remove it. | | **Swallowed errors** | AI catch blocks love to eat exceptions silently. Check EVERY catch block. Also check that exit codes are checked consistently — if `ListDevicesAsync` checks exit codes, `StopEmulatorAsync` should too. | | **Ignoring target framework** | AI generates code for the newest .NET. Check every API call against netstandard2.0. | | **Sloppy structure** | Multiple types in one file, block-scoped namespaces, #region directives, classes where records would do. New helpers marked `public` when `internal` suffices. | @@ -141,8 +141,9 @@ These are patterns that AI-generated code consistently gets wrong: | **Over-mocking** | Not everything needs to be mocked. Network integration tests with `Assert.Ignore` on failure are fine and catch real API changes that mocks never will. | | **Docs describe intent not reality** | AI doc comments often describe what the code *should* do, not what it *actually* does. Review doc comments against the implementation. | | **Unused parameters** | AI adds `CancellationToken` parameters but never observes them, or accepts `additionalArgs` as a string and interpolates it into a command. Unused CancellationToken is a broken contract; string args are injection risks. | +| **Null-forgiving operator (`!`)** | Never use `!` to suppress nullable warnings. If the value can be null, add a proper null check. If it can't be null, make the parameter/variable non-nullable. AI frequently sprinkles `!` to make the compiler happy — this turns compile-time warnings into runtime `NullReferenceException`s. Use `IsNullOrEmpty()` extension methods or null-coalescing instead. | -**Postmortem refs:** #7, #28, #29, #40, #41, #42, #49, #50, #51 +**Postmortem refs:** #7, #28, #29, #40, #41, #42, #49, #50, #51, #52, #54 --- @@ -154,8 +155,10 @@ These are patterns that AI-generated code consistently gets wrong: | **New helpers default to `internal`** | New utility methods should be `internal` unless a confirmed external consumer (e.g., `dotnet/android`) needs them. Use `InternalsVisibleTo` for test access. | | **Structured args, not string interpolation** | Additional arguments to processes should be `IEnumerable`, not a single `string` that gets interpolated. Use `ProcessUtils.CreateProcessStartInfo()` which handles `ArgumentList` safely. | | **Honor `CancellationToken`** | If a method accepts a `CancellationToken`, it MUST observe it — register a callback to kill processes, check `IsCancellationRequested` in loops, pass it to downstream async calls. Don't just accept it for API completeness. | +| **Add overloads to reduce caller ceremony** | If every caller performs the same conversion before calling a method (e.g., `writer.ToString()` before `ThrowIfFailed()`), the method should have an overload that accepts the unconverted type directly. | +| **Prefer C# pattern matching** | Use `is`, `switch` expressions, and property patterns instead of `if`/`else` type-check chains. Pattern matching is more concise, avoids casts, and enables exhaustiveness checks. | -**Postmortem refs:** #46, #47, #49, #50 +**Postmortem refs:** #46, #47, #49, #50, #53, #55 --- diff --git a/external/xamarin-android-tools/docs/CODE_REVIEW_POSTMORTEM.md b/external/xamarin-android-tools/docs/CODE_REVIEW_POSTMORTEM.md index b1cad7bee92..8fad7f2c2aa 100644 --- a/external/xamarin-android-tools/docs/CODE_REVIEW_POSTMORTEM.md +++ b/external/xamarin-android-tools/docs/CODE_REVIEW_POSTMORTEM.md @@ -541,16 +541,70 @@ Fixed by adding `ThrowIfFailed` consistently. --- +## 52. Never use the null-forgiving operator (`!`) + +**PR #283** (review 3892527868, `AdbRunner.cs:195` and `AndroidEnvironmentHelper.cs:20`) — Code had parameters typed as nullable but then used `!` to suppress warnings at call sites. Jonathan flagged both spots. + +> "We should avoid using `!`, do you even need it if you use our `IsNullOrEmpty()` extension method?" + +> "Why are the paths allowed to be null? Then we have `!` below? Can we make sure `copilot-instructions.md` says to never use `!` and to actually fix nullable warnings correctly?" + +**Why it matters:** The null-forgiving operator `!` silences the compiler without fixing the bug. If a value can actually be null at runtime, `!` turns a compile-time warning into a `NullReferenceException`. The correct fix is to either make the parameter non-nullable (if null is never valid) or add a proper null check. AI frequently sprinkles `!` to make warnings disappear. + +--- + +## 53. Add overloads to reduce caller ceremony + +**PR #283** (review 3892527868, `AdbRunner.cs:207`) — Multiple callers were calling `writer.ToString()` before passing to `ThrowIfFailed()`. Jonathan suggested adding a `StringWriter` overload instead. + +> "Should `ThrowIfFailed()` just have an overload for `StringWriter` and you wouldn't have to call `ToString()` from callers?" + +**Why it matters:** When every caller has to perform the same conversion before calling a method, the conversion belongs inside the method (or an overload). This reduces boilerplate, eliminates a class of copy-paste errors, and makes the API easier to use correctly. + +--- + +## 54. Delete unused overloads + +**PR #283** (review 3892527868, `AdbRunner.cs:233`) — An overload existed but wasn't needed. + +> "Can we delete this overload?" + +**Why it matters:** Dead code is a maintenance burden and a trap for readers who assume it exists for a reason. AI tends to generate speculative overloads "for completeness" — review each public method and ask whether any caller actually needs it. + +--- + +## 55. Prefer C# pattern matching + +**PR #283** (review 3892527868, `AdbRunner.cs:312`) — Code used traditional `if`/`else` chains to check types or values. Jonathan asked for pattern matching and to update `copilot-instructions.md`. + +> "Can this use C# pattern matching, and update `copilot-instructions.md` to do this for all new code?" + +**Why it matters:** Pattern matching (`is`, `switch` expressions, property patterns) is more concise, avoids unnecessary casts, and makes exhaustiveness checks possible. Encoding this in `copilot-instructions.md` prevents AI from generating old-style conditional chains. + +--- + +## 56. Section-separator comments are `#region` in disguise + +**PR #283** (review 3892527868, `RunnerIntegrationTests.cs:151`) — Test files had block comments acting as visual section dividers between groups of tests. + +> "It doesn't seem like we should be writing comments like this, they are basically `#region` and `#endregion`" + +**Why it matters:** This extends the `#region` ban (Finding #12). Any mechanism to partition code into visual "sections" — whether `#region`, banner comments, or ASCII art dividers — signals the file should be split or the test organization rethought. AI loves generating these as pseudo-structure. + +--- + ## Summary of Themes | Theme | Occurrences | Key Lesson | |-------|------------|------------| | **AI reinvents the wheel** | AndroidToolRunner vs ProcessUtils, rewriting downstream logic | Check existing code FIRST — AI doesn't look at what's already there | -| **AI over-engineers** | HttpClient injection, IsElevated, speculative code | Remove code until you need it (YAGNI) | +| **AI over-engineers** | HttpClient injection, IsElevated, speculative code, unused overloads | Remove code until you need it (YAGNI) | | **AI ignores target framework** | netstandard2.0, new lang features | Always check API availability against the lowest TFM | | **AI swallows errors** | Empty catch blocks, chmod failure, checksumType, OperationCanceledException, unchecked exit codes | Fail fast with clear errors; apply error handling consistently | -| **AI generates sloppy structure** | One type per file, #region, empty lines, naming, `public` when `internal` suffices | Encode conventions in copilot-instructions.md | -| **API design** | `List` vs `IReadOnlyList`, unused CancellationToken, string args vs structured args | Public APIs are contracts — get them right the first time | +| **AI generates sloppy structure** | One type per file, #region, section-separator comments, empty lines, naming, `public` when `internal` suffices | Encode conventions in copilot-instructions.md | +| **AI fights the type system** | Null-forgiving `!`, nullable params that shouldn't be, `!` to silence warnings | Fix nullability at the source; never use `!` | +| **API design** | `List` vs `IReadOnlyList`, unused CancellationToken, string args vs structured args, missing overloads | Public APIs are contracts — get them right the first time | +| **Modern C# idioms** | Pattern matching, records, file-scoped namespaces | Use modern syntax when available on the target framework | | **Performance awareness** | ArrayPool, XmlReader, p/invoke, list merging, cached arrays | Small allocations add up in library code | | **Security & correctness** | Zip Slip, command injection, path traversal, mandatory checksums, license consent | Libraries must be correct by default | | **AI reviewer can be wrong** | ANDROID_HOME vs ANDROID_SDK_ROOT, over-mocking | Always verify AI claims against authoritative docs | From 86eb83fac6c142c5d9a5ebc594950a551e934400 Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Thu, 5 Mar 2026 08:47:26 -0600 Subject: [PATCH 240/308] [skills] Fix link path in review rules document --- .../skills/android-tools-reviewer/references/review-rules.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/xamarin-android-tools/.github/skills/android-tools-reviewer/references/review-rules.md b/external/xamarin-android-tools/.github/skills/android-tools-reviewer/references/review-rules.md index 4d7d4b940db..76ab33a241b 100644 --- a/external/xamarin-android-tools/.github/skills/android-tools-reviewer/references/review-rules.md +++ b/external/xamarin-android-tools/.github/skills/android-tools-reviewer/references/review-rules.md @@ -1,6 +1,6 @@ # Android Tools Review Rules -Distilled from [CODE_REVIEW_POSTMORTEM.md](../../../docs/CODE_REVIEW_POSTMORTEM.md) — 56 findings +Distilled from [CODE_REVIEW_POSTMORTEM.md](../../docs/CODE_REVIEW_POSTMORTEM.md) — 56 findings from reviews by @jonathanpeppers on PRs #274, #275, #281–#284. --- From 170aa06016651cb9f44e2fd7566502f242f22487 Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Thu, 5 Mar 2026 08:48:35 -0600 Subject: [PATCH 241/308] [skills] Update link path in review rules document, take 2 --- .../skills/android-tools-reviewer/references/review-rules.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/xamarin-android-tools/.github/skills/android-tools-reviewer/references/review-rules.md b/external/xamarin-android-tools/.github/skills/android-tools-reviewer/references/review-rules.md index 76ab33a241b..f1a9b67e2dc 100644 --- a/external/xamarin-android-tools/.github/skills/android-tools-reviewer/references/review-rules.md +++ b/external/xamarin-android-tools/.github/skills/android-tools-reviewer/references/review-rules.md @@ -1,6 +1,6 @@ # Android Tools Review Rules -Distilled from [CODE_REVIEW_POSTMORTEM.md](../../docs/CODE_REVIEW_POSTMORTEM.md) — 56 findings +Distilled from [CODE_REVIEW_POSTMORTEM.md](../../../../docs/CODE_REVIEW_POSTMORTEM.md) — 56 findings from reviews by @jonathanpeppers on PRs #274, #275, #281–#284. --- From 669b4b6d5f857bba754c7da4a8df3c03befa781b Mon Sep 17 00:00:00 2001 From: Rui Marinho Date: Fri, 6 Mar 2026 14:59:24 +0000 Subject: [PATCH 242/308] Add `AdbRunner` for `adb` CLI operations (#283) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Wraps `adb` CLI operations for device management. Addresses #279. Parsing/formatting/merging logic ported from `dotnet/android` `GetAvailableAndroidDevices` MSBuild task, enabling code sharing via the `external/xamarin-android-tools` submodule. See draft PR: https://github.com/dotnet/android/pull/10880 ## Public API ```csharp public class AdbRunner { // Constructor — requires full path to adb executable public AdbRunner(string adbPath, IDictionary? environmentVariables = null); // Instance methods (async, invoke adb process) public Task> ListDevicesAsync(CancellationToken ct = default); public Task WaitForDeviceAsync(string? serial = null, TimeSpan? timeout = null, CancellationToken ct = default); public Task StopEmulatorAsync(string serial, CancellationToken ct = default); // Static helpers — public so dotnet/android can call without instantiating AdbRunner public static List ParseAdbDevicesOutput(IEnumerable lines); public static AdbDeviceStatus MapAdbStateToStatus(string adbState); public static string BuildDeviceDescription(AdbDeviceInfo device, Action? logger = null); public static string FormatDisplayName(string avdName); public static List MergeDevicesAndEmulators( IReadOnlyList adbDevices, IReadOnlyList availableEmulators, Action? logger = null); } ``` **Internal methods** (not part of public API): - `GetEmulatorAvdNameAsync` — queries AVD name via `adb emu avd name` with TCP console fallback - `ProcessUtils.ThrowIfFailed` — shared exit code validation (string and StringWriter overloads) ## Key Design Decisions - **Constructor takes `string adbPath`**: Callers pass the resolved path; no lazy `Func<>` indirection. Optional `environmentVariables` dictionary for ANDROID_HOME/JAVA_HOME/PATH. - **Static parsing methods** are `public static` so `dotnet/android` can call them without instantiating `AdbRunner` (e.g., `GetAvailableAndroidDevices` MSBuild task passes `List` to `ParseAdbDevicesOutput`) - **`IEnumerable` overload**: `dotnet/android` passes `List` directly from output lines - **Logger parameter**: `BuildDeviceDescription` and `MergeDevicesAndEmulators` accept `Action?` — `dotnet/android` passes `this.CreateTaskLogger()` for MSBuild trace output - **Regex with explicit state list**: Uses `\s+` separator to match one or more whitespace characters (spaces or tabs). Matches explicit known states with `IgnoreCase`. Daemon startup lines (`*`) are pre-filtered. - **Exit code checking**: `ListDevicesAsync`, `WaitForDeviceAsync`, and `StopEmulatorAsync` throw `InvalidOperationException` with stderr context on non-zero exit via `ProcessUtils.ThrowIfFailed` (internal) - **`MapAdbStateToStatus` as switch expression**: Simple value mapping uses C# switch expression for conciseness - **Property patterns instead of null-forgiving**: Uses `is { Length: > 0 }` patterns throughout for null checks on `netstandard2.0` where `string.IsNullOrEmpty()` lacks `[NotNullWhen(false)]` - **FormatDisplayName**: Lowercases before `ToTitleCase` to normalize mixed-case input (e.g., "PiXeL" → "Pixel") - **Environment variables via `StartProcess`**: Runners pass env vars dictionary to `ProcessUtils.StartProcess`. `AndroidEnvironmentHelper.GetEnvironmentVariables()` builds the dict. ## Tests **45 unit tests** (`AdbRunnerTests.cs`): - **ParseAdbDevicesOutput**: real-world data, empty output, single/multiple devices, mixed states, daemon messages, IP:port, Windows newlines, recovery/sideload, tab-separated output - **FormatDisplayName**: underscores, title case, API capitalization, mixed case, special chars, empty - **MapAdbStateToStatus**: all known states + unknown (recovery, sideload) - **MergeDevicesAndEmulators**: no emulators, no running, mixed, case-insensitive dedup, sorting - **Constructor**: valid path, null/empty throws - **WaitForDeviceAsync**: timeout validation (negative, zero) **4 integration tests** (`RunnerIntegrationTests.cs`): - Run only when `TF_BUILD=True` or `CI=true` (case-insensitive truthy check), skipped locally - Require pre-installed JDK (`JAVA_HOME`) and Android SDK (`ANDROID_HOME`) on CI agent - `Assert.Ignore` when `ANDROID_HOME` missing (no bootstrap/network dependency) - Cover: constructor, `ListDevicesAsync`, `WaitForDeviceAsync` timeout, tool discovery Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Jonathan Peppers --- .../.github/copilot-instructions.md | 2 + .../EnvironmentVariableNames.cs | 12 + .../Models/AdbDeviceInfo.cs | 62 ++ .../Models/AdbDeviceStatus.cs | 17 + .../Models/AdbDeviceType.cs | 13 + .../ProcessUtils.cs | 27 + .../Runners/AdbRunner.cs | 309 +++++++++ .../Runners/AndroidEnvironmentHelper.cs | 40 ++ .../SdkManager.Licenses.cs | 2 +- .../SdkManager.Process.cs | 19 +- .../AdbRunnerTests.cs | 633 ++++++++++++++++++ .../RunnerIntegrationTests.cs | 127 ++++ 12 files changed, 1244 insertions(+), 19 deletions(-) create mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Models/AdbDeviceInfo.cs create mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Models/AdbDeviceStatus.cs create mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Models/AdbDeviceType.cs create mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Runners/AdbRunner.cs create mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Runners/AndroidEnvironmentHelper.cs create mode 100644 external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/AdbRunnerTests.cs create mode 100644 external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/RunnerIntegrationTests.cs diff --git a/external/xamarin-android-tools/.github/copilot-instructions.md b/external/xamarin-android-tools/.github/copilot-instructions.md index 582d2a8f6d0..e2765ace979 100644 --- a/external/xamarin-android-tools/.github/copilot-instructions.md +++ b/external/xamarin-android-tools/.github/copilot-instructions.md @@ -49,6 +49,8 @@ When setting environment variables for SDK tools (e.g. `sdkmanager`, `avdmanager - **File-scoped namespaces**: all new files should use file-scoped namespaces (`namespace Foo;` instead of `namespace Foo { ... }`). - **Static `HttpClient`**: `HttpClient` instances must be `static` to avoid socket exhaustion. See [HttpClient guidelines](https://learn.microsoft.com/dotnet/fundamentals/networking/http/httpclient-guidelines#recommended-use). Do not create per-instance `HttpClient` fields or dispose them in `IDisposable`. - [Mono Coding Guidelines](http://www.mono-project.com/community/contributing/coding-guidelines/): tabs, K&R braces, `PascalCase` public members. +- **No null-forgiving operator (`!`)**: do not use the null-forgiving operator after null checks. Instead, use C# property patterns (e.g. `if (value is { Length: > 0 } v)`) which give the compiler proper non-null flow analysis on all target frameworks including `netstandard2.0`. +- **Prefer switch expressions**: use C# switch expressions over switch statements for simple value mappings (e.g. `return state switch { "x" => A, _ => B };`). Use switch statements only when the body has side effects or complex logic. - Nullable enabled in `AndroidSdk`. `NullableAttributes.cs` excluded on `net10.0+`. - Strong-named via `product.snk`. In the AndroidSdk project, tests use `InternalsVisibleTo` with full public key (`Properties/AssemblyInfo.cs`). - Assembly names support `$(VendorPrefix)`/`$(VendorSuffix)` for branding forks. diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/EnvironmentVariableNames.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/EnvironmentVariableNames.cs index 272a17b1db6..1ea607f0582 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/EnvironmentVariableNames.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/EnvironmentVariableNames.cs @@ -40,5 +40,17 @@ internal static class EnvironmentVariableNames /// Executable file extensions (Windows). /// public const string PathExt = "PATHEXT"; + + /// + /// Overrides the default location for Android user-specific data + /// (AVDs, preferences, etc.). Defaults to $HOME/.android. + /// + public const string AndroidUserHome = "ANDROID_USER_HOME"; + + /// + /// Overrides the AVD storage directory. Takes precedence over + /// /avd. + /// + public const string AndroidAvdHome = "ANDROID_AVD_HOME"; } } diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Models/AdbDeviceInfo.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Models/AdbDeviceInfo.cs new file mode 100644 index 00000000000..a14941ee5f1 --- /dev/null +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Models/AdbDeviceInfo.cs @@ -0,0 +1,62 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Xamarin.Android.Tools; + +/// +/// Represents an Android device or emulator from 'adb devices -l' output. +/// Mirrors the metadata produced by dotnet/android's GetAvailableAndroidDevices task. +/// +public class AdbDeviceInfo +{ + /// + /// Serial number of the device (e.g., "emulator-5554", "0A041FDD400327"). + /// For non-running emulators, this is the AVD name. + /// + public string Serial { get; set; } = string.Empty; + + /// + /// Human-friendly description of the device (e.g., "Pixel 7 API 35", "Pixel 6 Pro"). + /// + public string Description { get; set; } = string.Empty; + + /// + /// Device type: Device or Emulator. + /// + public AdbDeviceType Type { get; set; } + + /// + /// Device status: Online, Offline, Unauthorized, NoPermissions, NotRunning, Unknown. + /// + public AdbDeviceStatus Status { get; set; } + + /// + /// AVD name for emulators (e.g., "pixel_7_api_35"). Null for physical devices. + /// + public string? AvdName { get; set; } + + /// + /// Device model from adb properties (e.g., "Pixel_6_Pro"). + /// + public string? Model { get; set; } + + /// + /// Product name from adb properties (e.g., "raven"). + /// + public string? Product { get; set; } + + /// + /// Device code name from adb properties (e.g., "raven"). + /// + public string? Device { get; set; } + + /// + /// Transport ID from adb properties. + /// + public string? TransportId { get; set; } + + /// + /// Whether this device is an emulator. + /// + public bool IsEmulator => Type == AdbDeviceType.Emulator; +} diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Models/AdbDeviceStatus.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Models/AdbDeviceStatus.cs new file mode 100644 index 00000000000..18b51a4917e --- /dev/null +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Models/AdbDeviceStatus.cs @@ -0,0 +1,17 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Xamarin.Android.Tools; + +/// +/// Represents the status of an Android device. +/// +public enum AdbDeviceStatus +{ + Online, + Offline, + Unauthorized, + NoPermissions, + NotRunning, + Unknown +} diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Models/AdbDeviceType.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Models/AdbDeviceType.cs new file mode 100644 index 00000000000..7662d1b3052 --- /dev/null +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Models/AdbDeviceType.cs @@ -0,0 +1,13 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Xamarin.Android.Tools; + +/// +/// Represents the type of an Android device. +/// +public enum AdbDeviceType +{ + Device, + Emulator +} diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/ProcessUtils.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/ProcessUtils.cs index 28113d7272e..8527ccd5da5 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/ProcessUtils.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/ProcessUtils.cs @@ -203,6 +203,33 @@ static string JoinArguments (string[] args) } #endif + /// + /// Throws when is non-zero. + /// Includes stderr/stdout context in the message when available. + /// + internal static void ThrowIfFailed (int exitCode, string command, string? stderr = null, string? stdout = null) + { + if (exitCode == 0) + return; + + var message = $"'{command}' failed with exit code {exitCode}."; + + if (stderr is { Length: > 0 }) + message += $" stderr:{Environment.NewLine}{stderr.Trim ()}"; + if (stdout is { Length: > 0 }) + message += $" stdout:{Environment.NewLine}{stdout.Trim ()}"; + + throw new InvalidOperationException (message); + } + + /// + /// Overload that accepts directly so callers don't need to call ToString(). + /// + internal static void ThrowIfFailed (int exitCode, string command, StringWriter? stderr = null, StringWriter? stdout = null) + { + ThrowIfFailed (exitCode, command, stderr?.ToString (), stdout?.ToString ()); + } + internal static IEnumerable FindExecutablesInPath (string executable) { var path = Environment.GetEnvironmentVariable (EnvironmentVariableNames.Path) ?? ""; diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Runners/AdbRunner.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Runners/AdbRunner.cs new file mode 100644 index 00000000000..f321af6dd04 --- /dev/null +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Runners/AdbRunner.cs @@ -0,0 +1,309 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.IO; + +using System.Text.RegularExpressions; +using System.Threading; +using System.Threading.Tasks; + +namespace Xamarin.Android.Tools; + +/// +/// Runs Android Debug Bridge (adb) commands. +/// Parsing logic ported from dotnet/android GetAvailableAndroidDevices task. +/// +public class AdbRunner +{ + readonly string adbPath; + readonly IDictionary? environmentVariables; + + // Pattern to match device lines: [key:value ...] + // Uses \s+ to match one or more whitespace characters (spaces or tabs) between fields. + // Explicit state list prevents false positives from non-device lines. + static readonly Regex AdbDevicesRegex = new Regex ( + @"^([^\s]+)\s+(device|offline|unauthorized|authorizing|no permissions|recovery|sideload|bootloader|connecting|host)\s*(.*)$", + RegexOptions.Compiled | RegexOptions.IgnoreCase); + static readonly Regex ApiRegex = new Regex (@"\bApi\b", RegexOptions.Compiled); + + /// + /// Creates a new AdbRunner with the full path to the adb executable. + /// + /// Full path to the adb executable (e.g., "/path/to/sdk/platform-tools/adb"). + /// Optional environment variables to pass to adb processes. + public AdbRunner (string adbPath, IDictionary? environmentVariables = null) + { + if (string.IsNullOrWhiteSpace (adbPath)) + throw new ArgumentException ("Path to adb must not be empty.", nameof (adbPath)); + this.adbPath = adbPath; + this.environmentVariables = environmentVariables; + } + + /// + /// Lists connected devices using 'adb devices -l'. + /// For emulators, queries the AVD name using 'adb -s <serial> emu avd name'. + /// + public async Task> ListDevicesAsync (CancellationToken cancellationToken = default) + { + using var stdout = new StringWriter (); + using var stderr = new StringWriter (); + var psi = ProcessUtils.CreateProcessStartInfo (adbPath, "devices", "-l"); + var exitCode = await ProcessUtils.StartProcess (psi, stdout, stderr, cancellationToken, environmentVariables).ConfigureAwait (false); + + ProcessUtils.ThrowIfFailed (exitCode, "adb devices -l", stderr); + + var devices = ParseAdbDevicesOutput (stdout.ToString ().Split ('\n')); + + // For each emulator, try to get the AVD name + foreach (var device in devices) { + if (device.Type == AdbDeviceType.Emulator) { + device.AvdName = await GetEmulatorAvdNameAsync (device.Serial, cancellationToken).ConfigureAwait (false); + device.Description = BuildDeviceDescription (device); + } + } + + return devices; + } + + /// + /// Queries the emulator for its AVD name using 'adb -s <serial> emu avd name'. + /// Returns null if the query fails or produces no output. + /// Ported from dotnet/android GetAvailableAndroidDevices.GetEmulatorAvdName. + /// + internal async Task GetEmulatorAvdNameAsync (string serial, CancellationToken cancellationToken = default) + { + try { + using var stdout = new StringWriter (); + var psi = ProcessUtils.CreateProcessStartInfo (adbPath, "-s", serial, "emu", "avd", "name"); + await ProcessUtils.StartProcess (psi, stdout, null, cancellationToken, environmentVariables).ConfigureAwait (false); + + foreach (var line in stdout.ToString ().Split ('\n')) { + var trimmed = line.Trim (); + if (!string.IsNullOrEmpty (trimmed) && + !string.Equals (trimmed, "OK", StringComparison.OrdinalIgnoreCase)) { + return trimmed; + } + } + } catch (OperationCanceledException) { + throw; + } catch (Exception ex) { + Trace.WriteLine ($"GetEmulatorAvdNameAsync adb query failed for '{serial}': {ex.Message}"); + } + + return null; + } + + public async Task WaitForDeviceAsync (string? serial = null, TimeSpan? timeout = null, CancellationToken cancellationToken = default) + { + var effectiveTimeout = timeout ?? TimeSpan.FromSeconds (60); + + if (effectiveTimeout <= TimeSpan.Zero) + throw new ArgumentOutOfRangeException (nameof (timeout), effectiveTimeout, "Timeout must be a positive value."); + + var args = serial is { Length: > 0 } s + ? new [] { "-s", s, "wait-for-device" } + : new [] { "wait-for-device" }; + + var psi = ProcessUtils.CreateProcessStartInfo (adbPath, args); + + using var cts = CancellationTokenSource.CreateLinkedTokenSource (cancellationToken); + cts.CancelAfter (effectiveTimeout); + + using var stdout = new StringWriter (); + using var stderr = new StringWriter (); + + try { + var exitCode = await ProcessUtils.StartProcess (psi, stdout, stderr, cts.Token, environmentVariables).ConfigureAwait (false); + ProcessUtils.ThrowIfFailed (exitCode, "adb wait-for-device", stderr, stdout); + } catch (OperationCanceledException) when (!cancellationToken.IsCancellationRequested) { + throw new TimeoutException ($"Timed out waiting for device after {effectiveTimeout.TotalSeconds}s."); + } + } + + public async Task StopEmulatorAsync (string serial, CancellationToken cancellationToken = default) + { + if (string.IsNullOrWhiteSpace (serial)) + throw new ArgumentException ("Serial must not be empty.", nameof (serial)); + + var psi = ProcessUtils.CreateProcessStartInfo (adbPath, "-s", serial, "emu", "kill"); + using var stderr = new StringWriter (); + var exitCode = await ProcessUtils.StartProcess (psi, null, stderr, cancellationToken, environmentVariables).ConfigureAwait (false); + ProcessUtils.ThrowIfFailed (exitCode, $"adb -s {serial} emu kill", stderr); + } + + /// + /// Parses the output lines from 'adb devices -l'. + /// Accepts an to avoid allocating a joined string. + /// + public static IReadOnlyList ParseAdbDevicesOutput (IEnumerable lines) + { + var devices = new List (); + + foreach (var line in lines) { + var trimmed = line.Trim (); + if (string.IsNullOrEmpty (trimmed) || + trimmed.IndexOf ("List of devices", StringComparison.OrdinalIgnoreCase) >= 0 || + trimmed.StartsWith ("*", StringComparison.Ordinal)) + continue; + + var match = AdbDevicesRegex.Match (trimmed); + if (!match.Success) + continue; + + var serial = match.Groups [1].Value.Trim (); + var state = match.Groups [2].Value.Trim (); + var properties = match.Groups [3].Value.Trim (); + + // Parse key:value pairs from the properties string + var propDict = new Dictionary (StringComparer.OrdinalIgnoreCase); + if (!string.IsNullOrEmpty (properties)) { + var pairs = properties.Split (new [] { ' ' }, StringSplitOptions.RemoveEmptyEntries); + foreach (var pair in pairs) { + var colonIndex = pair.IndexOf (':'); + if (colonIndex > 0 && colonIndex < pair.Length - 1) { + var key = pair.Substring (0, colonIndex); + var value = pair.Substring (colonIndex + 1); + propDict [key] = value; + } + } + } + + var deviceType = serial.StartsWith ("emulator-", StringComparison.OrdinalIgnoreCase) + ? AdbDeviceType.Emulator + : AdbDeviceType.Device; + + var device = new AdbDeviceInfo { + Serial = serial, + Type = deviceType, + Status = MapAdbStateToStatus (state), + }; + + if (propDict.TryGetValue ("model", out var model)) + device.Model = model; + if (propDict.TryGetValue ("product", out var product)) + device.Product = product; + if (propDict.TryGetValue ("device", out var deviceCodeName)) + device.Device = deviceCodeName; + if (propDict.TryGetValue ("transport_id", out var transportId)) + device.TransportId = transportId; + + // Build description (will be updated later if emulator AVD name is available) + device.Description = BuildDeviceDescription (device); + + devices.Add (device); + } + + return devices; + } + + /// + /// Maps adb device states to status values. + /// Ported from dotnet/android GetAvailableAndroidDevices.MapAdbStateToStatus. + /// + public static AdbDeviceStatus MapAdbStateToStatus (string adbState) => adbState.ToLowerInvariant () switch { + "device" => AdbDeviceStatus.Online, + "offline" => AdbDeviceStatus.Offline, + "unauthorized" => AdbDeviceStatus.Unauthorized, + "no permissions" => AdbDeviceStatus.NoPermissions, + _ => AdbDeviceStatus.Unknown, + }; + + /// + /// Builds a human-friendly description for a device. + /// Priority: AVD name (for emulators) > model > product > device > serial. + /// Ported from dotnet/android GetAvailableAndroidDevices.BuildDeviceDescription. + /// + public static string BuildDeviceDescription (AdbDeviceInfo device, Action? logger = null) + { + if (device.Type == AdbDeviceType.Emulator && device.AvdName is { Length: > 0 } avdName) { + logger?.Invoke (TraceLevel.Verbose, $"Emulator {device.Serial}, original AVD name: {avdName}"); + var formatted = FormatDisplayName (avdName); + logger?.Invoke (TraceLevel.Verbose, $"Emulator {device.Serial}, formatted AVD display name: {formatted}"); + return formatted; + } + + if (device.Model is { Length: > 0 } model) + return model.Replace ('_', ' '); + + if (device.Product is { Length: > 0 } product) + return product.Replace ('_', ' '); + + if (device.Device is { Length: > 0 } deviceName) + return deviceName.Replace ('_', ' '); + + return device.Serial; + } + + /// + /// Formats an AVD name into a user-friendly display name. + /// Replaces underscores with spaces, applies title case, and capitalizes "API". + /// Ported from dotnet/android GetAvailableAndroidDevices.FormatDisplayName. + /// + public static string FormatDisplayName (string avdName) + { + if (string.IsNullOrEmpty (avdName)) + return avdName ?? string.Empty; + + var textInfo = CultureInfo.InvariantCulture.TextInfo; + avdName = textInfo.ToTitleCase (avdName.Replace ('_', ' ').ToLowerInvariant ()); + + // Replace "Api" with "API" + avdName = ApiRegex.Replace (avdName, "API"); + return avdName; + } + + /// + /// Merges devices from adb with available emulators from 'emulator -list-avds'. + /// Running emulators are not duplicated. Non-running emulators are added with Status=NotRunning. + /// Ported from dotnet/android GetAvailableAndroidDevices.MergeDevicesAndEmulators. + /// + public static IReadOnlyList MergeDevicesAndEmulators (IReadOnlyList adbDevices, IReadOnlyList availableEmulators, Action? logger = null) + { + var result = new List (adbDevices); + + // Build a set of AVD names that are already running + var runningAvdNames = new HashSet (StringComparer.OrdinalIgnoreCase); + foreach (var device in adbDevices) { + if (device.AvdName is { Length: > 0 } avdName) + runningAvdNames.Add (avdName); + } + + logger?.Invoke (TraceLevel.Verbose, $"Running emulators AVD names: {string.Join (", ", runningAvdNames)}"); + + // Add non-running emulators + foreach (var avdName in availableEmulators) { + if (runningAvdNames.Contains (avdName)) { + logger?.Invoke (TraceLevel.Verbose, $"Emulator '{avdName}' is already running, skipping"); + continue; + } + + var displayName = FormatDisplayName (avdName); + result.Add (new AdbDeviceInfo { + Serial = avdName, + Description = displayName + " (Not Running)", + Type = AdbDeviceType.Emulator, + Status = AdbDeviceStatus.NotRunning, + AvdName = avdName, + }); + logger?.Invoke (TraceLevel.Verbose, $"Added non-running emulator: {avdName}"); + } + + // Sort: online devices first, then not-running emulators, alphabetically by description + result.Sort ((a, b) => { + var aNotRunning = a.Status == AdbDeviceStatus.NotRunning; + var bNotRunning = b.Status == AdbDeviceStatus.NotRunning; + + if (aNotRunning != bNotRunning) + return aNotRunning ? 1 : -1; + + return string.Compare (a.Description, b.Description, StringComparison.OrdinalIgnoreCase); + }); + + return result; + } +} + diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Runners/AndroidEnvironmentHelper.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Runners/AndroidEnvironmentHelper.cs new file mode 100644 index 00000000000..51f6309d2f5 --- /dev/null +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Runners/AndroidEnvironmentHelper.cs @@ -0,0 +1,40 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.IO; + +namespace Xamarin.Android.Tools; + +/// +/// Helper for building environment variables for Android SDK tools. +/// Returns a dictionary that can be passed to . +/// +internal static class AndroidEnvironmentHelper +{ + /// + /// Builds environment variables needed to run Android SDK tools. + /// Pass the result to via the environmentVariables parameter. + /// + internal static Dictionary GetEnvironmentVariables (string? sdkPath, string? jdkPath) + { + var env = new Dictionary (); + + if (sdkPath is { Length: > 0 }) + env [EnvironmentVariableNames.AndroidHome] = sdkPath; + + if (jdkPath is { Length: > 0 }) { + env [EnvironmentVariableNames.JavaHome] = jdkPath; + var jdkBin = Path.Combine (jdkPath, "bin"); + var currentPath = Environment.GetEnvironmentVariable (EnvironmentVariableNames.Path) ?? ""; + env [EnvironmentVariableNames.Path] = string.IsNullOrEmpty (currentPath) ? jdkBin : jdkBin + Path.PathSeparator + currentPath; + } + + if (string.IsNullOrEmpty (Environment.GetEnvironmentVariable (EnvironmentVariableNames.AndroidUserHome))) + env [EnvironmentVariableNames.AndroidUserHome] = Path.Combine ( + Environment.GetFolderPath (Environment.SpecialFolder.UserProfile), ".android"); + + return env; + } +} diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/SdkManager.Licenses.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/SdkManager.Licenses.cs index f1d053c225b..b3e907d7b1d 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/SdkManager.Licenses.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/SdkManager.Licenses.cs @@ -60,7 +60,7 @@ public async Task> GetPendingLicensesAsync (Cancellati logger (TraceLevel.Verbose, "Checking for pending licenses..."); - var envVars = GetEnvironmentVariables (); + var envVars = AndroidEnvironmentHelper.GetEnvironmentVariables (AndroidSdkPath, JavaSdkPath); // Run --licenses without auto-accept to get the license text var psi = ProcessUtils.CreateProcessStartInfo (sdkManagerPath, "--licenses"); diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/SdkManager.Process.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/SdkManager.Process.cs index 96d70ef87f4..c526d27b0c4 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/SdkManager.Process.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/SdkManager.Process.cs @@ -29,7 +29,7 @@ public partial class SdkManager }; psi.RedirectStandardInput = acceptLicenses; - var envVars = GetEnvironmentVariables (); + var envVars = AndroidEnvironmentHelper.GetEnvironmentVariables (AndroidSdkPath, JavaSdkPath); using var stdout = new StringWriter (); using var stderr = new StringWriter (); @@ -90,21 +90,4 @@ async Task DownloadFileAsync (string url, string destinationPath, long expectedS await DownloadUtils.DownloadFileAsync (httpClient, url, destinationPath, expectedSize, downloadProgress, cancellationToken).ConfigureAwait (false); logger (TraceLevel.Info, $"Download complete: {destinationPath}"); } - - Dictionary GetEnvironmentVariables () - { - var env = new Dictionary { - ["ANDROID_USER_HOME"] = Path.Combine ( - Environment.GetFolderPath (Environment.SpecialFolder.UserProfile), ".android") - }; - - if (!string.IsNullOrEmpty (AndroidSdkPath)) - env[EnvironmentVariableNames.AndroidHome] = AndroidSdkPath!; - - if (!string.IsNullOrEmpty (JavaSdkPath)) - env[EnvironmentVariableNames.JavaHome] = JavaSdkPath!; - - return env; - } - } diff --git a/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/AdbRunnerTests.cs b/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/AdbRunnerTests.cs new file mode 100644 index 00000000000..b35ef08ba6c --- /dev/null +++ b/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/AdbRunnerTests.cs @@ -0,0 +1,633 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using NUnit.Framework; + +namespace Xamarin.Android.Tools.Tests; + +/// +/// Tests for AdbRunner parsing, formatting, and merging logic. +/// Ported from dotnet/android GetAvailableAndroidDevicesTests. +/// +/// API consumer reference: +/// - ParseAdbDevicesOutput: used by dotnet/android GetAvailableAndroidDevices task +/// - BuildDeviceDescription: used by dotnet/android GetAvailableAndroidDevices task +/// - FormatDisplayName: used by dotnet/android GetAvailableAndroidDevices tests +/// - MergeDevicesAndEmulators: used by dotnet/android GetAvailableAndroidDevices task +/// - MapAdbStateToStatus: used internally by ParseAdbDevicesOutput, public for extensibility +/// - ListDevicesAsync: used by MAUI DevTools Adb provider (Providers/Android/Adb.cs) +/// - WaitForDeviceAsync: used by MAUI DevTools Adb provider +/// - StopEmulatorAsync: used by MAUI DevTools Adb provider +/// - GetEmulatorAvdNameAsync: internal, used by ListDevicesAsync only +/// +[TestFixture] +public class AdbRunnerTests +{ + // --- ParseAdbDevicesOutput tests --- + // Consumer: dotnet/android GetAvailableAndroidDevices.cs, MAUI DevTools (via ListDevicesAsync) + + [Test] + public void ParseAdbDevicesOutput_RealWorldData () + { + var output = + "List of devices attached\n" + + "0A041FDD400327 device product:redfin model:Pixel_5 device:redfin transport_id:2\n" + + "emulator-5554 device product:sdk_gphone64_x86_64 model:sdk_gphone64_x86_64 device:emu64xa transport_id:1\n"; + + var devices = AdbRunner.ParseAdbDevicesOutput (output.Split ('\n')); + + Assert.AreEqual (2, devices.Count); + + // Physical device + Assert.AreEqual ("0A041FDD400327", devices [0].Serial); + Assert.AreEqual (AdbDeviceType.Device, devices [0].Type); + Assert.AreEqual (AdbDeviceStatus.Online, devices [0].Status); + Assert.AreEqual ("Pixel 5", devices [0].Description); + Assert.AreEqual ("Pixel_5", devices [0].Model); + Assert.AreEqual ("redfin", devices [0].Product); + Assert.AreEqual ("redfin", devices [0].Device); + Assert.AreEqual ("2", devices [0].TransportId); + Assert.IsFalse (devices [0].IsEmulator); + + // Emulator + Assert.AreEqual ("emulator-5554", devices [1].Serial); + Assert.AreEqual (AdbDeviceType.Emulator, devices [1].Type); + Assert.AreEqual (AdbDeviceStatus.Online, devices [1].Status); + Assert.AreEqual ("sdk gphone64 x86 64", devices [1].Description); // model with underscores replaced + Assert.AreEqual ("sdk_gphone64_x86_64", devices [1].Model); + Assert.AreEqual ("1", devices [1].TransportId); + Assert.IsTrue (devices [1].IsEmulator); + } + + [Test] + public void ParseAdbDevicesOutput_EmptyOutput () + { + var output = "List of devices attached\n\n"; + var devices = AdbRunner.ParseAdbDevicesOutput (output.Split ('\n')); + Assert.AreEqual (0, devices.Count); + } + + [Test] + public void ParseAdbDevicesOutput_SingleEmulator () + { + var output = + "List of devices attached\n" + + "emulator-5554 device product:sdk_gphone64_arm64 model:sdk_gphone64_arm64 device:emu64a transport_id:1\n"; + + var devices = AdbRunner.ParseAdbDevicesOutput (output.Split ('\n')); + + Assert.AreEqual (1, devices.Count); + Assert.AreEqual ("emulator-5554", devices [0].Serial); + Assert.AreEqual (AdbDeviceType.Emulator, devices [0].Type); + Assert.AreEqual (AdbDeviceStatus.Online, devices [0].Status); + Assert.AreEqual ("sdk_gphone64_arm64", devices [0].Model); + Assert.AreEqual ("1", devices [0].TransportId); + } + + [Test] + public void ParseAdbDevicesOutput_SinglePhysicalDevice () + { + var output = + "List of devices attached\n" + + "0A041FDD400327 device usb:1-1 product:raven model:Pixel_6_Pro device:raven transport_id:2\n"; + + var devices = AdbRunner.ParseAdbDevicesOutput (output.Split ('\n')); + + Assert.AreEqual (1, devices.Count); + Assert.AreEqual ("0A041FDD400327", devices [0].Serial); + Assert.AreEqual (AdbDeviceType.Device, devices [0].Type); + Assert.AreEqual (AdbDeviceStatus.Online, devices [0].Status); + Assert.AreEqual ("Pixel 6 Pro", devices [0].Description); + Assert.AreEqual ("Pixel_6_Pro", devices [0].Model); + Assert.AreEqual ("raven", devices [0].Product); + Assert.AreEqual ("2", devices [0].TransportId); + } + + [Test] + public void ParseAdbDevicesOutput_OfflineDevice () + { + var output = + "List of devices attached\n" + + "emulator-5554 offline product:sdk_gphone64_arm64 model:sdk_gphone64_arm64 device:emu64a transport_id:1\n"; + + var devices = AdbRunner.ParseAdbDevicesOutput (output.Split ('\n')); + + Assert.AreEqual (1, devices.Count); + Assert.AreEqual (AdbDeviceStatus.Offline, devices [0].Status); + } + + [Test] + public void ParseAdbDevicesOutput_UnauthorizedDevice () + { + var output = + "List of devices attached\n" + + "0A041FDD400327 unauthorized usb:1-1\n"; + + var devices = AdbRunner.ParseAdbDevicesOutput (output.Split ('\n')); + + Assert.AreEqual (1, devices.Count); + Assert.AreEqual ("0A041FDD400327", devices [0].Serial); + Assert.AreEqual (AdbDeviceStatus.Unauthorized, devices [0].Status); + Assert.AreEqual (AdbDeviceType.Device, devices [0].Type); + } + + [Test] + public void ParseAdbDevicesOutput_NoPermissionsDevice () + { + var output = + "List of devices attached\n" + + "???????????????? no permissions usb:1-1\n"; + + var devices = AdbRunner.ParseAdbDevicesOutput (output.Split ('\n')); + + Assert.AreEqual (1, devices.Count); + Assert.AreEqual ("????????????????", devices [0].Serial); + Assert.AreEqual (AdbDeviceStatus.NoPermissions, devices [0].Status); + } + + [Test] + public void ParseAdbDevicesOutput_DeviceWithMinimalMetadata () + { + var output = + "List of devices attached\n" + + "ABC123 device\n"; + + var devices = AdbRunner.ParseAdbDevicesOutput (output.Split ('\n')); + + Assert.AreEqual (1, devices.Count); + Assert.AreEqual ("ABC123", devices [0].Serial); + Assert.AreEqual (AdbDeviceType.Device, devices [0].Type); + Assert.AreEqual (AdbDeviceStatus.Online, devices [0].Status); + Assert.AreEqual ("ABC123", devices [0].Description, "Should fall back to serial"); + } + + [Test] + public void ParseAdbDevicesOutput_InvalidLines () + { + var output = + "List of devices attached\n" + + "\n" + + " \n" + + "Some random text\n" + + "* daemon not running; starting now at tcp:5037\n" + + "* daemon started successfully\n" + + "emulator-5554 device product:sdk_gphone64_arm64 model:sdk_gphone64_arm64 device:emu64a transport_id:1\n"; + + var devices = AdbRunner.ParseAdbDevicesOutput (output.Split ('\n')); + + Assert.AreEqual (1, devices.Count, "Should only return valid device lines"); + Assert.AreEqual ("emulator-5554", devices [0].Serial); + } + + [Test] + public void ParseAdbDevicesOutput_MixedDeviceStates () + { + var output = + "List of devices attached\n" + + "emulator-5554 device product:sdk_gphone64_arm64 model:Pixel_7 device:emu64a\n" + + "emulator-5556 offline\n" + + "0A041FDD400327 device usb:1-1 product:raven model:Pixel_6_Pro\n" + + "0B123456789ABC unauthorized usb:1-2\n"; + + var devices = AdbRunner.ParseAdbDevicesOutput (output.Split ('\n')); + + Assert.AreEqual (4, devices.Count); + Assert.AreEqual (AdbDeviceStatus.Online, devices [0].Status); + Assert.AreEqual (AdbDeviceStatus.Offline, devices [1].Status); + Assert.AreEqual (AdbDeviceStatus.Online, devices [2].Status); + Assert.AreEqual (AdbDeviceStatus.Unauthorized, devices [3].Status); + } + + [Test] + public void ParseAdbDevicesOutput_WindowsNewlines () + { + var output = + "List of devices attached\r\n" + + "emulator-5554 device transport_id:1\r\n" + + "\r\n"; + + var devices = AdbRunner.ParseAdbDevicesOutput (output.Split ('\n')); + + Assert.AreEqual (1, devices.Count); + Assert.AreEqual ("emulator-5554", devices [0].Serial); + Assert.IsTrue (devices [0].IsEmulator); + } + + [Test] + public void ParseAdbDevicesOutput_TabSeparator () + { + var output = + "List of devices attached\n" + + "emulator-5554\tdevice\n" + + "R5CR10YZQPJ\tdevice\n"; + + var devices = AdbRunner.ParseAdbDevicesOutput (output.Split ('\n')); + + Assert.AreEqual (2, devices.Count); + Assert.AreEqual ("emulator-5554", devices [0].Serial); + Assert.AreEqual ("R5CR10YZQPJ", devices [1].Serial); + } + + [Test] + public void ParseAdbDevicesOutput_IpPortDevice () + { + var output = + "List of devices attached\n" + + "192.168.1.100:5555 device product:sdk_gphone64_arm64 model:Remote_Device\n"; + + var devices = AdbRunner.ParseAdbDevicesOutput (output.Split ('\n')); + + Assert.AreEqual (1, devices.Count); + Assert.AreEqual ("192.168.1.100:5555", devices [0].Serial); + Assert.AreEqual (AdbDeviceType.Device, devices [0].Type, "IP devices should be Device"); + Assert.AreEqual ("Remote Device", devices [0].Description); + } + + [Test] + public void ParseAdbDevicesOutput_AdbDaemonStarting () + { + var output = + "* daemon not running; starting now at tcp:5037\n" + + "* daemon started successfully\n" + + "List of devices attached\n" + + "emulator-5554 device product:sdk_gphone64_arm64 model:sdk_gphone64_arm64 device:emu64a transport_id:1\n" + + "0A041FDD400327 device usb:1-1 product:raven model:Pixel_6_Pro device:raven transport_id:2\n"; + + var devices = AdbRunner.ParseAdbDevicesOutput (output.Split ('\n')); + + Assert.AreEqual (2, devices.Count, "Should parse devices even with daemon startup messages"); + } + + // --- BuildDeviceDescription tests --- + // Consumer: dotnet/android GetAvailableAndroidDevices.cs + + [Test] + public void DescriptionPriorityOrder () + { + // Model has highest priority + var output1 = "List of devices attached\ndevice1 device product:product_name model:model_name device:device_name\n"; + var devices1 = AdbRunner.ParseAdbDevicesOutput (output1.Split ('\n')); + Assert.AreEqual ("model name", devices1 [0].Description, "Model should have highest priority"); + + // Product has second priority + var output2 = "List of devices attached\ndevice2 device product:product_name device:device_name\n"; + var devices2 = AdbRunner.ParseAdbDevicesOutput (output2.Split ('\n')); + Assert.AreEqual ("product name", devices2 [0].Description, "Product should have second priority"); + + // Device code name has third priority + var output3 = "List of devices attached\ndevice3 device device:device_name\n"; + var devices3 = AdbRunner.ParseAdbDevicesOutput (output3.Split ('\n')); + Assert.AreEqual ("device name", devices3 [0].Description, "Device should have third priority"); + } + + // --- FormatDisplayName tests --- + // Consumer: dotnet/android GetAvailableAndroidDevicesTests, BuildDeviceDescription (via AVD name formatting) + + [Test] + public void FormatDisplayName_ReplacesUnderscoresWithSpaces () + { + Assert.AreEqual ("Pixel 7 Pro", AdbRunner.FormatDisplayName ("pixel_7_pro")); + } + + [Test] + public void FormatDisplayName_AppliesTitleCase () + { + Assert.AreEqual ("Pixel 7 Pro", AdbRunner.FormatDisplayName ("pixel 7 pro")); + } + + [Test] + public void FormatDisplayName_ReplacesApiWithAPIUppercase () + { + Assert.AreEqual ("Pixel 5 API 34", AdbRunner.FormatDisplayName ("pixel_5_api_34")); + } + + [Test] + public void FormatDisplayName_HandlesMultipleApiOccurrences () + { + Assert.AreEqual ("Test API Device API 35", AdbRunner.FormatDisplayName ("test_api_device_api_35")); + } + + [Test] + public void FormatDisplayName_HandlesMixedCaseInput () + { + Assert.AreEqual ("Pixel 7 API 35", AdbRunner.FormatDisplayName ("PiXeL_7_API_35")); + } + + [Test] + public void FormatDisplayName_HandlesComplexNames () + { + Assert.AreEqual ("Pixel 9 Pro Xl API 36", AdbRunner.FormatDisplayName ("pixel_9_pro_xl_api_36")); + } + + [Test] + public void FormatDisplayName_PreservesNumbersAndSpecialChars () + { + Assert.AreEqual ("Pixel 7-Pro API 35", AdbRunner.FormatDisplayName ("pixel_7-pro_api_35")); + } + + [Test] + public void FormatDisplayName_HandlesEmptyString () + { + Assert.AreEqual ("", AdbRunner.FormatDisplayName ("")); + } + + [Test] + public void FormatDisplayName_HandlesSingleWord () + { + Assert.AreEqual ("Pixel", AdbRunner.FormatDisplayName ("pixel")); + } + + [Test] + public void FormatDisplayName_DoesNotReplaceApiInsideWords () + { + Assert.AreEqual ("Erapidevice", AdbRunner.FormatDisplayName ("erapidevice")); + } + + // --- MapAdbStateToStatus tests --- + // Consumer: ParseAdbDevicesOutput (internal mapping), public for custom consumers + + [Test] + public void MapAdbStateToStatus_AllStates () + { + Assert.AreEqual (AdbDeviceStatus.Online, AdbRunner.MapAdbStateToStatus ("device")); + Assert.AreEqual (AdbDeviceStatus.Offline, AdbRunner.MapAdbStateToStatus ("offline")); + Assert.AreEqual (AdbDeviceStatus.Unauthorized, AdbRunner.MapAdbStateToStatus ("unauthorized")); + Assert.AreEqual (AdbDeviceStatus.NoPermissions, AdbRunner.MapAdbStateToStatus ("no permissions")); + Assert.AreEqual (AdbDeviceStatus.Unknown, AdbRunner.MapAdbStateToStatus ("something-else")); + } + + // --- MergeDevicesAndEmulators tests --- + // Consumer: dotnet/android GetAvailableAndroidDevices.cs + + [Test] + public void MergeDevicesAndEmulators_NoEmulators_ReturnsAdbDevicesOnly () + { + var adbDevices = new List { + new AdbDeviceInfo { Serial = "0A041FDD400327", Description = "Pixel 5", Type = AdbDeviceType.Device, Status = AdbDeviceStatus.Online }, + }; + + var result = AdbRunner.MergeDevicesAndEmulators (adbDevices, new List ()); + + Assert.AreEqual (1, result.Count); + Assert.AreEqual ("0A041FDD400327", result [0].Serial); + } + + [Test] + public void MergeDevicesAndEmulators_NoRunningEmulators_AddsAllAvailable () + { + var adbDevices = new List { + new AdbDeviceInfo { Serial = "0A041FDD400327", Description = "Pixel 5", Type = AdbDeviceType.Device, Status = AdbDeviceStatus.Online }, + }; + var available = new List { "pixel_7_api_35", "pixel_9_api_36" }; + + var result = AdbRunner.MergeDevicesAndEmulators (adbDevices, available); + + Assert.AreEqual (3, result.Count); + + // Online first + Assert.AreEqual ("0A041FDD400327", result [0].Serial); + + // Non-running sorted alphabetically + Assert.AreEqual ("pixel_7_api_35", result [1].Serial); + Assert.AreEqual (AdbDeviceStatus.NotRunning, result [1].Status); + Assert.AreEqual ("pixel_7_api_35", result [1].AvdName); + Assert.AreEqual ("Pixel 7 API 35 (Not Running)", result [1].Description); + + Assert.AreEqual ("pixel_9_api_36", result [2].Serial); + Assert.AreEqual (AdbDeviceStatus.NotRunning, result [2].Status); + Assert.AreEqual ("Pixel 9 API 36 (Not Running)", result [2].Description); + } + + [Test] + public void MergeDevicesAndEmulators_RunningEmulator_NoDuplicate () + { + var adbDevices = new List { + new AdbDeviceInfo { + Serial = "emulator-5554", Description = "Pixel 7 API 35", + Type = AdbDeviceType.Emulator, Status = AdbDeviceStatus.Online, + AvdName = "pixel_7_api_35" + }, + }; + var available = new List { "pixel_7_api_35" }; + + var result = AdbRunner.MergeDevicesAndEmulators (adbDevices, available); + + Assert.AreEqual (1, result.Count, "Should not duplicate running emulator"); + Assert.AreEqual ("emulator-5554", result [0].Serial); + Assert.AreEqual (AdbDeviceStatus.Online, result [0].Status); + } + + [Test] + public void MergeDevicesAndEmulators_MixedRunningAndNotRunning () + { + var adbDevices = new List { + new AdbDeviceInfo { + Serial = "emulator-5554", Description = "Pixel 7 API 35", + Type = AdbDeviceType.Emulator, Status = AdbDeviceStatus.Online, + AvdName = "pixel_7_api_35" + }, + new AdbDeviceInfo { + Serial = "0A041FDD400327", Description = "Pixel 5", + Type = AdbDeviceType.Device, Status = AdbDeviceStatus.Online, + }, + }; + var available = new List { "pixel_7_api_35", "pixel_9_api_36", "nexus_5_api_30" }; + + var result = AdbRunner.MergeDevicesAndEmulators (adbDevices, available); + + Assert.AreEqual (4, result.Count); + + // Online devices first, sorted alphabetically + Assert.AreEqual ("0A041FDD400327", result [0].Serial); + Assert.AreEqual (AdbDeviceStatus.Online, result [0].Status); + + Assert.AreEqual ("emulator-5554", result [1].Serial); + Assert.AreEqual (AdbDeviceStatus.Online, result [1].Status); + + // Non-running emulators second, sorted alphabetically + Assert.AreEqual ("nexus_5_api_30", result [2].Serial); + Assert.AreEqual (AdbDeviceStatus.NotRunning, result [2].Status); + Assert.AreEqual ("Nexus 5 API 30 (Not Running)", result [2].Description); + + Assert.AreEqual ("pixel_9_api_36", result [3].Serial); + Assert.AreEqual (AdbDeviceStatus.NotRunning, result [3].Status); + } + + [Test] + public void MergeDevicesAndEmulators_CaseInsensitiveAvdNameMatching () + { + var adbDevices = new List { + new AdbDeviceInfo { + Serial = "emulator-5554", Description = "Pixel 7 API 35", + Type = AdbDeviceType.Emulator, Status = AdbDeviceStatus.Online, + AvdName = "Pixel_7_API_35" + }, + }; + var available = new List { "pixel_7_api_35" }; // lowercase + + var result = AdbRunner.MergeDevicesAndEmulators (adbDevices, available); + + Assert.AreEqual (1, result.Count, "Should match AVD names case-insensitively"); + } + + [Test] + public void MergeDevicesAndEmulators_EmptyAdbDevices_ReturnsAllAvailable () + { + var result = AdbRunner.MergeDevicesAndEmulators (new List (), new List { "pixel_7_api_35", "pixel_9_api_36" }); + + Assert.AreEqual (2, result.Count); + Assert.AreEqual ("Pixel 7 API 35 (Not Running)", result [0].Description); + Assert.AreEqual ("Pixel 9 API 36 (Not Running)", result [1].Description); + } + + // --- AdbPath tests --- + // Consumer: MAUI DevTools Adb provider (AdbPath, IsAvailable properties) + + [Test] + public void Constructor_NullPath_ThrowsArgumentException () + { + Assert.Throws (() => new AdbRunner (null!)); + } + + [Test] + public void Constructor_EmptyPath_ThrowsArgumentException () + { + Assert.Throws (() => new AdbRunner ("")); + } + + [Test] + public void Constructor_WhitespacePath_ThrowsArgumentException () + { + Assert.Throws (() => new AdbRunner (" ")); + } + + [Test] + public void ParseAdbDevicesOutput_DeviceWithProductOnly () + { + var output = + "List of devices attached\n" + + "emulator-5554 device product:aosp_x86_64\n"; + + var devices = AdbRunner.ParseAdbDevicesOutput (output.Split ('\n')); + + Assert.AreEqual (1, devices.Count); + Assert.AreEqual ("aosp_x86_64", devices [0].Product); + Assert.AreEqual (AdbDeviceType.Emulator, devices [0].Type); + } + + [Test] + public void ParseAdbDevicesOutput_MultipleDevices () + { + var output = + "List of devices attached\n" + + "emulator-5554 device product:sdk_gphone64_arm64 model:sdk_gphone64_arm64 device:emu64a transport_id:1\n" + + "emulator-5556 device product:sdk_gphone64_x86_64 model:sdk_gphone64_x86_64 device:emu64x transport_id:3\n" + + "0A041FDD400327 device usb:1-1 product:raven model:Pixel_6_Pro device:raven transport_id:2\n"; + + var devices = AdbRunner.ParseAdbDevicesOutput (output.Split ('\n')); + + Assert.AreEqual (3, devices.Count); + Assert.AreEqual ("emulator-5554", devices [0].Serial); + Assert.AreEqual (AdbDeviceType.Emulator, devices [0].Type); + Assert.AreEqual ("emulator-5556", devices [1].Serial); + Assert.AreEqual (AdbDeviceType.Emulator, devices [1].Type); + Assert.AreEqual ("0A041FDD400327", devices [2].Serial); + Assert.AreEqual (AdbDeviceType.Device, devices [2].Type); + Assert.AreEqual ("Pixel_6_Pro", devices [2].Model); + } + + [Test] + public void MergeDevicesAndEmulators_AllEmulatorsRunning_NoDuplicate () + { + var emulator1 = new AdbDeviceInfo { + Serial = "emulator-5554", Description = "Pixel 7 API 35", + Type = AdbDeviceType.Emulator, Status = AdbDeviceStatus.Online, + AvdName = "pixel_7_api_35", + }; + var emulator2 = new AdbDeviceInfo { + Serial = "emulator-5556", Description = "Pixel 9 API 36", + Type = AdbDeviceType.Emulator, Status = AdbDeviceStatus.Online, + AvdName = "pixel_9_api_36", + }; + + var result = AdbRunner.MergeDevicesAndEmulators ( + new List { emulator1, emulator2 }, + new List { "pixel_7_api_35", "pixel_9_api_36" }); + + Assert.AreEqual (2, result.Count, "Should not add duplicates when all emulators are running"); + Assert.IsTrue (result.All (d => d.Status == AdbDeviceStatus.Online)); + } + + [Test] + public void MergeDevicesAndEmulators_NonRunningEmulatorHasFormattedDescription () + { + var result = AdbRunner.MergeDevicesAndEmulators ( + new List (), + new List { "pixel_7_pro_api_35" }); + + Assert.AreEqual (1, result.Count); + Assert.AreEqual ("Pixel 7 Pro API 35 (Not Running)", result [0].Description); + Assert.AreEqual (AdbDeviceStatus.NotRunning, result [0].Status); + } + + [Test] + public void ParseAdbDevicesOutput_RecoveryState () + { + var output = "List of devices attached\n" + + "0A041FDD400327 recovery product:redfin model:Pixel_5 device:redfin transport_id:2\n"; + + var devices = AdbRunner.ParseAdbDevicesOutput (output.Split ('\n')); + + Assert.AreEqual (1, devices.Count); + Assert.AreEqual ("0A041FDD400327", devices [0].Serial); + Assert.AreEqual (AdbDeviceStatus.Unknown, devices [0].Status); + } + + [Test] + public void ParseAdbDevicesOutput_SideloadState () + { + var output = "List of devices attached\n" + + "0A041FDD400327 sideload\n"; + + var devices = AdbRunner.ParseAdbDevicesOutput (output.Split ('\n')); + + Assert.AreEqual (1, devices.Count); + Assert.AreEqual ("0A041FDD400327", devices [0].Serial); + Assert.AreEqual (AdbDeviceStatus.Unknown, devices [0].Status); + } + + [Test] + public void MapAdbStateToStatus_Recovery_ReturnsUnknown () + { + Assert.AreEqual (AdbDeviceStatus.Unknown, AdbRunner.MapAdbStateToStatus ("recovery")); + } + + [Test] + public void MapAdbStateToStatus_Sideload_ReturnsUnknown () + { + Assert.AreEqual (AdbDeviceStatus.Unknown, AdbRunner.MapAdbStateToStatus ("sideload")); + } + + // --- WaitForDeviceAsync tests --- + // Consumer: MAUI DevTools Adb provider (WaitForDeviceAsync) + + [Test] + public void WaitForDeviceAsync_NegativeTimeout_ThrowsArgumentOutOfRange () + { + var runner = new AdbRunner ("/fake/sdk/platform-tools/adb"); + Assert.ThrowsAsync ( + async () => await runner.WaitForDeviceAsync (timeout: System.TimeSpan.FromSeconds (-1))); + } + + [Test] + public void WaitForDeviceAsync_ZeroTimeout_ThrowsArgumentOutOfRange () + { + var runner = new AdbRunner ("/fake/sdk/platform-tools/adb"); + Assert.ThrowsAsync ( + async () => await runner.WaitForDeviceAsync (timeout: System.TimeSpan.Zero)); + } +} diff --git a/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/RunnerIntegrationTests.cs b/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/RunnerIntegrationTests.cs new file mode 100644 index 00000000000..8b1a872f500 --- /dev/null +++ b/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/RunnerIntegrationTests.cs @@ -0,0 +1,127 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Diagnostics; +using System.IO; +using System.Threading.Tasks; + +using NUnit.Framework; + +namespace Xamarin.Android.Tools.Tests; + +/// +/// Integration tests that verify AdbRunner works against real Android SDK tools. +/// +/// These tests only run on CI (TF_BUILD=True or CI=true) where hosted +/// images have JDK (JAVA_HOME) and Android SDK (ANDROID_HOME) pre-installed. +/// Tests are skipped on local developer machines. +/// +[TestFixture] +[Category ("Integration")] +public class RunnerIntegrationTests +{ + static string sdkPath; + static string jdkPath; + static string adbPath; + static SdkManager sdkManager; + + static void Log (TraceLevel level, string message) + { + TestContext.Progress.WriteLine ($"[{level}] {message}"); + } + + static void RequireCi () + { + var tfBuild = Environment.GetEnvironmentVariable ("TF_BUILD"); + var ci = Environment.GetEnvironmentVariable ("CI"); + + if (!string.Equals (tfBuild, "true", StringComparison.OrdinalIgnoreCase) && + !string.Equals (ci, "true", StringComparison.OrdinalIgnoreCase)) { + Assert.Ignore ("Integration tests only run on CI (TF_BUILD=True or CI=true)."); + } + } + + /// + /// One-time setup: use pre-installed JDK/SDK on CI agents. + /// Azure Pipelines hosted images have JAVA_HOME and ANDROID_HOME already configured. + /// + [OneTimeSetUp] + public void OneTimeSetUp () + { + RequireCi (); + + // Use pre-installed JDK from JAVA_HOME (always available on CI agents) + jdkPath = Environment.GetEnvironmentVariable (EnvironmentVariableNames.JavaHome); + if (string.IsNullOrEmpty (jdkPath) || !Directory.Exists (jdkPath)) { + Assert.Ignore ("JAVA_HOME not set or invalid — cannot run integration tests."); + return; + } + TestContext.Progress.WriteLine ($"Using JDK from JAVA_HOME: {jdkPath}"); + + // Use pre-installed Android SDK from ANDROID_HOME (must be available on CI agents) + sdkPath = Environment.GetEnvironmentVariable (EnvironmentVariableNames.AndroidHome); + if (string.IsNullOrEmpty (sdkPath) || !Directory.Exists (sdkPath)) { + Assert.Ignore ("ANDROID_HOME not set or invalid — cannot run integration tests. Provision Android SDK and set ANDROID_HOME to enable these tests."); + return; + } + + TestContext.Progress.WriteLine ($"Using SDK from ANDROID_HOME: {sdkPath}"); + sdkManager = new SdkManager (Log); + sdkManager.JavaSdkPath = jdkPath; + sdkManager.AndroidSdkPath = sdkPath; + + // Resolve the full path to adb for AdbRunner + var adbExe = OS.IsWindows ? "adb.exe" : "adb"; + adbPath = Path.Combine (sdkPath, "platform-tools", adbExe); + if (!File.Exists (adbPath)) + Assert.Ignore ($"adb not found at {adbPath}"); + } + + [OneTimeTearDown] + public void OneTimeTearDown () + { + sdkManager?.Dispose (); + } + + [Test] + public void AdbRunner_Constructor_AcceptsValidPath () + { + var runner = new AdbRunner (adbPath); + Assert.IsNotNull (runner); + } + + [Test] + public async Task AdbRunner_ListDevicesAsync_ReturnsWithoutError () + { + var runner = new AdbRunner (adbPath); + + // On CI there are no physical devices or emulators, but the command + // should succeed and return an empty (or non-null) list. + var devices = await runner.ListDevicesAsync (); + + Assert.IsNotNull (devices); + TestContext.Progress.WriteLine ($"ListDevicesAsync returned {devices.Count} device(s)"); + } + + [Test] + public void AdbRunner_WaitForDeviceAsync_TimesOut_WhenNoDevice () + { + var runner = new AdbRunner (adbPath); + var ex = Assert.ThrowsAsync (async () => + await runner.WaitForDeviceAsync (timeout: TimeSpan.FromSeconds (5))); + + Assert.That (ex, Is.Not.Null); + TestContext.Progress.WriteLine ($"WaitForDeviceAsync timed out as expected: {ex?.Message}"); + } + + [Test] + public void AllRunners_ToolDiscovery_ConsistentWithSdk () + { + var runner = new AdbRunner (adbPath); + + // adb path should be under the SDK + Assert.IsTrue (File.Exists (adbPath), $"adb should exist at {adbPath}"); + StringAssert.StartsWith (sdkPath, adbPath); + } +} From ae757a19dc2e187e7183a4f1bc7c9f3d5d94404d Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Tue, 10 Mar 2026 14:05:59 -0500 Subject: [PATCH 243/308] Setup PublicApi Roslyn analyzer for Xamarin.Android.Tools.AndroidSdk (#294) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds `Microsoft.CodeAnalysis.PublicApiAnalyzers` to track public API surface changes, with APIs split between shipped (.NET 10) and unshipped (.NET 11). - **Package**: `Microsoft.CodeAnalysis.PublicApiAnalyzers` 3.3.4, `PrivateAssets=All` - **Per-TFM API files**: `PublicAPI/{netstandard2.0,net10.0}/PublicAPI.{Shipped,Unshipped}.txt` — both TFMs currently expose identical API surfaces - **Shipped.txt**: 130 public API entries representing the API surface at the .NET 10 release point ([`8f709da`](https://github.com/dotnet/android-tools/tree/8f709da6546defd83c7a8a09a5603b756e318761)) - **Unshipped.txt**: 131 entries for APIs introduced after the .NET 10 release (new .NET 11 APIs), including a `*REMOVED*` entry for the old `ProcessUtils.StartProcess` overload which gained an `environmentVariables` parameter ```xml ``` Follows the per-TFM directory convention from [dotnet/maui](https://github.com/dotnet/maui/blob/main/src/PublicAPI.targets) and the [analyzer docs](https://github.com/crummel/dotnet_roslyn-analyzers/blob/main/src/PublicApiAnalyzers/PublicApiAnalyzers.Help.md#conditional-api-differences) for multi-targeting projects. Co-authored-by: Jonathan Peppers --- .../PublicAPI/net10.0/PublicAPI.Shipped.txt | 131 +++++++++++++++++ .../PublicAPI/net10.0/PublicAPI.Unshipped.txt | 132 ++++++++++++++++++ .../netstandard2.0/PublicAPI.Shipped.txt | 131 +++++++++++++++++ .../netstandard2.0/PublicAPI.Unshipped.txt | 132 ++++++++++++++++++ .../Xamarin.Android.Tools.AndroidSdk.csproj | 6 + 5 files changed, 532 insertions(+) create mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/PublicAPI/net10.0/PublicAPI.Shipped.txt create mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/PublicAPI/net10.0/PublicAPI.Unshipped.txt create mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/PublicAPI/netstandard2.0/PublicAPI.Shipped.txt create mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/PublicAPI/net10.0/PublicAPI.Shipped.txt b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/PublicAPI/net10.0/PublicAPI.Shipped.txt new file mode 100644 index 00000000000..47e8e682995 --- /dev/null +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/PublicAPI/net10.0/PublicAPI.Shipped.txt @@ -0,0 +1,131 @@ +#nullable enable +Xamarin.Android.Tools.AndroidAppManifest +Xamarin.Android.Tools.AndroidAppManifest.AndroidPermissions.get -> System.Collections.Generic.IEnumerable! +Xamarin.Android.Tools.AndroidAppManifest.AndroidPermissionsQualified.get -> System.Collections.Generic.IEnumerable! +Xamarin.Android.Tools.AndroidAppManifest.ApplicationIcon.get -> string? +Xamarin.Android.Tools.AndroidAppManifest.ApplicationIcon.set -> void +Xamarin.Android.Tools.AndroidAppManifest.ApplicationLabel.get -> string? +Xamarin.Android.Tools.AndroidAppManifest.ApplicationLabel.set -> void +Xamarin.Android.Tools.AndroidAppManifest.ApplicationRoundIcon.get -> string? +Xamarin.Android.Tools.AndroidAppManifest.ApplicationRoundIcon.set -> void +Xamarin.Android.Tools.AndroidAppManifest.ApplicationTheme.get -> string? +Xamarin.Android.Tools.AndroidAppManifest.ApplicationTheme.set -> void +Xamarin.Android.Tools.AndroidAppManifest.Debuggable.get -> bool? +Xamarin.Android.Tools.AndroidAppManifest.Debuggable.set -> void +Xamarin.Android.Tools.AndroidAppManifest.Document.get -> System.Xml.Linq.XDocument! +Xamarin.Android.Tools.AndroidAppManifest.GetAllActivityNames() -> System.Collections.Generic.IEnumerable! +Xamarin.Android.Tools.AndroidAppManifest.GetLaunchableActivityName() -> string? +Xamarin.Android.Tools.AndroidAppManifest.GetLaunchableActivityNames() -> System.Collections.Generic.IEnumerable! +Xamarin.Android.Tools.AndroidAppManifest.GetLaunchableFastDevActivityName() -> string? +Xamarin.Android.Tools.AndroidAppManifest.GetLaunchableUserActivityName() -> string? +Xamarin.Android.Tools.AndroidAppManifest.InstallLocation.get -> string? +Xamarin.Android.Tools.AndroidAppManifest.InstallLocation.set -> void +Xamarin.Android.Tools.AndroidAppManifest.MinSdkVersion.get -> int? +Xamarin.Android.Tools.AndroidAppManifest.MinSdkVersion.set -> void +Xamarin.Android.Tools.AndroidAppManifest.PackageName.get -> string? +Xamarin.Android.Tools.AndroidAppManifest.PackageName.set -> void +Xamarin.Android.Tools.AndroidAppManifest.SetAndroidPermissions(System.Collections.Generic.IEnumerable! permissions) -> void +Xamarin.Android.Tools.AndroidAppManifest.TargetSdkVersion.get -> int? +Xamarin.Android.Tools.AndroidAppManifest.TargetSdkVersion.set -> void +Xamarin.Android.Tools.AndroidAppManifest.VersionCode.get -> string? +Xamarin.Android.Tools.AndroidAppManifest.VersionCode.set -> void +Xamarin.Android.Tools.AndroidAppManifest.VersionName.get -> string? +Xamarin.Android.Tools.AndroidAppManifest.VersionName.set -> void +Xamarin.Android.Tools.AndroidAppManifest.Write(System.Xml.XmlWriter! writer) -> void +Xamarin.Android.Tools.AndroidAppManifest.WriteToFile(string! fileName) -> void +Xamarin.Android.Tools.AndroidSdkInfo +Xamarin.Android.Tools.AndroidSdkInfo.AllAndroidSdkPaths.get -> string![]! +Xamarin.Android.Tools.AndroidSdkInfo.AndroidNdkHostPlatform.get -> string! +Xamarin.Android.Tools.AndroidSdkInfo.AndroidNdkPath.get -> string? +Xamarin.Android.Tools.AndroidSdkInfo.AndroidSdkInfo(System.Action? logger = null, string? androidSdkPath = null, string? androidNdkPath = null, string? javaSdkPath = null) -> void +Xamarin.Android.Tools.AndroidSdkInfo.AndroidSdkPath.get -> string! +Xamarin.Android.Tools.AndroidSdkInfo.GetBuildToolsPaths() -> System.Collections.Generic.IEnumerable! +Xamarin.Android.Tools.AndroidSdkInfo.GetBuildToolsPaths(string! preferredBuildToolsVersion) -> System.Collections.Generic.IEnumerable! +Xamarin.Android.Tools.AndroidSdkInfo.GetCommandLineToolsPaths() -> System.Collections.Generic.IEnumerable! +Xamarin.Android.Tools.AndroidSdkInfo.GetCommandLineToolsPaths(string! preferredCommandLineToolsVersion) -> System.Collections.Generic.IEnumerable! +Xamarin.Android.Tools.AndroidSdkInfo.GetInstalledPlatformVersions(Xamarin.Android.Tools.AndroidVersions! versions) -> System.Collections.Generic.IEnumerable! +Xamarin.Android.Tools.AndroidSdkInfo.GetPlatformDirectory(int apiLevel) -> string! +Xamarin.Android.Tools.AndroidSdkInfo.GetPlatformDirectoryFromId(string! id) -> string! +Xamarin.Android.Tools.AndroidSdkInfo.IsPlatformInstalled(int apiLevel) -> bool +Xamarin.Android.Tools.AndroidSdkInfo.JavaSdkPath.get -> string! +Xamarin.Android.Tools.AndroidSdkInfo.TryGetCommandLineToolsPath() -> string? +Xamarin.Android.Tools.AndroidSdkInfo.TryGetPlatformDirectoryFromApiLevel(string! idOrApiLevel, Xamarin.Android.Tools.AndroidVersions! versions) -> string? +Xamarin.Android.Tools.AndroidTargetArch +Xamarin.Android.Tools.AndroidTargetArch.Arm = 1 -> Xamarin.Android.Tools.AndroidTargetArch +Xamarin.Android.Tools.AndroidTargetArch.Arm64 = 8 -> Xamarin.Android.Tools.AndroidTargetArch +Xamarin.Android.Tools.AndroidTargetArch.Mips = 4 -> Xamarin.Android.Tools.AndroidTargetArch +Xamarin.Android.Tools.AndroidTargetArch.None = 0 -> Xamarin.Android.Tools.AndroidTargetArch +Xamarin.Android.Tools.AndroidTargetArch.Other = 65536 -> Xamarin.Android.Tools.AndroidTargetArch +Xamarin.Android.Tools.AndroidTargetArch.X86 = 2 -> Xamarin.Android.Tools.AndroidTargetArch +Xamarin.Android.Tools.AndroidTargetArch.X86_64 = 16 -> Xamarin.Android.Tools.AndroidTargetArch +Xamarin.Android.Tools.AndroidVersion +Xamarin.Android.Tools.AndroidVersion.AndroidVersion(System.Version! versionCodeFull, string! osVersion, string? codeName = null, string? id = null, bool stable = true) -> void +Xamarin.Android.Tools.AndroidVersion.AndroidVersion(int apiLevel, string! osVersion, string? codeName = null, string? id = null, bool stable = true) -> void +Xamarin.Android.Tools.AndroidVersion.ApiLevel.get -> int +Xamarin.Android.Tools.AndroidVersion.CodeName.get -> string? +Xamarin.Android.Tools.AndroidVersion.FrameworkVersion.get -> string! +Xamarin.Android.Tools.AndroidVersion.Id.get -> string! +Xamarin.Android.Tools.AndroidVersion.OSVersion.get -> string! +Xamarin.Android.Tools.AndroidVersion.Stable.get -> bool +Xamarin.Android.Tools.AndroidVersion.TargetFrameworkVersion.get -> System.Version! +Xamarin.Android.Tools.AndroidVersion.VersionCodeFull.get -> System.Version! +Xamarin.Android.Tools.AndroidVersions +Xamarin.Android.Tools.AndroidVersions.AndroidVersions(System.Collections.Generic.IEnumerable! versions) -> void +Xamarin.Android.Tools.AndroidVersions.AndroidVersions(System.Collections.Generic.IEnumerable! frameworkDirectories) -> void +Xamarin.Android.Tools.AndroidVersions.FrameworkDirectories.get -> System.Collections.Generic.IReadOnlyList! +Xamarin.Android.Tools.AndroidVersions.GetApiLevelFromFrameworkVersion(string! frameworkVersion) -> int? +Xamarin.Android.Tools.AndroidVersions.GetApiLevelFromId(string! id) -> int? +Xamarin.Android.Tools.AndroidVersions.GetFrameworkVersionFromApiLevel(int apiLevel) -> string? +Xamarin.Android.Tools.AndroidVersions.GetFrameworkVersionFromId(string! id) -> string? +Xamarin.Android.Tools.AndroidVersions.GetIdFromApiLevel(int apiLevel) -> string? +Xamarin.Android.Tools.AndroidVersions.GetIdFromApiLevel(string! apiLevel) -> string? +Xamarin.Android.Tools.AndroidVersions.GetIdFromFrameworkVersion(string! frameworkVersion) -> string? +Xamarin.Android.Tools.AndroidVersions.GetIdFromVersionCodeFull(System.Version! versionCodeFull) -> string? +Xamarin.Android.Tools.AndroidVersions.InstalledBindingVersions.get -> System.Collections.Generic.IReadOnlyList! +Xamarin.Android.Tools.AndroidVersions.MaxStableVersion.get -> Xamarin.Android.Tools.AndroidVersion? +Xamarin.Android.Tools.AndroidVersions.MinStableVersion.get -> Xamarin.Android.Tools.AndroidVersion? +Xamarin.Android.Tools.JdkInfo +Xamarin.Android.Tools.JdkInfo.GetJavaSettingsPropertyValue(string! key, out string? value) -> bool +Xamarin.Android.Tools.JdkInfo.GetJavaSettingsPropertyValues(string! key, out System.Collections.Generic.IEnumerable? value) -> bool +Xamarin.Android.Tools.JdkInfo.HomePath.get -> string! +Xamarin.Android.Tools.JdkInfo.IncludePath.get -> System.Collections.ObjectModel.ReadOnlyCollection! +Xamarin.Android.Tools.JdkInfo.JarPath.get -> string! +Xamarin.Android.Tools.JdkInfo.JavaPath.get -> string! +Xamarin.Android.Tools.JdkInfo.JavaSettingsPropertyKeys.get -> System.Collections.Generic.IEnumerable! +Xamarin.Android.Tools.JdkInfo.JavacPath.get -> string! +Xamarin.Android.Tools.JdkInfo.JdkInfo(string! homePath) -> void +Xamarin.Android.Tools.JdkInfo.JdkInfo(string! homePath, string! locator) -> void +Xamarin.Android.Tools.JdkInfo.JdkInfo(string! homePath, string? locator = null, System.Action? logger = null) -> void +Xamarin.Android.Tools.JdkInfo.JdkJvmPath.get -> string! +Xamarin.Android.Tools.JdkInfo.Locator.get -> string? +Xamarin.Android.Tools.JdkInfo.ReleaseProperties.get -> System.Collections.ObjectModel.ReadOnlyDictionary! +Xamarin.Android.Tools.JdkInfo.Vendor.get -> string? +Xamarin.Android.Tools.JdkInfo.Version.get -> System.Version? +Xamarin.Android.Tools.KernelEx +Xamarin.Android.Tools.OS +Xamarin.Android.Tools.OS.OS() -> void +Xamarin.Android.Tools.ProcessUtils +override Xamarin.Android.Tools.AndroidVersion.ToString() -> string! +override Xamarin.Android.Tools.JdkInfo.ToString() -> string! +static Xamarin.Android.Tools.AndroidAppManifest.AndroidXNamespace.get -> System.Xml.Linq.XNamespace! +static Xamarin.Android.Tools.AndroidAppManifest.CanonicalizePackageName(string! packageNameOrAssemblyName) -> string! +static Xamarin.Android.Tools.AndroidAppManifest.Create(string! packageName, string! appLabel, Xamarin.Android.Tools.AndroidVersions! versions) -> Xamarin.Android.Tools.AndroidAppManifest! +static Xamarin.Android.Tools.AndroidAppManifest.Load(System.Xml.Linq.XDocument! doc, Xamarin.Android.Tools.AndroidVersions! versions) -> Xamarin.Android.Tools.AndroidAppManifest! +static Xamarin.Android.Tools.AndroidAppManifest.Load(string! filename, Xamarin.Android.Tools.AndroidVersions! versions) -> Xamarin.Android.Tools.AndroidAppManifest! +static Xamarin.Android.Tools.AndroidAppManifest.NameXName.get -> System.Xml.Linq.XName! +static Xamarin.Android.Tools.AndroidSdkInfo.DetectAndSetPreferredJavaSdkPathToLatest(System.Action? logger = null) -> void +static Xamarin.Android.Tools.AndroidSdkInfo.SetPreferredAndroidNdkPath(string! path, System.Action? logger = null) -> void +static Xamarin.Android.Tools.AndroidSdkInfo.SetPreferredAndroidSdkPath(string! path, System.Action? logger = null) -> void +static Xamarin.Android.Tools.AndroidSdkInfo.SetPreferredJavaSdkPath(string! path, System.Action? logger = null) -> void +static Xamarin.Android.Tools.AndroidVersion.Load(System.IO.Stream! stream) -> Xamarin.Android.Tools.AndroidVersion! +static Xamarin.Android.Tools.AndroidVersion.Load(string! uri) -> Xamarin.Android.Tools.AndroidVersion! +static Xamarin.Android.Tools.JdkInfo.GetKnownSystemJdkInfos(System.Action? logger = null) -> System.Collections.Generic.IEnumerable! +static Xamarin.Android.Tools.JdkInfo.GetSupportedJdkInfos(System.Action? logger = null) -> System.Collections.Generic.IEnumerable! +static Xamarin.Android.Tools.KernelEx.GetLongPathName(string! path) -> string! +static Xamarin.Android.Tools.KernelEx.GetShortPathName(string! path) -> string! +static Xamarin.Android.Tools.ProcessUtils.ExecuteToolAsync(string! exe, System.Func! result, System.Threading.CancellationToken token, System.Action? onStarted = null) -> System.Threading.Tasks.Task! +static Xamarin.Android.Tools.ProcessUtils.StartProcess(System.Diagnostics.ProcessStartInfo! psi, System.IO.TextWriter? stdout, System.IO.TextWriter? stderr, System.Threading.CancellationToken cancellationToken, System.Action? onStarted = null) -> System.Threading.Tasks.Task! +static readonly Xamarin.Android.Tools.AndroidVersions.KnownVersions -> Xamarin.Android.Tools.AndroidVersion![]! +static readonly Xamarin.Android.Tools.OS.IsLinux -> bool +static readonly Xamarin.Android.Tools.OS.IsMac -> bool +static readonly Xamarin.Android.Tools.OS.IsWindows -> bool diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/PublicAPI/net10.0/PublicAPI.Unshipped.txt b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/PublicAPI/net10.0/PublicAPI.Unshipped.txt new file mode 100644 index 00000000000..70283f7b667 --- /dev/null +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/PublicAPI/net10.0/PublicAPI.Unshipped.txt @@ -0,0 +1,132 @@ +#nullable enable +*REMOVED*static Xamarin.Android.Tools.ProcessUtils.StartProcess(System.Diagnostics.ProcessStartInfo! psi, System.IO.TextWriter? stdout, System.IO.TextWriter? stderr, System.Threading.CancellationToken cancellationToken, System.Action? onStarted = null) -> System.Threading.Tasks.Task! +Xamarin.Android.Tools.AdbDeviceInfo +Xamarin.Android.Tools.AdbDeviceInfo.AdbDeviceInfo() -> void +Xamarin.Android.Tools.AdbDeviceInfo.AvdName.get -> string? +Xamarin.Android.Tools.AdbDeviceInfo.AvdName.set -> void +Xamarin.Android.Tools.AdbDeviceInfo.Description.get -> string! +Xamarin.Android.Tools.AdbDeviceInfo.Description.set -> void +Xamarin.Android.Tools.AdbDeviceInfo.Device.get -> string? +Xamarin.Android.Tools.AdbDeviceInfo.Device.set -> void +Xamarin.Android.Tools.AdbDeviceInfo.IsEmulator.get -> bool +Xamarin.Android.Tools.AdbDeviceInfo.Model.get -> string? +Xamarin.Android.Tools.AdbDeviceInfo.Model.set -> void +Xamarin.Android.Tools.AdbDeviceInfo.Product.get -> string? +Xamarin.Android.Tools.AdbDeviceInfo.Product.set -> void +Xamarin.Android.Tools.AdbDeviceInfo.Serial.get -> string! +Xamarin.Android.Tools.AdbDeviceInfo.Serial.set -> void +Xamarin.Android.Tools.AdbDeviceInfo.Status.get -> Xamarin.Android.Tools.AdbDeviceStatus +Xamarin.Android.Tools.AdbDeviceInfo.Status.set -> void +Xamarin.Android.Tools.AdbDeviceInfo.TransportId.get -> string? +Xamarin.Android.Tools.AdbDeviceInfo.TransportId.set -> void +Xamarin.Android.Tools.AdbDeviceInfo.Type.get -> Xamarin.Android.Tools.AdbDeviceType +Xamarin.Android.Tools.AdbDeviceInfo.Type.set -> void +Xamarin.Android.Tools.AdbDeviceStatus +Xamarin.Android.Tools.AdbDeviceStatus.NoPermissions = 3 -> Xamarin.Android.Tools.AdbDeviceStatus +Xamarin.Android.Tools.AdbDeviceStatus.NotRunning = 4 -> Xamarin.Android.Tools.AdbDeviceStatus +Xamarin.Android.Tools.AdbDeviceStatus.Offline = 1 -> Xamarin.Android.Tools.AdbDeviceStatus +Xamarin.Android.Tools.AdbDeviceStatus.Online = 0 -> Xamarin.Android.Tools.AdbDeviceStatus +Xamarin.Android.Tools.AdbDeviceStatus.Unauthorized = 2 -> Xamarin.Android.Tools.AdbDeviceStatus +Xamarin.Android.Tools.AdbDeviceStatus.Unknown = 5 -> Xamarin.Android.Tools.AdbDeviceStatus +Xamarin.Android.Tools.AdbDeviceType +Xamarin.Android.Tools.AdbDeviceType.Device = 0 -> Xamarin.Android.Tools.AdbDeviceType +Xamarin.Android.Tools.AdbDeviceType.Emulator = 1 -> Xamarin.Android.Tools.AdbDeviceType +Xamarin.Android.Tools.AdbRunner +Xamarin.Android.Tools.AdbRunner.AdbRunner(string! adbPath, System.Collections.Generic.IDictionary? environmentVariables = null) -> void +Xamarin.Android.Tools.AdbRunner.ListDevicesAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task!>! +Xamarin.Android.Tools.AdbRunner.StopEmulatorAsync(string! serial, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task! +Xamarin.Android.Tools.AdbRunner.WaitForDeviceAsync(string? serial = null, System.TimeSpan? timeout = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task! +Xamarin.Android.Tools.ChecksumType +Xamarin.Android.Tools.ChecksumType.Sha1 = 0 -> Xamarin.Android.Tools.ChecksumType +Xamarin.Android.Tools.ChecksumType.Sha256 = 1 -> Xamarin.Android.Tools.ChecksumType +Xamarin.Android.Tools.JdkInstallPhase +Xamarin.Android.Tools.JdkInstallPhase.Complete = 4 -> Xamarin.Android.Tools.JdkInstallPhase +Xamarin.Android.Tools.JdkInstallPhase.Downloading = 0 -> Xamarin.Android.Tools.JdkInstallPhase +Xamarin.Android.Tools.JdkInstallPhase.Extracting = 2 -> Xamarin.Android.Tools.JdkInstallPhase +Xamarin.Android.Tools.JdkInstallPhase.Validating = 3 -> Xamarin.Android.Tools.JdkInstallPhase +Xamarin.Android.Tools.JdkInstallPhase.Verifying = 1 -> Xamarin.Android.Tools.JdkInstallPhase +Xamarin.Android.Tools.JdkInstallProgress +Xamarin.Android.Tools.JdkInstallProgress.JdkInstallProgress(Xamarin.Android.Tools.JdkInstallPhase Phase, double PercentComplete, string? Message = null) -> void +Xamarin.Android.Tools.JdkInstallProgress.Message.get -> string? +Xamarin.Android.Tools.JdkInstallProgress.Message.init -> void +Xamarin.Android.Tools.JdkInstallProgress.PercentComplete.get -> double +Xamarin.Android.Tools.JdkInstallProgress.PercentComplete.init -> void +Xamarin.Android.Tools.JdkInstallProgress.Phase.get -> Xamarin.Android.Tools.JdkInstallPhase +Xamarin.Android.Tools.JdkInstallProgress.Phase.init -> void +Xamarin.Android.Tools.JdkInstaller +Xamarin.Android.Tools.JdkInstaller.DiscoverAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task!>! +Xamarin.Android.Tools.JdkInstaller.Dispose() -> void +Xamarin.Android.Tools.JdkInstaller.InstallAsync(int majorVersion, string! targetPath, System.IProgress? progress = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task! +Xamarin.Android.Tools.JdkInstaller.IsValid(string! jdkPath) -> bool +Xamarin.Android.Tools.JdkInstaller.JdkInstaller(System.Action? logger = null) -> void +Xamarin.Android.Tools.JdkInstaller.Remove(string! jdkPath) -> bool +Xamarin.Android.Tools.JdkVersionInfo +Xamarin.Android.Tools.JdkVersionInfo.Checksum.get -> string? +Xamarin.Android.Tools.JdkVersionInfo.ChecksumUrl.get -> string! +Xamarin.Android.Tools.JdkVersionInfo.DisplayName.get -> string! +Xamarin.Android.Tools.JdkVersionInfo.DownloadUrl.get -> string! +Xamarin.Android.Tools.JdkVersionInfo.JdkVersionInfo(int majorVersion, string! displayName, string! downloadUrl, string! checksumUrl, long size = 0, string? checksum = null) -> void +Xamarin.Android.Tools.JdkVersionInfo.MajorVersion.get -> int +Xamarin.Android.Tools.JdkVersionInfo.ResolvedUrl.get -> string? +Xamarin.Android.Tools.JdkVersionInfo.Size.get -> long +Xamarin.Android.Tools.SdkBootstrapPhase +Xamarin.Android.Tools.SdkBootstrapPhase.Complete = 4 -> Xamarin.Android.Tools.SdkBootstrapPhase +Xamarin.Android.Tools.SdkBootstrapPhase.Downloading = 1 -> Xamarin.Android.Tools.SdkBootstrapPhase +Xamarin.Android.Tools.SdkBootstrapPhase.Extracting = 3 -> Xamarin.Android.Tools.SdkBootstrapPhase +Xamarin.Android.Tools.SdkBootstrapPhase.ReadingManifest = 0 -> Xamarin.Android.Tools.SdkBootstrapPhase +Xamarin.Android.Tools.SdkBootstrapPhase.Verifying = 2 -> Xamarin.Android.Tools.SdkBootstrapPhase +Xamarin.Android.Tools.SdkBootstrapProgress +Xamarin.Android.Tools.SdkBootstrapProgress.Message.get -> string! +Xamarin.Android.Tools.SdkBootstrapProgress.Message.init -> void +Xamarin.Android.Tools.SdkBootstrapProgress.PercentComplete.get -> int +Xamarin.Android.Tools.SdkBootstrapProgress.PercentComplete.init -> void +Xamarin.Android.Tools.SdkBootstrapProgress.Phase.get -> Xamarin.Android.Tools.SdkBootstrapPhase +Xamarin.Android.Tools.SdkBootstrapProgress.Phase.init -> void +Xamarin.Android.Tools.SdkBootstrapProgress.SdkBootstrapProgress(Xamarin.Android.Tools.SdkBootstrapPhase Phase, int PercentComplete = -1, string! Message = "") -> void +Xamarin.Android.Tools.SdkLicense +Xamarin.Android.Tools.SdkLicense.Id.get -> string! +Xamarin.Android.Tools.SdkLicense.Id.init -> void +Xamarin.Android.Tools.SdkLicense.SdkLicense(string! Id, string! Text) -> void +Xamarin.Android.Tools.SdkLicense.Text.get -> string! +Xamarin.Android.Tools.SdkLicense.Text.init -> void +Xamarin.Android.Tools.SdkManager +Xamarin.Android.Tools.SdkManager.AcceptLicensesAsync(System.Collections.Generic.IEnumerable! licenseIds, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task! +Xamarin.Android.Tools.SdkManager.AcceptLicensesAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task! +Xamarin.Android.Tools.SdkManager.AndroidSdkPath.get -> string? +Xamarin.Android.Tools.SdkManager.AndroidSdkPath.set -> void +Xamarin.Android.Tools.SdkManager.AreLicensesAccepted() -> bool +Xamarin.Android.Tools.SdkManager.BootstrapAsync(string! targetPath, System.IProgress? progress = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task! +Xamarin.Android.Tools.SdkManager.Dispose() -> void +Xamarin.Android.Tools.SdkManager.FindSdkManagerPath() -> string? +Xamarin.Android.Tools.SdkManager.GetPendingLicensesAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task!>! +Xamarin.Android.Tools.SdkManager.InstallAsync(System.Collections.Generic.IEnumerable! packages, bool acceptLicenses = true, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task! +Xamarin.Android.Tools.SdkManager.JavaSdkPath.get -> string? +Xamarin.Android.Tools.SdkManager.JavaSdkPath.set -> void +Xamarin.Android.Tools.SdkManager.ListAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task<(System.Collections.Generic.IReadOnlyList! Installed, System.Collections.Generic.IReadOnlyList! Available)>! +Xamarin.Android.Tools.SdkManager.ManifestFeedUrl.get -> string! +Xamarin.Android.Tools.SdkManager.ManifestFeedUrl.set -> void +Xamarin.Android.Tools.SdkManager.SdkManager(System.Action? logger = null) -> void +Xamarin.Android.Tools.SdkManager.UninstallAsync(System.Collections.Generic.IEnumerable! packages, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task! +Xamarin.Android.Tools.SdkManager.UpdateAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task! +Xamarin.Android.Tools.SdkPackage +Xamarin.Android.Tools.SdkPackage.Description.get -> string? +Xamarin.Android.Tools.SdkPackage.Description.init -> void +Xamarin.Android.Tools.SdkPackage.IsInstalled.get -> bool +Xamarin.Android.Tools.SdkPackage.IsInstalled.init -> void +Xamarin.Android.Tools.SdkPackage.Path.get -> string! +Xamarin.Android.Tools.SdkPackage.Path.init -> void +Xamarin.Android.Tools.SdkPackage.SdkPackage(string! Path, string? Version = null, string? Description = null, bool IsInstalled = false) -> void +Xamarin.Android.Tools.SdkPackage.Version.get -> string? +Xamarin.Android.Tools.SdkPackage.Version.init -> void +const Xamarin.Android.Tools.SdkManager.DefaultManifestFeedUrl = "https://aka.ms/AndroidManifestFeed/d18-0" -> string! +override Xamarin.Android.Tools.JdkVersionInfo.ToString() -> string! +static Xamarin.Android.Tools.AdbRunner.BuildDeviceDescription(Xamarin.Android.Tools.AdbDeviceInfo! device, System.Action? logger = null) -> string! +static Xamarin.Android.Tools.AdbRunner.FormatDisplayName(string! avdName) -> string! +static Xamarin.Android.Tools.AdbRunner.MapAdbStateToStatus(string! adbState) -> Xamarin.Android.Tools.AdbDeviceStatus +static Xamarin.Android.Tools.AdbRunner.MergeDevicesAndEmulators(System.Collections.Generic.IReadOnlyList! adbDevices, System.Collections.Generic.IReadOnlyList! availableEmulators, System.Action? logger = null) -> System.Collections.Generic.IReadOnlyList! +static Xamarin.Android.Tools.AdbRunner.ParseAdbDevicesOutput(System.Collections.Generic.IEnumerable! lines) -> System.Collections.Generic.IReadOnlyList! +static Xamarin.Android.Tools.JdkInstaller.RecommendedMajorVersion.get -> int +static Xamarin.Android.Tools.JdkInstaller.SupportedVersions.get -> System.Collections.Generic.IReadOnlyList! +static Xamarin.Android.Tools.ProcessUtils.CreateProcessStartInfo(string! fileName, params string![]! args) -> System.Diagnostics.ProcessStartInfo! +static Xamarin.Android.Tools.ProcessUtils.IsElevated() -> bool +static Xamarin.Android.Tools.ProcessUtils.StartProcess(System.Diagnostics.ProcessStartInfo! psi, System.IO.TextWriter? stdout, System.IO.TextWriter? stderr, System.Threading.CancellationToken cancellationToken, System.Collections.Generic.IDictionary? environmentVariables = null, System.Action? onStarted = null) -> System.Threading.Tasks.Task! diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/PublicAPI/netstandard2.0/PublicAPI.Shipped.txt b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/PublicAPI/netstandard2.0/PublicAPI.Shipped.txt new file mode 100644 index 00000000000..47e8e682995 --- /dev/null +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/PublicAPI/netstandard2.0/PublicAPI.Shipped.txt @@ -0,0 +1,131 @@ +#nullable enable +Xamarin.Android.Tools.AndroidAppManifest +Xamarin.Android.Tools.AndroidAppManifest.AndroidPermissions.get -> System.Collections.Generic.IEnumerable! +Xamarin.Android.Tools.AndroidAppManifest.AndroidPermissionsQualified.get -> System.Collections.Generic.IEnumerable! +Xamarin.Android.Tools.AndroidAppManifest.ApplicationIcon.get -> string? +Xamarin.Android.Tools.AndroidAppManifest.ApplicationIcon.set -> void +Xamarin.Android.Tools.AndroidAppManifest.ApplicationLabel.get -> string? +Xamarin.Android.Tools.AndroidAppManifest.ApplicationLabel.set -> void +Xamarin.Android.Tools.AndroidAppManifest.ApplicationRoundIcon.get -> string? +Xamarin.Android.Tools.AndroidAppManifest.ApplicationRoundIcon.set -> void +Xamarin.Android.Tools.AndroidAppManifest.ApplicationTheme.get -> string? +Xamarin.Android.Tools.AndroidAppManifest.ApplicationTheme.set -> void +Xamarin.Android.Tools.AndroidAppManifest.Debuggable.get -> bool? +Xamarin.Android.Tools.AndroidAppManifest.Debuggable.set -> void +Xamarin.Android.Tools.AndroidAppManifest.Document.get -> System.Xml.Linq.XDocument! +Xamarin.Android.Tools.AndroidAppManifest.GetAllActivityNames() -> System.Collections.Generic.IEnumerable! +Xamarin.Android.Tools.AndroidAppManifest.GetLaunchableActivityName() -> string? +Xamarin.Android.Tools.AndroidAppManifest.GetLaunchableActivityNames() -> System.Collections.Generic.IEnumerable! +Xamarin.Android.Tools.AndroidAppManifest.GetLaunchableFastDevActivityName() -> string? +Xamarin.Android.Tools.AndroidAppManifest.GetLaunchableUserActivityName() -> string? +Xamarin.Android.Tools.AndroidAppManifest.InstallLocation.get -> string? +Xamarin.Android.Tools.AndroidAppManifest.InstallLocation.set -> void +Xamarin.Android.Tools.AndroidAppManifest.MinSdkVersion.get -> int? +Xamarin.Android.Tools.AndroidAppManifest.MinSdkVersion.set -> void +Xamarin.Android.Tools.AndroidAppManifest.PackageName.get -> string? +Xamarin.Android.Tools.AndroidAppManifest.PackageName.set -> void +Xamarin.Android.Tools.AndroidAppManifest.SetAndroidPermissions(System.Collections.Generic.IEnumerable! permissions) -> void +Xamarin.Android.Tools.AndroidAppManifest.TargetSdkVersion.get -> int? +Xamarin.Android.Tools.AndroidAppManifest.TargetSdkVersion.set -> void +Xamarin.Android.Tools.AndroidAppManifest.VersionCode.get -> string? +Xamarin.Android.Tools.AndroidAppManifest.VersionCode.set -> void +Xamarin.Android.Tools.AndroidAppManifest.VersionName.get -> string? +Xamarin.Android.Tools.AndroidAppManifest.VersionName.set -> void +Xamarin.Android.Tools.AndroidAppManifest.Write(System.Xml.XmlWriter! writer) -> void +Xamarin.Android.Tools.AndroidAppManifest.WriteToFile(string! fileName) -> void +Xamarin.Android.Tools.AndroidSdkInfo +Xamarin.Android.Tools.AndroidSdkInfo.AllAndroidSdkPaths.get -> string![]! +Xamarin.Android.Tools.AndroidSdkInfo.AndroidNdkHostPlatform.get -> string! +Xamarin.Android.Tools.AndroidSdkInfo.AndroidNdkPath.get -> string? +Xamarin.Android.Tools.AndroidSdkInfo.AndroidSdkInfo(System.Action? logger = null, string? androidSdkPath = null, string? androidNdkPath = null, string? javaSdkPath = null) -> void +Xamarin.Android.Tools.AndroidSdkInfo.AndroidSdkPath.get -> string! +Xamarin.Android.Tools.AndroidSdkInfo.GetBuildToolsPaths() -> System.Collections.Generic.IEnumerable! +Xamarin.Android.Tools.AndroidSdkInfo.GetBuildToolsPaths(string! preferredBuildToolsVersion) -> System.Collections.Generic.IEnumerable! +Xamarin.Android.Tools.AndroidSdkInfo.GetCommandLineToolsPaths() -> System.Collections.Generic.IEnumerable! +Xamarin.Android.Tools.AndroidSdkInfo.GetCommandLineToolsPaths(string! preferredCommandLineToolsVersion) -> System.Collections.Generic.IEnumerable! +Xamarin.Android.Tools.AndroidSdkInfo.GetInstalledPlatformVersions(Xamarin.Android.Tools.AndroidVersions! versions) -> System.Collections.Generic.IEnumerable! +Xamarin.Android.Tools.AndroidSdkInfo.GetPlatformDirectory(int apiLevel) -> string! +Xamarin.Android.Tools.AndroidSdkInfo.GetPlatformDirectoryFromId(string! id) -> string! +Xamarin.Android.Tools.AndroidSdkInfo.IsPlatformInstalled(int apiLevel) -> bool +Xamarin.Android.Tools.AndroidSdkInfo.JavaSdkPath.get -> string! +Xamarin.Android.Tools.AndroidSdkInfo.TryGetCommandLineToolsPath() -> string? +Xamarin.Android.Tools.AndroidSdkInfo.TryGetPlatformDirectoryFromApiLevel(string! idOrApiLevel, Xamarin.Android.Tools.AndroidVersions! versions) -> string? +Xamarin.Android.Tools.AndroidTargetArch +Xamarin.Android.Tools.AndroidTargetArch.Arm = 1 -> Xamarin.Android.Tools.AndroidTargetArch +Xamarin.Android.Tools.AndroidTargetArch.Arm64 = 8 -> Xamarin.Android.Tools.AndroidTargetArch +Xamarin.Android.Tools.AndroidTargetArch.Mips = 4 -> Xamarin.Android.Tools.AndroidTargetArch +Xamarin.Android.Tools.AndroidTargetArch.None = 0 -> Xamarin.Android.Tools.AndroidTargetArch +Xamarin.Android.Tools.AndroidTargetArch.Other = 65536 -> Xamarin.Android.Tools.AndroidTargetArch +Xamarin.Android.Tools.AndroidTargetArch.X86 = 2 -> Xamarin.Android.Tools.AndroidTargetArch +Xamarin.Android.Tools.AndroidTargetArch.X86_64 = 16 -> Xamarin.Android.Tools.AndroidTargetArch +Xamarin.Android.Tools.AndroidVersion +Xamarin.Android.Tools.AndroidVersion.AndroidVersion(System.Version! versionCodeFull, string! osVersion, string? codeName = null, string? id = null, bool stable = true) -> void +Xamarin.Android.Tools.AndroidVersion.AndroidVersion(int apiLevel, string! osVersion, string? codeName = null, string? id = null, bool stable = true) -> void +Xamarin.Android.Tools.AndroidVersion.ApiLevel.get -> int +Xamarin.Android.Tools.AndroidVersion.CodeName.get -> string? +Xamarin.Android.Tools.AndroidVersion.FrameworkVersion.get -> string! +Xamarin.Android.Tools.AndroidVersion.Id.get -> string! +Xamarin.Android.Tools.AndroidVersion.OSVersion.get -> string! +Xamarin.Android.Tools.AndroidVersion.Stable.get -> bool +Xamarin.Android.Tools.AndroidVersion.TargetFrameworkVersion.get -> System.Version! +Xamarin.Android.Tools.AndroidVersion.VersionCodeFull.get -> System.Version! +Xamarin.Android.Tools.AndroidVersions +Xamarin.Android.Tools.AndroidVersions.AndroidVersions(System.Collections.Generic.IEnumerable! versions) -> void +Xamarin.Android.Tools.AndroidVersions.AndroidVersions(System.Collections.Generic.IEnumerable! frameworkDirectories) -> void +Xamarin.Android.Tools.AndroidVersions.FrameworkDirectories.get -> System.Collections.Generic.IReadOnlyList! +Xamarin.Android.Tools.AndroidVersions.GetApiLevelFromFrameworkVersion(string! frameworkVersion) -> int? +Xamarin.Android.Tools.AndroidVersions.GetApiLevelFromId(string! id) -> int? +Xamarin.Android.Tools.AndroidVersions.GetFrameworkVersionFromApiLevel(int apiLevel) -> string? +Xamarin.Android.Tools.AndroidVersions.GetFrameworkVersionFromId(string! id) -> string? +Xamarin.Android.Tools.AndroidVersions.GetIdFromApiLevel(int apiLevel) -> string? +Xamarin.Android.Tools.AndroidVersions.GetIdFromApiLevel(string! apiLevel) -> string? +Xamarin.Android.Tools.AndroidVersions.GetIdFromFrameworkVersion(string! frameworkVersion) -> string? +Xamarin.Android.Tools.AndroidVersions.GetIdFromVersionCodeFull(System.Version! versionCodeFull) -> string? +Xamarin.Android.Tools.AndroidVersions.InstalledBindingVersions.get -> System.Collections.Generic.IReadOnlyList! +Xamarin.Android.Tools.AndroidVersions.MaxStableVersion.get -> Xamarin.Android.Tools.AndroidVersion? +Xamarin.Android.Tools.AndroidVersions.MinStableVersion.get -> Xamarin.Android.Tools.AndroidVersion? +Xamarin.Android.Tools.JdkInfo +Xamarin.Android.Tools.JdkInfo.GetJavaSettingsPropertyValue(string! key, out string? value) -> bool +Xamarin.Android.Tools.JdkInfo.GetJavaSettingsPropertyValues(string! key, out System.Collections.Generic.IEnumerable? value) -> bool +Xamarin.Android.Tools.JdkInfo.HomePath.get -> string! +Xamarin.Android.Tools.JdkInfo.IncludePath.get -> System.Collections.ObjectModel.ReadOnlyCollection! +Xamarin.Android.Tools.JdkInfo.JarPath.get -> string! +Xamarin.Android.Tools.JdkInfo.JavaPath.get -> string! +Xamarin.Android.Tools.JdkInfo.JavaSettingsPropertyKeys.get -> System.Collections.Generic.IEnumerable! +Xamarin.Android.Tools.JdkInfo.JavacPath.get -> string! +Xamarin.Android.Tools.JdkInfo.JdkInfo(string! homePath) -> void +Xamarin.Android.Tools.JdkInfo.JdkInfo(string! homePath, string! locator) -> void +Xamarin.Android.Tools.JdkInfo.JdkInfo(string! homePath, string? locator = null, System.Action? logger = null) -> void +Xamarin.Android.Tools.JdkInfo.JdkJvmPath.get -> string! +Xamarin.Android.Tools.JdkInfo.Locator.get -> string? +Xamarin.Android.Tools.JdkInfo.ReleaseProperties.get -> System.Collections.ObjectModel.ReadOnlyDictionary! +Xamarin.Android.Tools.JdkInfo.Vendor.get -> string? +Xamarin.Android.Tools.JdkInfo.Version.get -> System.Version? +Xamarin.Android.Tools.KernelEx +Xamarin.Android.Tools.OS +Xamarin.Android.Tools.OS.OS() -> void +Xamarin.Android.Tools.ProcessUtils +override Xamarin.Android.Tools.AndroidVersion.ToString() -> string! +override Xamarin.Android.Tools.JdkInfo.ToString() -> string! +static Xamarin.Android.Tools.AndroidAppManifest.AndroidXNamespace.get -> System.Xml.Linq.XNamespace! +static Xamarin.Android.Tools.AndroidAppManifest.CanonicalizePackageName(string! packageNameOrAssemblyName) -> string! +static Xamarin.Android.Tools.AndroidAppManifest.Create(string! packageName, string! appLabel, Xamarin.Android.Tools.AndroidVersions! versions) -> Xamarin.Android.Tools.AndroidAppManifest! +static Xamarin.Android.Tools.AndroidAppManifest.Load(System.Xml.Linq.XDocument! doc, Xamarin.Android.Tools.AndroidVersions! versions) -> Xamarin.Android.Tools.AndroidAppManifest! +static Xamarin.Android.Tools.AndroidAppManifest.Load(string! filename, Xamarin.Android.Tools.AndroidVersions! versions) -> Xamarin.Android.Tools.AndroidAppManifest! +static Xamarin.Android.Tools.AndroidAppManifest.NameXName.get -> System.Xml.Linq.XName! +static Xamarin.Android.Tools.AndroidSdkInfo.DetectAndSetPreferredJavaSdkPathToLatest(System.Action? logger = null) -> void +static Xamarin.Android.Tools.AndroidSdkInfo.SetPreferredAndroidNdkPath(string! path, System.Action? logger = null) -> void +static Xamarin.Android.Tools.AndroidSdkInfo.SetPreferredAndroidSdkPath(string! path, System.Action? logger = null) -> void +static Xamarin.Android.Tools.AndroidSdkInfo.SetPreferredJavaSdkPath(string! path, System.Action? logger = null) -> void +static Xamarin.Android.Tools.AndroidVersion.Load(System.IO.Stream! stream) -> Xamarin.Android.Tools.AndroidVersion! +static Xamarin.Android.Tools.AndroidVersion.Load(string! uri) -> Xamarin.Android.Tools.AndroidVersion! +static Xamarin.Android.Tools.JdkInfo.GetKnownSystemJdkInfos(System.Action? logger = null) -> System.Collections.Generic.IEnumerable! +static Xamarin.Android.Tools.JdkInfo.GetSupportedJdkInfos(System.Action? logger = null) -> System.Collections.Generic.IEnumerable! +static Xamarin.Android.Tools.KernelEx.GetLongPathName(string! path) -> string! +static Xamarin.Android.Tools.KernelEx.GetShortPathName(string! path) -> string! +static Xamarin.Android.Tools.ProcessUtils.ExecuteToolAsync(string! exe, System.Func! result, System.Threading.CancellationToken token, System.Action? onStarted = null) -> System.Threading.Tasks.Task! +static Xamarin.Android.Tools.ProcessUtils.StartProcess(System.Diagnostics.ProcessStartInfo! psi, System.IO.TextWriter? stdout, System.IO.TextWriter? stderr, System.Threading.CancellationToken cancellationToken, System.Action? onStarted = null) -> System.Threading.Tasks.Task! +static readonly Xamarin.Android.Tools.AndroidVersions.KnownVersions -> Xamarin.Android.Tools.AndroidVersion![]! +static readonly Xamarin.Android.Tools.OS.IsLinux -> bool +static readonly Xamarin.Android.Tools.OS.IsMac -> bool +static readonly Xamarin.Android.Tools.OS.IsWindows -> bool diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt new file mode 100644 index 00000000000..70283f7b667 --- /dev/null +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt @@ -0,0 +1,132 @@ +#nullable enable +*REMOVED*static Xamarin.Android.Tools.ProcessUtils.StartProcess(System.Diagnostics.ProcessStartInfo! psi, System.IO.TextWriter? stdout, System.IO.TextWriter? stderr, System.Threading.CancellationToken cancellationToken, System.Action? onStarted = null) -> System.Threading.Tasks.Task! +Xamarin.Android.Tools.AdbDeviceInfo +Xamarin.Android.Tools.AdbDeviceInfo.AdbDeviceInfo() -> void +Xamarin.Android.Tools.AdbDeviceInfo.AvdName.get -> string? +Xamarin.Android.Tools.AdbDeviceInfo.AvdName.set -> void +Xamarin.Android.Tools.AdbDeviceInfo.Description.get -> string! +Xamarin.Android.Tools.AdbDeviceInfo.Description.set -> void +Xamarin.Android.Tools.AdbDeviceInfo.Device.get -> string? +Xamarin.Android.Tools.AdbDeviceInfo.Device.set -> void +Xamarin.Android.Tools.AdbDeviceInfo.IsEmulator.get -> bool +Xamarin.Android.Tools.AdbDeviceInfo.Model.get -> string? +Xamarin.Android.Tools.AdbDeviceInfo.Model.set -> void +Xamarin.Android.Tools.AdbDeviceInfo.Product.get -> string? +Xamarin.Android.Tools.AdbDeviceInfo.Product.set -> void +Xamarin.Android.Tools.AdbDeviceInfo.Serial.get -> string! +Xamarin.Android.Tools.AdbDeviceInfo.Serial.set -> void +Xamarin.Android.Tools.AdbDeviceInfo.Status.get -> Xamarin.Android.Tools.AdbDeviceStatus +Xamarin.Android.Tools.AdbDeviceInfo.Status.set -> void +Xamarin.Android.Tools.AdbDeviceInfo.TransportId.get -> string? +Xamarin.Android.Tools.AdbDeviceInfo.TransportId.set -> void +Xamarin.Android.Tools.AdbDeviceInfo.Type.get -> Xamarin.Android.Tools.AdbDeviceType +Xamarin.Android.Tools.AdbDeviceInfo.Type.set -> void +Xamarin.Android.Tools.AdbDeviceStatus +Xamarin.Android.Tools.AdbDeviceStatus.NoPermissions = 3 -> Xamarin.Android.Tools.AdbDeviceStatus +Xamarin.Android.Tools.AdbDeviceStatus.NotRunning = 4 -> Xamarin.Android.Tools.AdbDeviceStatus +Xamarin.Android.Tools.AdbDeviceStatus.Offline = 1 -> Xamarin.Android.Tools.AdbDeviceStatus +Xamarin.Android.Tools.AdbDeviceStatus.Online = 0 -> Xamarin.Android.Tools.AdbDeviceStatus +Xamarin.Android.Tools.AdbDeviceStatus.Unauthorized = 2 -> Xamarin.Android.Tools.AdbDeviceStatus +Xamarin.Android.Tools.AdbDeviceStatus.Unknown = 5 -> Xamarin.Android.Tools.AdbDeviceStatus +Xamarin.Android.Tools.AdbDeviceType +Xamarin.Android.Tools.AdbDeviceType.Device = 0 -> Xamarin.Android.Tools.AdbDeviceType +Xamarin.Android.Tools.AdbDeviceType.Emulator = 1 -> Xamarin.Android.Tools.AdbDeviceType +Xamarin.Android.Tools.AdbRunner +Xamarin.Android.Tools.AdbRunner.AdbRunner(string! adbPath, System.Collections.Generic.IDictionary? environmentVariables = null) -> void +Xamarin.Android.Tools.AdbRunner.ListDevicesAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task!>! +Xamarin.Android.Tools.AdbRunner.StopEmulatorAsync(string! serial, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task! +Xamarin.Android.Tools.AdbRunner.WaitForDeviceAsync(string? serial = null, System.TimeSpan? timeout = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task! +Xamarin.Android.Tools.ChecksumType +Xamarin.Android.Tools.ChecksumType.Sha1 = 0 -> Xamarin.Android.Tools.ChecksumType +Xamarin.Android.Tools.ChecksumType.Sha256 = 1 -> Xamarin.Android.Tools.ChecksumType +Xamarin.Android.Tools.JdkInstallPhase +Xamarin.Android.Tools.JdkInstallPhase.Complete = 4 -> Xamarin.Android.Tools.JdkInstallPhase +Xamarin.Android.Tools.JdkInstallPhase.Downloading = 0 -> Xamarin.Android.Tools.JdkInstallPhase +Xamarin.Android.Tools.JdkInstallPhase.Extracting = 2 -> Xamarin.Android.Tools.JdkInstallPhase +Xamarin.Android.Tools.JdkInstallPhase.Validating = 3 -> Xamarin.Android.Tools.JdkInstallPhase +Xamarin.Android.Tools.JdkInstallPhase.Verifying = 1 -> Xamarin.Android.Tools.JdkInstallPhase +Xamarin.Android.Tools.JdkInstallProgress +Xamarin.Android.Tools.JdkInstallProgress.JdkInstallProgress(Xamarin.Android.Tools.JdkInstallPhase Phase, double PercentComplete, string? Message = null) -> void +Xamarin.Android.Tools.JdkInstallProgress.Message.get -> string? +Xamarin.Android.Tools.JdkInstallProgress.Message.init -> void +Xamarin.Android.Tools.JdkInstallProgress.PercentComplete.get -> double +Xamarin.Android.Tools.JdkInstallProgress.PercentComplete.init -> void +Xamarin.Android.Tools.JdkInstallProgress.Phase.get -> Xamarin.Android.Tools.JdkInstallPhase +Xamarin.Android.Tools.JdkInstallProgress.Phase.init -> void +Xamarin.Android.Tools.JdkInstaller +Xamarin.Android.Tools.JdkInstaller.DiscoverAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task!>! +Xamarin.Android.Tools.JdkInstaller.Dispose() -> void +Xamarin.Android.Tools.JdkInstaller.InstallAsync(int majorVersion, string! targetPath, System.IProgress? progress = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task! +Xamarin.Android.Tools.JdkInstaller.IsValid(string! jdkPath) -> bool +Xamarin.Android.Tools.JdkInstaller.JdkInstaller(System.Action? logger = null) -> void +Xamarin.Android.Tools.JdkInstaller.Remove(string! jdkPath) -> bool +Xamarin.Android.Tools.JdkVersionInfo +Xamarin.Android.Tools.JdkVersionInfo.Checksum.get -> string? +Xamarin.Android.Tools.JdkVersionInfo.ChecksumUrl.get -> string! +Xamarin.Android.Tools.JdkVersionInfo.DisplayName.get -> string! +Xamarin.Android.Tools.JdkVersionInfo.DownloadUrl.get -> string! +Xamarin.Android.Tools.JdkVersionInfo.JdkVersionInfo(int majorVersion, string! displayName, string! downloadUrl, string! checksumUrl, long size = 0, string? checksum = null) -> void +Xamarin.Android.Tools.JdkVersionInfo.MajorVersion.get -> int +Xamarin.Android.Tools.JdkVersionInfo.ResolvedUrl.get -> string? +Xamarin.Android.Tools.JdkVersionInfo.Size.get -> long +Xamarin.Android.Tools.SdkBootstrapPhase +Xamarin.Android.Tools.SdkBootstrapPhase.Complete = 4 -> Xamarin.Android.Tools.SdkBootstrapPhase +Xamarin.Android.Tools.SdkBootstrapPhase.Downloading = 1 -> Xamarin.Android.Tools.SdkBootstrapPhase +Xamarin.Android.Tools.SdkBootstrapPhase.Extracting = 3 -> Xamarin.Android.Tools.SdkBootstrapPhase +Xamarin.Android.Tools.SdkBootstrapPhase.ReadingManifest = 0 -> Xamarin.Android.Tools.SdkBootstrapPhase +Xamarin.Android.Tools.SdkBootstrapPhase.Verifying = 2 -> Xamarin.Android.Tools.SdkBootstrapPhase +Xamarin.Android.Tools.SdkBootstrapProgress +Xamarin.Android.Tools.SdkBootstrapProgress.Message.get -> string! +Xamarin.Android.Tools.SdkBootstrapProgress.Message.init -> void +Xamarin.Android.Tools.SdkBootstrapProgress.PercentComplete.get -> int +Xamarin.Android.Tools.SdkBootstrapProgress.PercentComplete.init -> void +Xamarin.Android.Tools.SdkBootstrapProgress.Phase.get -> Xamarin.Android.Tools.SdkBootstrapPhase +Xamarin.Android.Tools.SdkBootstrapProgress.Phase.init -> void +Xamarin.Android.Tools.SdkBootstrapProgress.SdkBootstrapProgress(Xamarin.Android.Tools.SdkBootstrapPhase Phase, int PercentComplete = -1, string! Message = "") -> void +Xamarin.Android.Tools.SdkLicense +Xamarin.Android.Tools.SdkLicense.Id.get -> string! +Xamarin.Android.Tools.SdkLicense.Id.init -> void +Xamarin.Android.Tools.SdkLicense.SdkLicense(string! Id, string! Text) -> void +Xamarin.Android.Tools.SdkLicense.Text.get -> string! +Xamarin.Android.Tools.SdkLicense.Text.init -> void +Xamarin.Android.Tools.SdkManager +Xamarin.Android.Tools.SdkManager.AcceptLicensesAsync(System.Collections.Generic.IEnumerable! licenseIds, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task! +Xamarin.Android.Tools.SdkManager.AcceptLicensesAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task! +Xamarin.Android.Tools.SdkManager.AndroidSdkPath.get -> string? +Xamarin.Android.Tools.SdkManager.AndroidSdkPath.set -> void +Xamarin.Android.Tools.SdkManager.AreLicensesAccepted() -> bool +Xamarin.Android.Tools.SdkManager.BootstrapAsync(string! targetPath, System.IProgress? progress = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task! +Xamarin.Android.Tools.SdkManager.Dispose() -> void +Xamarin.Android.Tools.SdkManager.FindSdkManagerPath() -> string? +Xamarin.Android.Tools.SdkManager.GetPendingLicensesAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task!>! +Xamarin.Android.Tools.SdkManager.InstallAsync(System.Collections.Generic.IEnumerable! packages, bool acceptLicenses = true, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task! +Xamarin.Android.Tools.SdkManager.JavaSdkPath.get -> string? +Xamarin.Android.Tools.SdkManager.JavaSdkPath.set -> void +Xamarin.Android.Tools.SdkManager.ListAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task<(System.Collections.Generic.IReadOnlyList! Installed, System.Collections.Generic.IReadOnlyList! Available)>! +Xamarin.Android.Tools.SdkManager.ManifestFeedUrl.get -> string! +Xamarin.Android.Tools.SdkManager.ManifestFeedUrl.set -> void +Xamarin.Android.Tools.SdkManager.SdkManager(System.Action? logger = null) -> void +Xamarin.Android.Tools.SdkManager.UninstallAsync(System.Collections.Generic.IEnumerable! packages, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task! +Xamarin.Android.Tools.SdkManager.UpdateAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task! +Xamarin.Android.Tools.SdkPackage +Xamarin.Android.Tools.SdkPackage.Description.get -> string? +Xamarin.Android.Tools.SdkPackage.Description.init -> void +Xamarin.Android.Tools.SdkPackage.IsInstalled.get -> bool +Xamarin.Android.Tools.SdkPackage.IsInstalled.init -> void +Xamarin.Android.Tools.SdkPackage.Path.get -> string! +Xamarin.Android.Tools.SdkPackage.Path.init -> void +Xamarin.Android.Tools.SdkPackage.SdkPackage(string! Path, string? Version = null, string? Description = null, bool IsInstalled = false) -> void +Xamarin.Android.Tools.SdkPackage.Version.get -> string? +Xamarin.Android.Tools.SdkPackage.Version.init -> void +const Xamarin.Android.Tools.SdkManager.DefaultManifestFeedUrl = "https://aka.ms/AndroidManifestFeed/d18-0" -> string! +override Xamarin.Android.Tools.JdkVersionInfo.ToString() -> string! +static Xamarin.Android.Tools.AdbRunner.BuildDeviceDescription(Xamarin.Android.Tools.AdbDeviceInfo! device, System.Action? logger = null) -> string! +static Xamarin.Android.Tools.AdbRunner.FormatDisplayName(string! avdName) -> string! +static Xamarin.Android.Tools.AdbRunner.MapAdbStateToStatus(string! adbState) -> Xamarin.Android.Tools.AdbDeviceStatus +static Xamarin.Android.Tools.AdbRunner.MergeDevicesAndEmulators(System.Collections.Generic.IReadOnlyList! adbDevices, System.Collections.Generic.IReadOnlyList! availableEmulators, System.Action? logger = null) -> System.Collections.Generic.IReadOnlyList! +static Xamarin.Android.Tools.AdbRunner.ParseAdbDevicesOutput(System.Collections.Generic.IEnumerable! lines) -> System.Collections.Generic.IReadOnlyList! +static Xamarin.Android.Tools.JdkInstaller.RecommendedMajorVersion.get -> int +static Xamarin.Android.Tools.JdkInstaller.SupportedVersions.get -> System.Collections.Generic.IReadOnlyList! +static Xamarin.Android.Tools.ProcessUtils.CreateProcessStartInfo(string! fileName, params string![]! args) -> System.Diagnostics.ProcessStartInfo! +static Xamarin.Android.Tools.ProcessUtils.IsElevated() -> bool +static Xamarin.Android.Tools.ProcessUtils.StartProcess(System.Diagnostics.ProcessStartInfo! psi, System.IO.TextWriter? stdout, System.IO.TextWriter? stderr, System.Threading.CancellationToken cancellationToken, System.Collections.Generic.IDictionary? environmentVariables = null, System.Action? onStarted = null) -> System.Threading.Tasks.Task! diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj index 13032f2d9d6..4daa59c38b8 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj @@ -37,10 +37,16 @@ all runtime; build; native; contentfiles; analyzers + + + + + + From 7611bd27e373c6612889fba3e0138a1006a35abf Mon Sep 17 00:00:00 2001 From: Rui Marinho Date: Tue, 10 Mar 2026 19:25:05 +0000 Subject: [PATCH 244/308] Fix FormatDisplayName to match original dotnet/android implementation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The ported FormatDisplayName added .ToLowerInvariant() before ToTitleCase(), which destroyed uppercase segments like 'XL' → 'Xl'. The original dotnet/android implementation passes the string directly to ToTitleCase(), which naturally preserves fully-uppercase words (it treats them as acronyms per .NET docs). Fix: remove the spurious .ToLowerInvariant() call to match the original behavior. Added end-to-end test BuildDeviceDescription_EmulatorWithUppercaseAvdName that mirrors dotnet/android's ParseMixedDeviceStates scenario exactly. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Runners/AdbRunner.cs | 3 ++- .../AdbRunnerTests.cs | 25 +++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Runners/AdbRunner.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Runners/AdbRunner.cs index f321af6dd04..24a62cec80a 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Runners/AdbRunner.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Runners/AdbRunner.cs @@ -241,6 +241,7 @@ public static string BuildDeviceDescription (AdbDeviceInfo device, Action /// Formats an AVD name into a user-friendly display name. /// Replaces underscores with spaces, applies title case, and capitalizes "API". + /// ToTitleCase naturally preserves fully-uppercase segments (e.g. "XL", "SE"). /// Ported from dotnet/android GetAvailableAndroidDevices.FormatDisplayName. /// public static string FormatDisplayName (string avdName) @@ -249,7 +250,7 @@ public static string FormatDisplayName (string avdName) return avdName ?? string.Empty; var textInfo = CultureInfo.InvariantCulture.TextInfo; - avdName = textInfo.ToTitleCase (avdName.Replace ('_', ' ').ToLowerInvariant ()); + avdName = textInfo.ToTitleCase (avdName.Replace ('_', ' ')); // Replace "Api" with "API" avdName = ApiRegex.Replace (avdName, "API"); diff --git a/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/AdbRunnerTests.cs b/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/AdbRunnerTests.cs index b35ef08ba6c..1e9a232afd7 100644 --- a/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/AdbRunnerTests.cs +++ b/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/AdbRunnerTests.cs @@ -284,6 +284,22 @@ public void DescriptionPriorityOrder () Assert.AreEqual ("device name", devices3 [0].Description, "Device should have third priority"); } + [Test] + public void BuildDeviceDescription_EmulatorWithUppercaseAvdName () + { + // Mirrors dotnet/android ParseMixedDeviceStates: offline emulator gets AVD name "Pixel_9_Pro_XL" + // BuildDeviceDescription should format it preserving "XL" uppercase + var device = new AdbDeviceInfo { + Serial = "emulator-5556", + Type = AdbDeviceType.Emulator, + Status = AdbDeviceStatus.Offline, + AvdName = "Pixel_9_Pro_XL", + }; + + var description = AdbRunner.BuildDeviceDescription (device); + Assert.AreEqual ("Pixel 9 Pro XL", description, "Offline emulator should still get AVD name with uppercase preserved"); + } + // --- FormatDisplayName tests --- // Consumer: dotnet/android GetAvailableAndroidDevicesTests, BuildDeviceDescription (via AVD name formatting) @@ -320,9 +336,18 @@ public void FormatDisplayName_HandlesMixedCaseInput () [Test] public void FormatDisplayName_HandlesComplexNames () { + // Lowercase input: "xl" gets title-cased to "Xl" Assert.AreEqual ("Pixel 9 Pro Xl API 36", AdbRunner.FormatDisplayName ("pixel_9_pro_xl_api_36")); } + [Test] + public void FormatDisplayName_PreservesUppercaseSegments () + { + // Fully-uppercase segments like "XL", "SE", "FE" are preserved + Assert.AreEqual ("Pixel 9 Pro XL", AdbRunner.FormatDisplayName ("Pixel_9_Pro_XL")); + Assert.AreEqual ("Galaxy S24 FE", AdbRunner.FormatDisplayName ("Galaxy_S24_FE")); + } + [Test] public void FormatDisplayName_PreservesNumbersAndSpecialChars () { From 208e5a584479423f9026f2b0fc0ef8a1d5a6ea40 Mon Sep 17 00:00:00 2001 From: Rui Marinho Date: Wed, 11 Mar 2026 14:01:06 +0000 Subject: [PATCH 245/308] Fix RS0026: split AcceptLicensesAsync into explicit overloads (#298) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary The two `AcceptLicensesAsync` overloads (parameterless and `IEnumerable`) both had optional `CancellationToken` parameters, which the PublicAPI analyzer (RS0026) flags as potentially ambiguous at call sites. This causes build failures in `dotnet/android` which treats warnings as errors via the submodule. ## Fix Instead of suppressing the warning with `#pragma`, this restructures the API to use **explicit overloads**: - `AcceptLicensesAsync()` → delegates to `AcceptLicensesAsync(CancellationToken.None)` - `AcceptLicensesAsync(CancellationToken)` → the actual implementation (no longer `= default`) - `AcceptLicensesAsync(IEnumerable)` → delegates to `AcceptLicensesAsync(licenseIds, CancellationToken.None)` - `AcceptLicensesAsync(IEnumerable, CancellationToken)` → the actual implementation (no longer `= default`) This is the [recommended fix](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1026) for RS0026 — it eliminates ambiguity while remaining source- and binary-compatible with existing callers. ### Files changed - **`SdkManager.Licenses.cs`** — split optional-parameter methods into explicit overload pairs - **`PublicAPI.Unshipped.txt`** (both TFMs) — updated to reflect the 4 distinct overloads (was 2) --- .../PublicAPI/net10.0/PublicAPI.Unshipped.txt | 6 ++++-- .../netstandard2.0/PublicAPI.Unshipped.txt | 6 ++++-- .../SdkManager.Licenses.cs | 15 +++++++++++++-- 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/PublicAPI/net10.0/PublicAPI.Unshipped.txt b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/PublicAPI/net10.0/PublicAPI.Unshipped.txt index 70283f7b667..a9553bc0be2 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/PublicAPI/net10.0/PublicAPI.Unshipped.txt +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/PublicAPI/net10.0/PublicAPI.Unshipped.txt @@ -90,8 +90,10 @@ Xamarin.Android.Tools.SdkLicense.SdkLicense(string! Id, string! Text) -> void Xamarin.Android.Tools.SdkLicense.Text.get -> string! Xamarin.Android.Tools.SdkLicense.Text.init -> void Xamarin.Android.Tools.SdkManager -Xamarin.Android.Tools.SdkManager.AcceptLicensesAsync(System.Collections.Generic.IEnumerable! licenseIds, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task! -Xamarin.Android.Tools.SdkManager.AcceptLicensesAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task! +Xamarin.Android.Tools.SdkManager.AcceptLicensesAsync() -> System.Threading.Tasks.Task! +Xamarin.Android.Tools.SdkManager.AcceptLicensesAsync(System.Collections.Generic.IEnumerable! licenseIds) -> System.Threading.Tasks.Task! +Xamarin.Android.Tools.SdkManager.AcceptLicensesAsync(System.Collections.Generic.IEnumerable! licenseIds, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task! +Xamarin.Android.Tools.SdkManager.AcceptLicensesAsync(System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task! Xamarin.Android.Tools.SdkManager.AndroidSdkPath.get -> string? Xamarin.Android.Tools.SdkManager.AndroidSdkPath.set -> void Xamarin.Android.Tools.SdkManager.AreLicensesAccepted() -> bool diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt index 70283f7b667..a9553bc0be2 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt @@ -90,8 +90,10 @@ Xamarin.Android.Tools.SdkLicense.SdkLicense(string! Id, string! Text) -> void Xamarin.Android.Tools.SdkLicense.Text.get -> string! Xamarin.Android.Tools.SdkLicense.Text.init -> void Xamarin.Android.Tools.SdkManager -Xamarin.Android.Tools.SdkManager.AcceptLicensesAsync(System.Collections.Generic.IEnumerable! licenseIds, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task! -Xamarin.Android.Tools.SdkManager.AcceptLicensesAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task! +Xamarin.Android.Tools.SdkManager.AcceptLicensesAsync() -> System.Threading.Tasks.Task! +Xamarin.Android.Tools.SdkManager.AcceptLicensesAsync(System.Collections.Generic.IEnumerable! licenseIds) -> System.Threading.Tasks.Task! +Xamarin.Android.Tools.SdkManager.AcceptLicensesAsync(System.Collections.Generic.IEnumerable! licenseIds, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task! +Xamarin.Android.Tools.SdkManager.AcceptLicensesAsync(System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task! Xamarin.Android.Tools.SdkManager.AndroidSdkPath.get -> string? Xamarin.Android.Tools.SdkManager.AndroidSdkPath.set -> void Xamarin.Android.Tools.SdkManager.AreLicensesAccepted() -> bool diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/SdkManager.Licenses.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/SdkManager.Licenses.cs index b3e907d7b1d..3262065e245 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/SdkManager.Licenses.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/SdkManager.Licenses.cs @@ -14,11 +14,16 @@ namespace Xamarin.Android.Tools; public partial class SdkManager { + /// + /// Accepts all SDK licenses using sdkmanager --licenses. + /// + public Task AcceptLicensesAsync () => AcceptLicensesAsync (CancellationToken.None); + /// /// Accepts all SDK licenses using sdkmanager --licenses. /// /// Cancellation token. - public async Task AcceptLicensesAsync (CancellationToken cancellationToken = default) + public async Task AcceptLicensesAsync (CancellationToken cancellationToken) { var sdkManagerPath = RequireSdkManagerPath (); logger (TraceLevel.Info, "Accepting SDK licenses..."); @@ -99,12 +104,18 @@ public async Task> GetPendingLicensesAsync (Cancellati return ParseLicenseOutput (stdout.ToString ()); } + /// + /// Accepts specific licenses by ID. + /// + /// The license IDs to accept (e.g., "android-sdk-license"). + public Task AcceptLicensesAsync (IEnumerable licenseIds) => AcceptLicensesAsync (licenseIds, CancellationToken.None); + /// /// Accepts specific licenses by ID. /// /// The license IDs to accept (e.g., "android-sdk-license"). /// Cancellation token. - public async Task AcceptLicensesAsync (IEnumerable licenseIds, CancellationToken cancellationToken = default) + public async Task AcceptLicensesAsync (IEnumerable licenseIds, CancellationToken cancellationToken) { ThrowIfDisposed (); if (licenseIds is null || !licenseIds.Any ()) From bc191f5984ee1c04edf718a77119a30823290e4a Mon Sep 17 00:00:00 2001 From: Rui Marinho Date: Wed, 11 Mar 2026 14:03:55 +0000 Subject: [PATCH 246/308] Add no-whitespace-only-diffs rule to copilot instructions (#296) Prevents line-ending or trailing-whitespace changes from creating full-file diffs that obscure the actual code change (as seen in PR #293). --- external/xamarin-android-tools/.github/copilot-instructions.md | 1 + 1 file changed, 1 insertion(+) diff --git a/external/xamarin-android-tools/.github/copilot-instructions.md b/external/xamarin-android-tools/.github/copilot-instructions.md index e2765ace979..181b752109a 100644 --- a/external/xamarin-android-tools/.github/copilot-instructions.md +++ b/external/xamarin-android-tools/.github/copilot-instructions.md @@ -46,6 +46,7 @@ When setting environment variables for SDK tools (e.g. `sdkmanager`, `avdmanager - **Concise XML docs**: omit `` tags for self-explanatory methods. Only add doc comments when the behavior is non-obvious. Avoid restating the method name. - **`netstandard2.0` awareness**: many modern .NET APIs are unavailable or have fewer overloads on `netstandard2.0`. When unsure about API availability, search mslearn to check documentation for the target framework. - **Format your code**: always match the existing file indentation (tabs, not spaces — see `.editorconfig`). Only format code you add or modify; never reformat existing lines. +- **No whitespace-only diffs**: before committing, run `git diff --stat` and verify only files with intentional code changes appear. If a file shows as fully rewritten (all lines removed and re-added) you have introduced line-ending or trailing-whitespace changes — revert that file with `git checkout -- ` and re-apply only your code change. Never commit whitespace-only or line-ending-only changes. - **File-scoped namespaces**: all new files should use file-scoped namespaces (`namespace Foo;` instead of `namespace Foo { ... }`). - **Static `HttpClient`**: `HttpClient` instances must be `static` to avoid socket exhaustion. See [HttpClient guidelines](https://learn.microsoft.com/dotnet/fundamentals/networking/http/httpclient-guidelines#recommended-use). Do not create per-instance `HttpClient` fields or dispose them in `IDisposable`. - [Mono Coding Guidelines](http://www.mono-project.com/community/contributing/coding-guidelines/): tabs, K&R braces, `PascalCase` public members. From c56b9cada27405128a6691b5b52a2175f1bf55a2 Mon Sep 17 00:00:00 2001 From: Rui Marinho Date: Wed, 11 Mar 2026 14:05:02 +0000 Subject: [PATCH 247/308] Fix `CS8604` nullable warning in `AndroidVersion.AlternateIds` (#293) Guard HashSet.UnionWith against null value using pattern matching, eliminating the CS8604 nullable reference warning on both TFMs. --- .../src/Xamarin.Android.Tools.AndroidSdk/AndroidVersion.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidVersion.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidVersion.cs index 911f3c1790f..31c95ab2f96 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidVersion.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidVersion.cs @@ -35,7 +35,10 @@ public class AndroidVersion // Alternate Ids for a given API level. Allows for historical mapping, e.g. API-11 has alternate ID 'H'. internal string[]? AlternateIds { - set => Ids.UnionWith (value); + set { + if (value is { Length: > 0 }) + Ids.UnionWith (value); + } } public AndroidVersion (int apiLevel, string osVersion, string? codeName = null, string? id = null, bool stable = true) From ee58c6a88ce6243c981eff54c7951fb6d0d28cbc Mon Sep 17 00:00:00 2001 From: Rui Marinho Date: Wed, 11 Mar 2026 15:17:05 +0000 Subject: [PATCH 248/308] Add PublicAPI file update rule to copilot instructions (#297) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a convention: when adding or removing `public` API, update the `PublicAPI.Unshipped.txt` files for both `net10.0` and `netstandard2.0` TFMs, and verify zero `RS0016`/`RS0017` warnings. Motivated by @jonathanpeppers feedback on #282 — new `AvdManagerRunner` and `AvdInfo` types were missing from the unshipped files after #294 added the analyzer. --- external/xamarin-android-tools/.github/copilot-instructions.md | 1 + 1 file changed, 1 insertion(+) diff --git a/external/xamarin-android-tools/.github/copilot-instructions.md b/external/xamarin-android-tools/.github/copilot-instructions.md index 181b752109a..6b6abfb7933 100644 --- a/external/xamarin-android-tools/.github/copilot-instructions.md +++ b/external/xamarin-android-tools/.github/copilot-instructions.md @@ -40,6 +40,7 @@ When setting environment variables for SDK tools (e.g. `sdkmanager`, `avdmanager - **One type per file**: each public class, struct, enum, or interface must be in its own `.cs` file named after the type (e.g. `JdkVersionInfo` → `JdkVersionInfo.cs`). Do not combine multiple top-level types in a single file. - **Minimal public API**: prefer `internal` for new methods/classes unless they are consumed by external projects (dotnet/android, IDE extensions). Use `InternalsVisibleTo` for test access. +- **Update PublicAPI files**: when adding or removing `public` API surface, update the `PublicAPI.Unshipped.txt` files under `src/Xamarin.Android.Tools.AndroidSdk/PublicAPI/net10.0/` and `src/Xamarin.Android.Tools.AndroidSdk/PublicAPI/netstandard2.0/`. New types and members go in the unshipped file. Build with `--no-incremental` and verify zero `RS0016` (missing) or `RS0017` (removed) warnings. See `PublicAPI.Shipped.txt` for the expected entry format. - **Use `ProcessUtils`**: never use `System.Diagnostics.Process` directly. Use the existing helpers such as `ProcessUtils.CreateProcessStartInfo()`, `ProcessUtils.StartProcess()`, and `ProcessUtils.ExecuteToolAsync()` for launching external tools. This ensures consistent logging, timeout handling, and cancellation. - **Process arguments**: use `ProcessUtils.CreateProcessStartInfo()` and pass arguments as separate strings instead of building a single arguments string yourself. `ProcessUtils` will use `ProcessStartInfo.ArgumentList` when available and fall back to `Arguments` on `netstandard2.0`. - **Use `FileUtil`**: file operations like extraction, downloads, checksum verification, and path checks belong in `FileUtil.cs`. Don't duplicate file helpers in domain classes. From 9fb3444d7458b587a1d1359600125fd67bdc8cbf Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Wed, 11 Mar 2026 14:16:48 -0500 Subject: [PATCH 249/308] [skills] Incorporate review feedback from dotnet/android#10918 (#299) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix submit_review.cs issues found during porting: - Replace GetString()! with ?? "" to avoid null-forgiving operator - Add null check for Process.Start() instead of ! - Narrow bare catch blocks to catch (JsonException) - Restructure success output so catch block isn't misleading Merge C#-focused review rules back into review-rules.md: - Async: thread safety of shared state - Error handling: actionable exception details, challenge exception swallowing - Code org: no commented-out code, early returns, skip default field init - Performance: pre-allocate collections, avoid closures, cheap checks first, O(n²) - Patterns: method names reflect behavior, why-not-what comments, track TODOs - YAGNI: git commit --amend, commit message quality, typos, filler words - New section 13: Testing (regression tests, specific assertions, deterministic data, edge cases) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Port review feedback from dotnet/android#10918 submit_review.cs: - Add ValueKind checks for path/body in comment validation to prevent InvalidOperationException on non-string JSON values - Treat non-array 'comments' field as a validation error instead of silently skipping - Log a note when API response is not JSON instead of silent comment review-rules.md: - Add ThrowIf helpers rule noting unavailability on netstandard2.0 --- .../skills/android-tools-reviewer/SKILL.md | 47 ++++++++---- .../references/review-rules.md | 34 ++++++++- .../scripts/submit_review.cs | 72 +++++++++++-------- 3 files changed, 110 insertions(+), 43 deletions(-) diff --git a/external/xamarin-android-tools/.github/skills/android-tools-reviewer/SKILL.md b/external/xamarin-android-tools/.github/skills/android-tools-reviewer/SKILL.md index 188c40488ca..5c222cf8090 100644 --- a/external/xamarin-android-tools/.github/skills/android-tools-reviewer/SKILL.md +++ b/external/xamarin-android-tools/.github/skills/android-tools-reviewer/SKILL.md @@ -12,6 +12,15 @@ description: >- Review PRs against guidelines distilled from past reviews by senior maintainers. +## Review Mindset + +Be polite but skeptical. Prioritize bugs, performance regressions, safety issues, and pattern violations over style nitpicks. **3 important comments > 15 nitpicks.** + +Flag severity clearly in every comment: +- ❌ **error** — Must fix before merge. Bugs, security issues, broken patterns. +- ⚠️ **warning** — Should fix. Performance issues, missing validation, inconsistency with patterns. +- 💡 **suggestion** — Consider changing. Style, readability, optional improvements. + ## Workflow ### 1. Parse the PR URL @@ -19,18 +28,30 @@ Review PRs against guidelines distilled from past reviews by senior maintainers. Extract `owner`, `repo`, `pr_number` from the URL. Formats: `https://github.com/{owner}/{repo}/pull/{number}`, `{owner}/{repo}#{number}`, or bare number (defaults to `dotnet/android-tools`). -### 2. Fetch PR data +### 2. Gather context (before reading PR description) ``` -gh pr view {number} --repo {owner}/{repo} --json title,body,files gh pr diff {number} --repo {owner}/{repo} +gh pr view {number} --repo {owner}/{repo} --json files +``` + +For each changed file, read the **full source file** (not just the diff) to understand surrounding invariants, call patterns, and data flow. If the change modifies a public/internal API or utility, search for callers. Check whether sibling types need the same fix. + +**Form an independent assessment** of what the change does and what problems it has *before* reading the PR description. + +### 3. Incorporate PR narrative and reconcile + ``` +gh pr view {number} --repo {owner}/{repo} --json title,body +``` + +Now read the PR description and linked issues. Treat them as claims to verify, not facts to accept. Where your independent reading disagrees with the PR description, investigate further. If the PR claims a performance improvement, require evidence. If it claims a bug fix, verify the bug exists and the fix addresses root cause — not symptoms. -### 3. Load review rules +### 4. Load review rules Read `references/review-rules.md` from this skill's directory. -### 4. Analyze the diff +### 5. Analyze the diff For each changed file, check against the review rules. Record issues as: @@ -41,23 +62,25 @@ For each changed file, check against the review rules. Record issues as: Constraints: - Only comment on added/modified lines in the diff — the API rejects out-of-range lines. - `line` = line number in the NEW file (right side). Double-check against the diff. -- High signal only: 3 important comments > 15 nitpicks. Skip style/formatting unless it violates a specific rule. - One issue per comment. +- **Don't pile on.** If the same issue appears many times, flag it once with a note listing all affected files. +- **Don't flag what CI catches.** Skip compiler errors, formatting the linter will catch, etc. +- **Avoid false positives.** Verify the concern actually applies given the full context. If unsure, phrase it as a question rather than a firm claim. -### 5. Build the review JSON +### 6. Build the review JSON Write a temp JSON file: ```json { "event": "COMMENT", - "body": "## 🤖 AI Review Summary\n\nFound **N issues**: ...\n\n- **Category**: description (`file:line`)\n\n👍 Positive callouts.\n\n---\n_Review generated by android-tools-reviewer from [review guidelines](../../../docs/CODE_REVIEW_POSTMORTEM.md) by @jonathanpeppers._", + "body": "## 🤖 AI Review Summary\n\n**Verdict**: ✅ LGTM | ⚠️ Needs Changes | ❌ Reject\n\nFound **N issues**: ...\n\n- ❌ **Category**: description (`file:line`)\n- ⚠️ **Category**: description (`file:line`)\n\n👍 Positive callouts.\n\n---\n_Review generated by android-tools-reviewer from [review guidelines](../../../docs/CODE_REVIEW_POSTMORTEM.md) by @jonathanpeppers._", "comments": [ { "path": "src/Example.cs", "line": 42, "side": "RIGHT", - "body": "🤖 **Error handling** — Every `catch` should capture the `Exception` and log it.\n\n_Rule: No empty catch blocks (Postmortem `#11`)_" + "body": "🤖 ❌ **Error handling** — Every `catch` should capture the `Exception` and log it.\n\n_Rule: No empty catch blocks (Postmortem `#11`)_" } ] } @@ -65,7 +88,7 @@ Write a temp JSON file: If no issues found, submit with empty `comments` and a positive summary. -### 6. Submit as a single batch +### 7. Submit as a single batch ```powershell dotnet run {skill-dir}/scripts/submit_review.cs {owner} {repo} {number} {path-to-json} @@ -76,11 +99,11 @@ The script validates structure (required fields, 🤖 prefix, positive line numb ## Comment format ``` -🤖 **{Category}** — {What's wrong and what to do instead.} +🤖 {severity} **{Category}** — {What's wrong and what to do instead.} _{Rule: Brief name (Postmortem `#N`)}_ ``` -Always wrap `#N` in backticks so GitHub doesn't auto-link to issues. +Where `{severity}` is ❌, ⚠️, or 💡. Always wrap `#N` in backticks so GitHub doesn't auto-link to issues. -**Categories:** Target framework · Async pattern · Resource management · Error handling · Security · Code organization · Naming · Performance · Pattern · YAGNI · API design · Code duplication · Documentation +**Categories:** Target framework · Async pattern · Resource management · Error handling · Security · Code organization · Naming · Performance · Pattern · YAGNI · API design · Code duplication · Testing · Documentation diff --git a/external/xamarin-android-tools/.github/skills/android-tools-reviewer/references/review-rules.md b/external/xamarin-android-tools/.github/skills/android-tools-reviewer/references/review-rules.md index f1a9b67e2dc..4af9bee8139 100644 --- a/external/xamarin-android-tools/.github/skills/android-tools-reviewer/references/review-rules.md +++ b/external/xamarin-android-tools/.github/skills/android-tools-reviewer/references/review-rules.md @@ -27,6 +27,7 @@ Framework. Every API call must work on both targets. | **CancellationToken propagation** | Every `async` method that accepts a `CancellationToken` must pass it to ALL downstream async calls (`GetAsync`, `ReadAsStreamAsync`, `SendAsync`, etc.). A token that's accepted but never used is a broken contract. | | **OperationCanceledException** | Catch-all blocks (`catch (Exception)`) must NOT swallow `OperationCanceledException`. Either catch it explicitly first and rethrow, or use a type filter. | | **GetStringAsync** | On netstandard2.0, `GetStringAsync(url)` doesn't accept a `CancellationToken`. Use `GetAsync(url, ct)` + `ReadAsStringAsync()` instead. | +| **Thread safety of shared state** | If a new field or property can be accessed from multiple threads (e.g., static caches, event handlers), verify thread-safe access: `ConcurrentDictionary`, `Interlocked`, or explicit locks. A `Dictionary` read concurrently with a write is undefined behavior. | **Postmortem refs:** #5, #37 @@ -53,6 +54,9 @@ Framework. Every API call must work on both targets. | **Validate parameters** | Enum parameters and string-typed "mode" values must be validated — throw `ArgumentException` or `NotSupportedException` for unexpected values. Don't silently accept garbage. | | **Fail fast on critical ops** | If an operation like `chmod` or checksum verification fails, throw immediately. Silently continuing leads to confusing downstream failures ("permission denied" when the real problem was chmod). | | **Mandatory verification** | Checksum/hash verification must NOT be optional. If the checksum can't be fetched, the operation must fail — not proceed unverified. | +| **Include actionable details in exceptions** | Use `nameof` for parameter names. Include the unsupported value or unexpected type. Never throw empty exceptions. | +| **Use `ThrowIf` helpers where available** | In .NET 10+ projects, prefer `ArgumentOutOfRangeException.ThrowIfNegative`, `ArgumentNullException.ThrowIfNull`, etc. over manual if-then-throw patterns. In `netstandard2.0` projects where these helpers are unavailable, use explicit checks such as `if (x is null) throw new ArgumentNullException (nameof (x));`. | +| **Challenge exception swallowing** | When a PR adds `catch { continue; }` or `catch { return null; }`, question whether the exception is truly expected or masking a deeper problem. The default should be to let unexpected exceptions propagate. | **Postmortem refs:** #11, #20, #22, #35 @@ -80,6 +84,9 @@ Framework. Every API call must work on both targets. | **No #region directives** | `#region` hides code and makes reviews harder. Remove them. This also applies to banner/section-separator comments (e.g., `// --- Device Tests ---`) — they serve the same purpose as `#region` and signal the file should be split instead. | | **Use `record` for data types** | Immutable data-carrier types (progress, version info, license info) should be `record` types. They get value equality, `ToString()`, and deconstruction for free. | | **Remove unused code** | Dead methods, speculative helpers, and code "for later" should be removed. Ship only what's needed. | +| **No commented-out code** | Commented-out code should be deleted — Git has history. | +| **Reduce indentation with early returns** | Invert conditions with `continue` or early `return` to reduce nesting. `foreach (var x in items ?? Array.Empty())` eliminates null-check nesting. | +| **Don't initialize fields to default values** | `bool flag = false;` and `int count = 0;` are noise. The CLR zero-initializes all fields. Only assign when the initial value is non-default. | **Postmortem refs:** #9, #12, #25, #28, #56 @@ -106,6 +113,10 @@ Framework. Every API call must work on both targets. | **p/invoke over process spawn** | For single syscalls like `chmod`, use `[DllImport("libc")]` instead of spawning a child process. Process creation is orders of magnitude more expensive. | | **Avoid intermediate collections** | Don't create two lists and `AddRange()` one to the other. Build a single list, or use LINQ to chain. | | **Cache reusable arrays** | Char arrays for `string.Split()` (like whitespace chars) should be `static readonly` fields, not allocated on each call. | +| **Pre-allocate collections when size is known** | Use `new List(capacity)` or `new Dictionary(count)` when the size is known or estimable. Repeated resizing is O(n) allocation waste. | +| **Avoid closures in hot paths** | Lambdas that capture local variables allocate a closure object on every call. In loops or frequently-called methods, extract the lambda to a static method or cache the delegate. | +| **Place cheap checks before expensive ones** | In validation chains, test simple conditions (null checks, boolean flags) before allocating strings or doing I/O. Short-circuit with `&&`/`||`. | +| **Watch for O(n²)** | Nested loops over the same or related collections, repeated `.Contains()` on a `List`, or LINQ `.Where()` inside a loop are O(n²). Switch to `HashSet` or `Dictionary` for lookups. | **Postmortem refs:** #8, #14, #21, #31 @@ -121,6 +132,10 @@ Framework. Every API call must work on both targets. | **Version-based directories** | Install SDK/JDK to versioned paths (`cmdline-tools/19.0/`, not `cmdline-tools/latest/`). Versioned paths are self-documenting and allow side-by-side installs. | | **Safe directory replacement** | Use move-with-rollback: rename existing → temp, move new in place, validate, delete temp only after validation succeeds. Never delete the backup before confirming the new install works. | | **Cross-volume moves** | `Directory.Move` is really a rename — it fails across filesystems. Extract archives near the target path (same parent directory), or catch `IOException` and fall back to recursive copy + delete. | +| **Method names must reflect behavior** | If `CreateFoo()` sometimes returns an existing instance, rename it `GetOrCreateFoo()` or `GetFoo()`. | +| **Comments explain "why", not "what"** | `// increment i` adds nothing. `// skip the BOM — aapt2 chokes on it` explains intent. If a comment restates the code, delete it. | +| **Track TODOs as issues** | A `// TODO` hidden in code will be forgotten. File an issue and reference it in the comment. | +| **Remove stale comments** | If the code changed, update the comment. Comments that describe old behavior are misleading. | **Postmortem refs:** #15, #16, #23, #36, #38 @@ -141,7 +156,11 @@ These are patterns that AI-generated code consistently gets wrong: | **Over-mocking** | Not everything needs to be mocked. Network integration tests with `Assert.Ignore` on failure are fine and catch real API changes that mocks never will. | | **Docs describe intent not reality** | AI doc comments often describe what the code *should* do, not what it *actually* does. Review doc comments against the implementation. | | **Unused parameters** | AI adds `CancellationToken` parameters but never observes them, or accepts `additionalArgs` as a string and interpolates it into a command. Unused CancellationToken is a broken contract; string args are injection risks. | -| **Null-forgiving operator (`!`)** | Never use `!` to suppress nullable warnings. If the value can be null, add a proper null check. If it can't be null, make the parameter/variable non-nullable. AI frequently sprinkles `!` to make the compiler happy — this turns compile-time warnings into runtime `NullReferenceException`s. Use `IsNullOrEmpty()` extension methods or null-coalescing instead. | +| **Null-forgiving operator (`!`)** | The postfix `!` null-forgiving operator (e.g., `foo!.Bar`) is banned. If the value can be null, add a proper null check. If it can't be null, make the parameter/variable non-nullable. AI frequently sprinkles `!` to make the compiler happy — this turns compile-time warnings into runtime `NullReferenceException`s. Use `IsNullOrEmpty()` extension methods or null-coalescing instead. Note: this rule is about the postfix `!` operator, not the logical negation `!` (e.g., `if (!someBool)` or `if (!string.IsNullOrEmpty (s))`). | +| **`git commit --amend`** | AI uses `--amend` on commits that are already pushed or belong to another author. Always create new commits — the maintainer will squash as needed. | +| **Commit messages omit non-obvious choices** | Behavioral decisions and known limitations belong in the commit message, not just the code. | +| **Typos in user-visible strings** | Users copy-paste error messages into bug reports. Get them right. | +| **Filler words in docs** | "So" at the start of a sentence adds nothing. Be direct. | **Postmortem refs:** #7, #28, #29, #40, #41, #42, #49, #50, #51, #52, #54 @@ -172,3 +191,16 @@ These are patterns that AI-generated code consistently gets wrong: | **Check exit codes consistently** | If one operation (`ListDevicesAsync`) checks the process exit code, ALL similar operations (`StopEmulatorAsync`, `WaitForDeviceAsync`) must too. Inconsistent error checking creates a false sense of safety. | **Postmortem refs:** #42, #43, #44, #45, #48 + +--- + +## 13. Testing + +| Check | What to look for | +|-------|-----------------| +| **Bug fixes need regression tests** | Every PR that fixes a bug should include a test that fails without the fix and passes with it. If the PR description says "fixes #N" but adds no test, ask for one. | +| **Test assertions must be specific** | `Assert.IsNotNull(result)` or `Assert.IsTrue(success)` don't tell you what went wrong. Prefer `Assert.AreEqual(expected, actual)` or NUnit constraints (`Assert.That` with `Does.Contain`, `Is.EqualTo`, etc.) for richer failure messages. | +| **Deterministic test data** | Tests should not depend on system locale, timezone, or current date. Use explicit `CultureInfo.InvariantCulture` and hardcoded dates when testing formatting. | +| **Test edge cases** | Empty collections, null inputs, boundary values, and very large inputs should all be considered. If the PR only tests the happy path, suggest edge cases. | + +**Source:** dotnet/android`#10918` diff --git a/external/xamarin-android-tools/.github/skills/android-tools-reviewer/scripts/submit_review.cs b/external/xamarin-android-tools/.github/skills/android-tools-reviewer/scripts/submit_review.cs index 66314d1454b..0cac123b5c8 100644 --- a/external/xamarin-android-tools/.github/skills/android-tools-reviewer/scripts/submit_review.cs +++ b/external/xamarin-android-tools/.github/skills/android-tools-reviewer/scripts/submit_review.cs @@ -43,7 +43,7 @@ if (!root.TryGetProperty ("event", out var eventProp) || eventProp.ValueKind != JsonValueKind.String) { errors.Add ("Missing or invalid 'event' field — must be COMMENT, APPROVE, or REQUEST_CHANGES"); } else { - var ev = eventProp.GetString ()!; + var ev = eventProp.GetString () ?? ""; if (ev != "COMMENT" && ev != "APPROVE" && ev != "REQUEST_CHANGES") errors.Add ($"Invalid event '{ev}' — must be COMMENT, APPROVE, or REQUEST_CHANGES"); } @@ -52,29 +52,38 @@ errors.Add ("Missing or empty 'body' field (the review summary)"); } -if (root.TryGetProperty ("comments", out var commentsProp) && commentsProp.ValueKind == JsonValueKind.Array) { - int i = 0; - foreach (var c in commentsProp.EnumerateArray ()) { - var prefix = $"comments[{i}]"; - - if (!c.TryGetProperty ("path", out var pathProp) || string.IsNullOrEmpty (pathProp.GetString ())) - errors.Add ($"{prefix}: missing 'path'"); - - if (!c.TryGetProperty ("line", out var lineProp) || lineProp.ValueKind != JsonValueKind.Number || lineProp.GetInt32 () < 1) - errors.Add ($"{prefix}: 'line' must be a positive integer"); - - if (!c.TryGetProperty ("body", out var cbody) || string.IsNullOrWhiteSpace (cbody.GetString ())) - errors.Add ($"{prefix}: missing or empty 'body'"); - else if (!cbody.GetString ()!.StartsWith ("🤖")) - errors.Add ($"{prefix}: body must start with 🤖 prefix"); - - if (c.TryGetProperty ("side", out var sideProp) && sideProp.ValueKind == JsonValueKind.String) { - var side = sideProp.GetString ()!; - if (side != "LEFT" && side != "RIGHT") - errors.Add ($"{prefix}: 'side' must be LEFT or RIGHT, got '{side}'"); +if (root.TryGetProperty ("comments", out var commentsProp)) { + if (commentsProp.ValueKind != JsonValueKind.Array) { + errors.Add ("Invalid 'comments' field — must be an array"); + } else { + int i = 0; + foreach (var c in commentsProp.EnumerateArray ()) { + var prefix = $"comments[{i}]"; + + if (!c.TryGetProperty ("path", out var pathProp) || pathProp.ValueKind != JsonValueKind.String || string.IsNullOrEmpty (pathProp.GetString ())) + errors.Add ($"{prefix}: missing 'path'"); + + if (!c.TryGetProperty ("line", out var lineProp) || lineProp.ValueKind != JsonValueKind.Number || lineProp.GetInt32 () < 1) + errors.Add ($"{prefix}: 'line' must be a positive integer"); + + if (!c.TryGetProperty ("body", out var cbody) || cbody.ValueKind != JsonValueKind.String) { + errors.Add ($"{prefix}: missing or empty 'body'"); + } else { + var commentBody = cbody.GetString () ?? ""; + if (string.IsNullOrWhiteSpace (commentBody)) + errors.Add ($"{prefix}: missing or empty 'body'"); + else if (!commentBody.StartsWith ("🤖")) + errors.Add ($"{prefix}: body must start with 🤖 prefix"); + } + + if (c.TryGetProperty ("side", out var sideProp) && sideProp.ValueKind == JsonValueKind.String) { + var side = sideProp.GetString () ?? ""; + if (side != "LEFT" && side != "RIGHT") + errors.Add ($"{prefix}: 'side' must be LEFT or RIGHT, got '{side}'"); + } + + i++; } - - i++; } } @@ -103,7 +112,11 @@ psi.ArgumentList.Add ("--input"); psi.ArgumentList.Add (jsonPath); -var process = Process.Start (psi)!; +using var process = Process.Start (psi); +if (process is null) { + Console.Error.WriteLine ("❌ Failed to start 'gh' — is it installed and on PATH?"); + return 1; +} var stdoutTask = process.StandardOutput.ReadToEndAsync (); var stderrTask = process.StandardError.ReadToEndAsync (); process.WaitForExit (); @@ -119,21 +132,20 @@ using var errDoc = JsonDocument.Parse (stdout); if (errDoc.RootElement.TryGetProperty ("message", out var msg)) Console.Error.WriteLine ($" GitHub says: {msg.GetString ()}"); - } catch { + } catch (JsonException) { Console.Error.WriteLine (stdout); } } return 1; } +Console.WriteLine ("✅ Review posted."); try { using var resp = JsonDocument.Parse (stdout); if (resp.RootElement.TryGetProperty ("html_url", out var url)) - Console.WriteLine ($"✅ Review posted: {url.GetString ()}"); - else - Console.WriteLine ("✅ Review posted."); -} catch { - Console.WriteLine ("✅ Review posted."); + Console.WriteLine ($" {url.GetString ()}"); +} catch (JsonException) { + Console.WriteLine ("Note: API response was not JSON; review was posted but URL is unavailable."); } } // using (doc) From b48b057ff1898aa2563a6e33d9b18b242ecde19c Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Wed, 11 Mar 2026 16:17:35 -0500 Subject: [PATCH 250/308] [skills] Document dotnet run file.cs for standalone C# scripts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit .NET 10 added support for running standalone .cs files with top-level statements directly via `dotnet run file.cs` — no .csproj needed. - copilot-instructions.md: add 'Running Scripts' section after Build & Test - SKILL.md: add note block explaining the feature and -- separator Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../xamarin-android-tools/.github/copilot-instructions.md | 8 ++++++++ .../.github/skills/android-tools-reviewer/SKILL.md | 6 +++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/external/xamarin-android-tools/.github/copilot-instructions.md b/external/xamarin-android-tools/.github/copilot-instructions.md index 6b6abfb7933..a18916c4e37 100644 --- a/external/xamarin-android-tools/.github/copilot-instructions.md +++ b/external/xamarin-android-tools/.github/copilot-instructions.md @@ -24,6 +24,14 @@ dotnet test tests/Microsoft.Android.Build.BaseTasks-Tests/Microsoft.Android.Buil Output: `bin\$(Configuration)\` (redistributables), `bin\Test$(Configuration)\` (tests). `$(DotNetTargetFrameworkVersion)` = `10.0` in `Directory.Build.props`. Versioning: `nuget.version` has `major.minor`; patch = git commit count since file changed. +## Running Scripts + +This repo uses `dotnet run file.cs` (.NET 10+ feature) to execute standalone C# scripts with top-level statements — no `.csproj` needed. Example: + +```sh +dotnet run .github/skills/android-tools-reviewer/scripts/submit_review.cs -- arg1 arg2 +``` + ## Android Environment Variables Per the [official Android docs](https://developer.android.com/tools/variables#envar): diff --git a/external/xamarin-android-tools/.github/skills/android-tools-reviewer/SKILL.md b/external/xamarin-android-tools/.github/skills/android-tools-reviewer/SKILL.md index 5c222cf8090..3541491b26a 100644 --- a/external/xamarin-android-tools/.github/skills/android-tools-reviewer/SKILL.md +++ b/external/xamarin-android-tools/.github/skills/android-tools-reviewer/SKILL.md @@ -91,9 +91,13 @@ If no issues found, submit with empty `comments` and a positive summary. ### 7. Submit as a single batch ```powershell -dotnet run {skill-dir}/scripts/submit_review.cs {owner} {repo} {number} {path-to-json} +dotnet run {skill-dir}/scripts/submit_review.cs -- {owner} {repo} {number} {path-to-json} ``` +> **Note:** `dotnet run file.cs` is a .NET 10+ feature that runs standalone C# files +> with top-level statements directly — no `.csproj` needed. Use `--` to separate +> `dotnet run` flags from script arguments. + The script validates structure (required fields, 🤖 prefix, positive line numbers) then calls `gh api`. Delete the temp file after success. ## Comment format From 684a7f4fb0f10addf1e3b6af78321b138d0f26c2 Mon Sep 17 00:00:00 2001 From: Rui Marinho Date: Thu, 12 Mar 2026 18:03:29 +0000 Subject: [PATCH 251/308] Add `AvdManagerRunner` for `avdmanager` CLI operations (#282) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds `AvdManagerRunner` — a typed async wrapper around the Android `avdmanager` CLI tool for creating, listing, and deleting Android Virtual Devices. Also adds `FindCmdlineTool` to `ProcessUtils` — discovers cmdline-tools binaries by checking the `latest` symlink first, then falling back to the highest versioned directory. ### API Surface ```csharp public class AvdManagerRunner { public AvdManagerRunner(string avdManagerPath, IDictionary? environmentVariables = null, Action? logger = null); public Task> ListAvdsAsync(CancellationToken cancellationToken = default); public Task GetOrCreateAvdAsync(string name, string systemImage, string? deviceProfile = null, bool force = false, CancellationToken cancellationToken = default); public Task DeleteAvdAsync(string name, CancellationToken cancellationToken = default); } public record AvdInfo(string Name, string? DeviceProfile = null, string? Path = null); ``` ```csharp // ProcessUtils (internal) internal static string? FindCmdlineTool(string sdkPath, string toolName, string extension, Action? logger = null); ``` ### Key Design Decisions - **`AvdInfo` is a positional `record`** — immutable, value equality, minimal boilerplate. - **Resolved-path constructor**: Caller resolves the full avdmanager path upfront (consistent with `AdbRunner`/`EmulatorRunner` pattern). - **`Action? logger`**: Optional diagnostic logger on constructor (consistent with all other runners). Logs command execution, get-or-create shortcuts, idempotent deletes, and IOException diagnostics. - **Get-or-create pattern**: `GetOrCreateAvdAsync` returns an existing AVD if one matches by name when `force=false`. Callers who need to overwrite orphaned AVDs pass `force: true` explicitly. - **Idempotent delete**: `DeleteAvdAsync` succeeds silently when the AVD doesn't exist. - **`FindCmdlineTool`**: Checks `latest` directory first (per SDK convention), then versioned directories sorted descending with stable preferred over prerelease. No legacy `tools/bin` fallback (different CLI args). Uses `Action? logger` for diagnostic messages (not `Debug.WriteLine`). ### Tests (25 total) | Category | Count | What | |----------|-------|------| | Parse AVD list | 6 | Multiple AVDs, Windows newlines, empty, no AVDs, single missing device, IReadOnlyList return type | | Constructor validation | 4 | Null/empty/whitespace throws, accepts env vars | | GetOrCreateAvdAsync validation | 6 | Null/empty/whitespace for both name and systemImage | | DeleteAvdAsync validation | 3 | Null/empty/whitespace name | | FindCmdlineTool | 6 | Versioned dir, higher version, prerelease, latest-first, missing SDK, stable-over-prerelease | --- .../Models/AvdInfo.cs | 6 + .../ProcessUtils.cs | 57 ++++ .../PublicAPI/net10.0/PublicAPI.Unshipped.txt | 13 + .../netstandard2.0/PublicAPI.Unshipped.txt | 13 + .../Runners/AvdManagerRunner.cs | 151 +++++++++ .../AvdManagerRunnerTests.cs | 296 ++++++++++++++++++ .../RunnerIntegrationTests.cs | 39 +++ 7 files changed, 575 insertions(+) create mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Models/AvdInfo.cs create mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Runners/AvdManagerRunner.cs create mode 100644 external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/AvdManagerRunnerTests.cs diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Models/AvdInfo.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Models/AvdInfo.cs new file mode 100644 index 00000000000..6116e6cf88c --- /dev/null +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Models/AvdInfo.cs @@ -0,0 +1,6 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Xamarin.Android.Tools; + +public record AvdInfo (string Name, string? DeviceProfile = null, string? Path = null); diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/ProcessUtils.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/ProcessUtils.cs index 8527ccd5da5..8ed2b93a5b6 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/ProcessUtils.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/ProcessUtils.cs @@ -230,6 +230,63 @@ internal static void ThrowIfFailed (int exitCode, string command, StringWriter? ThrowIfFailed (exitCode, command, stderr?.ToString (), stdout?.ToString ()); } + /// + /// Searches for a cmdline-tools binary in the SDK. + /// Prefers the "latest" symlink, then the highest versioned directory. + /// + /// Root path to the Android SDK. + /// Tool binary name without extension (e.g., "avdmanager"). + /// File extension including the dot (e.g., ".bat") or empty string for no extension. + /// Optional logger for diagnostic messages. + internal static string? FindCmdlineTool (string sdkPath, string toolName, string extension, Action? logger = null) + { + var cmdlineToolsDir = Path.Combine (sdkPath, "cmdline-tools"); + + if (Directory.Exists (cmdlineToolsDir)) { + // Prefer "latest" symlink first — it's the SDK's own recommended default + var latestPath = Path.Combine (cmdlineToolsDir, "latest", "bin", toolName + extension); + if (File.Exists (latestPath)) + return latestPath; + + try { + var subdirs = new List<(string name, Version version, bool isPreRelease)> (); + foreach (var dir in Directory.GetDirectories (cmdlineToolsDir)) { + var name = Path.GetFileName (dir); + if (string.IsNullOrEmpty (name) || name == "latest") + continue; + // Strip pre-release suffixes (e.g., "5.0-rc1" → "5.0") before parsing + var versionStr = name; + var dashIndex = name.IndexOf ('-'); + var isPreRelease = dashIndex >= 0; + if (isPreRelease) + versionStr = name.Substring (0, dashIndex); + Version.TryParse (versionStr, out var v); + subdirs.Add ((name, v ?? new Version (0, 0), isPreRelease)); + } + // Sort by version descending, then prefer stable (non-prerelease) over prerelease + subdirs.Sort ((a, b) => { + var cmp = b.version.CompareTo (a.version); + if (cmp != 0) return cmp; + if (a.isPreRelease != b.isPreRelease) + return a.isPreRelease ? 1 : -1; // stable first + return string.Compare (a.name, b.name, StringComparison.Ordinal); + }); + + foreach (var (name, _, _) in subdirs) { + var toolPath = Path.Combine (cmdlineToolsDir, name, "bin", toolName + extension); + if (File.Exists (toolPath)) + return toolPath; + } + } catch (IOException ex) { + logger?.Invoke (TraceLevel.Warning, $"FindCmdlineTool: IO error enumerating {cmdlineToolsDir}: {ex.Message}"); + } catch (UnauthorizedAccessException ex) { + logger?.Invoke (TraceLevel.Warning, $"FindCmdlineTool: Permission denied on {cmdlineToolsDir}: {ex.Message}"); + } + } + + return null; + } + internal static IEnumerable FindExecutablesInPath (string executable) { var path = Environment.GetEnvironmentVariable (EnvironmentVariableNames.Path) ?? ""; diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/PublicAPI/net10.0/PublicAPI.Unshipped.txt b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/PublicAPI/net10.0/PublicAPI.Unshipped.txt index a9553bc0be2..03a3352ec70 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/PublicAPI/net10.0/PublicAPI.Unshipped.txt +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/PublicAPI/net10.0/PublicAPI.Unshipped.txt @@ -132,3 +132,16 @@ static Xamarin.Android.Tools.JdkInstaller.SupportedVersions.get -> System.Collec static Xamarin.Android.Tools.ProcessUtils.CreateProcessStartInfo(string! fileName, params string![]! args) -> System.Diagnostics.ProcessStartInfo! static Xamarin.Android.Tools.ProcessUtils.IsElevated() -> bool static Xamarin.Android.Tools.ProcessUtils.StartProcess(System.Diagnostics.ProcessStartInfo! psi, System.IO.TextWriter? stdout, System.IO.TextWriter? stderr, System.Threading.CancellationToken cancellationToken, System.Collections.Generic.IDictionary? environmentVariables = null, System.Action? onStarted = null) -> System.Threading.Tasks.Task! +Xamarin.Android.Tools.AvdInfo +Xamarin.Android.Tools.AvdInfo.AvdInfo(string! Name, string? DeviceProfile = null, string? Path = null) -> void +Xamarin.Android.Tools.AvdInfo.DeviceProfile.get -> string? +Xamarin.Android.Tools.AvdInfo.DeviceProfile.init -> void +Xamarin.Android.Tools.AvdInfo.Name.get -> string! +Xamarin.Android.Tools.AvdInfo.Name.init -> void +Xamarin.Android.Tools.AvdInfo.Path.get -> string? +Xamarin.Android.Tools.AvdInfo.Path.init -> void +Xamarin.Android.Tools.AvdManagerRunner +Xamarin.Android.Tools.AvdManagerRunner.AvdManagerRunner(string! avdManagerPath, System.Collections.Generic.IDictionary? environmentVariables = null, System.Action? logger = null) -> void +Xamarin.Android.Tools.AvdManagerRunner.GetOrCreateAvdAsync(string! name, string! systemImage, string? deviceProfile = null, bool force = false, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task! +Xamarin.Android.Tools.AvdManagerRunner.DeleteAvdAsync(string! name, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task! +Xamarin.Android.Tools.AvdManagerRunner.ListAvdsAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task!>! diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt index a9553bc0be2..03a3352ec70 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt @@ -132,3 +132,16 @@ static Xamarin.Android.Tools.JdkInstaller.SupportedVersions.get -> System.Collec static Xamarin.Android.Tools.ProcessUtils.CreateProcessStartInfo(string! fileName, params string![]! args) -> System.Diagnostics.ProcessStartInfo! static Xamarin.Android.Tools.ProcessUtils.IsElevated() -> bool static Xamarin.Android.Tools.ProcessUtils.StartProcess(System.Diagnostics.ProcessStartInfo! psi, System.IO.TextWriter? stdout, System.IO.TextWriter? stderr, System.Threading.CancellationToken cancellationToken, System.Collections.Generic.IDictionary? environmentVariables = null, System.Action? onStarted = null) -> System.Threading.Tasks.Task! +Xamarin.Android.Tools.AvdInfo +Xamarin.Android.Tools.AvdInfo.AvdInfo(string! Name, string? DeviceProfile = null, string? Path = null) -> void +Xamarin.Android.Tools.AvdInfo.DeviceProfile.get -> string? +Xamarin.Android.Tools.AvdInfo.DeviceProfile.init -> void +Xamarin.Android.Tools.AvdInfo.Name.get -> string! +Xamarin.Android.Tools.AvdInfo.Name.init -> void +Xamarin.Android.Tools.AvdInfo.Path.get -> string? +Xamarin.Android.Tools.AvdInfo.Path.init -> void +Xamarin.Android.Tools.AvdManagerRunner +Xamarin.Android.Tools.AvdManagerRunner.AvdManagerRunner(string! avdManagerPath, System.Collections.Generic.IDictionary? environmentVariables = null, System.Action? logger = null) -> void +Xamarin.Android.Tools.AvdManagerRunner.GetOrCreateAvdAsync(string! name, string! systemImage, string? deviceProfile = null, bool force = false, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task! +Xamarin.Android.Tools.AvdManagerRunner.DeleteAvdAsync(string! name, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task! +Xamarin.Android.Tools.AvdManagerRunner.ListAvdsAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task!>! diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Runners/AvdManagerRunner.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Runners/AvdManagerRunner.cs new file mode 100644 index 00000000000..d678991b262 --- /dev/null +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Runners/AvdManagerRunner.cs @@ -0,0 +1,151 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Xamarin.Android.Tools; + +/// +/// Runs Android Virtual Device Manager (avdmanager) commands. +/// +public class AvdManagerRunner +{ + readonly string avdManagerPath; + readonly IDictionary? environmentVariables; + readonly Action? logger; + + /// + /// Creates a new AvdManagerRunner with the full path to the avdmanager executable. + /// + /// Full path to avdmanager (e.g., "/path/to/sdk/cmdline-tools/latest/bin/avdmanager"). + /// Optional environment variables to pass to avdmanager processes. + /// Optional logger callback for diagnostic messages. + public AvdManagerRunner (string avdManagerPath, IDictionary? environmentVariables = null, Action? logger = null) + { + if (string.IsNullOrWhiteSpace (avdManagerPath)) + throw new ArgumentException ("Path to avdmanager must not be empty.", nameof (avdManagerPath)); + this.avdManagerPath = avdManagerPath; + this.environmentVariables = environmentVariables; + this.logger = logger; + } + + public async Task> ListAvdsAsync (CancellationToken cancellationToken = default) + { + using var stdout = new StringWriter (); + using var stderr = new StringWriter (); + var psi = ProcessUtils.CreateProcessStartInfo (avdManagerPath, "list", "avd"); + logger?.Invoke (TraceLevel.Verbose, "Running: avdmanager list avd"); + var exitCode = await ProcessUtils.StartProcess (psi, stdout, stderr, cancellationToken, environmentVariables).ConfigureAwait (false); + + ProcessUtils.ThrowIfFailed (exitCode, "avdmanager list avd", stderr); + + return ParseAvdListOutput (stdout.ToString ()); + } + + /// + /// Creates an AVD with the specified name and system image. If is false + /// and an AVD with the same name already exists, returns the existing AVD without re-creating it. + /// + public async Task GetOrCreateAvdAsync (string name, string systemImage, string? deviceProfile = null, + bool force = false, CancellationToken cancellationToken = default) + { + if (string.IsNullOrWhiteSpace (name)) + throw new ArgumentException ("Value cannot be null or whitespace.", nameof (name)); + if (string.IsNullOrWhiteSpace (systemImage)) + throw new ArgumentException ("Value cannot be null or whitespace.", nameof (systemImage)); + + // Check if AVD already exists — return it instead of failing + if (!force) { + var existing = (await ListAvdsAsync (cancellationToken).ConfigureAwait (false)) + .FirstOrDefault (a => string.Equals (a.Name, name, StringComparison.OrdinalIgnoreCase)); + if (existing is not null) { + logger?.Invoke (TraceLevel.Verbose, $"AVD '{name}' already exists, returning existing"); + return existing; + } + } + + var args = new List { "create", "avd", "-n", name, "-k", systemImage }; + if (deviceProfile is { Length: > 0 }) + args.AddRange (new [] { "-d", deviceProfile }); + if (force) + args.Add ("--force"); + + using var stdout = new StringWriter (); + using var stderr = new StringWriter (); + var psi = ProcessUtils.CreateProcessStartInfo (avdManagerPath, args.ToArray ()); + psi.RedirectStandardInput = true; + + // avdmanager prompts "Do you wish to create a custom hardware profile?" — answer "no" + var exitCode = await ProcessUtils.StartProcess (psi, stdout, stderr, cancellationToken, environmentVariables, + onStarted: p => { + try { + p.StandardInput.WriteLine ("no"); + p.StandardInput.Close (); + } catch (IOException ex) { + logger?.Invoke (TraceLevel.Warning, $"Failed to write to avdmanager stdin: {ex.Message}"); + } + }).ConfigureAwait (false); + + ProcessUtils.ThrowIfFailed (exitCode, $"avdmanager create avd -n {name}", stderr, stdout); + + // Re-list to get the actual path from avdmanager (respects ANDROID_USER_HOME/ANDROID_AVD_HOME) + var avds = await ListAvdsAsync (cancellationToken).ConfigureAwait (false); + var created = avds.FirstOrDefault (a => string.Equals (a.Name, name, StringComparison.OrdinalIgnoreCase)); + if (created is not null) + return created; + + throw new InvalidOperationException ($"avdmanager reported success but AVD '{name}' was not found in the list."); + } + + public async Task DeleteAvdAsync (string name, CancellationToken cancellationToken = default) + { + if (string.IsNullOrWhiteSpace (name)) + throw new ArgumentException ("Value cannot be null or whitespace.", nameof (name)); + + // Idempotent: if the AVD doesn't exist, treat as success + var avds = await ListAvdsAsync (cancellationToken).ConfigureAwait (false); + if (!avds.Any (a => string.Equals (a.Name, name, StringComparison.OrdinalIgnoreCase))) { + logger?.Invoke (TraceLevel.Verbose, $"AVD '{name}' does not exist, nothing to delete"); + return; + } + + using var stderr = new StringWriter (); + var psi = ProcessUtils.CreateProcessStartInfo (avdManagerPath, "delete", "avd", "--name", name); + var exitCode = await ProcessUtils.StartProcess (psi, null, stderr, cancellationToken, environmentVariables).ConfigureAwait (false); + + ProcessUtils.ThrowIfFailed (exitCode, $"avdmanager delete avd --name {name}", stderr); + } + + internal static IReadOnlyList ParseAvdListOutput (string output) + { + var avds = new List (); + string? currentName = null, currentDevice = null, currentPath = null; + + foreach (var line in output.Split ('\n')) { + var trimmed = line.Trim (); + if (trimmed.StartsWith ("Name:", StringComparison.OrdinalIgnoreCase)) { + if (currentName is not null) + avds.Add (new AvdInfo (currentName, currentDevice, currentPath)); + currentName = trimmed.Substring (5).Trim (); + currentDevice = currentPath = null; + } + else if (trimmed.StartsWith ("Device:", StringComparison.OrdinalIgnoreCase)) + currentDevice = trimmed.Substring (7).Trim (); + else if (trimmed.StartsWith ("Path:", StringComparison.OrdinalIgnoreCase)) + currentPath = trimmed.Substring (5).Trim (); + } + + if (currentName is not null) + avds.Add (new AvdInfo (currentName, currentDevice, currentPath)); + + return avds; + } + +} + diff --git a/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/AvdManagerRunnerTests.cs b/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/AvdManagerRunnerTests.cs new file mode 100644 index 00000000000..6ae30de8292 --- /dev/null +++ b/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/AvdManagerRunnerTests.cs @@ -0,0 +1,296 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using NUnit.Framework; + +namespace Xamarin.Android.Tools.Tests; + +[TestFixture] +public class AvdManagerRunnerTests +{ + [Test] + public void ParseAvdListOutput_MultipleAvds () + { + var output = + "Available Android Virtual Devices:\n" + + " Name: Pixel_7_API_35\n" + + " Device: pixel_7 (Google)\n" + + " Path: /Users/test/.android/avd/Pixel_7_API_35.avd\n" + + " Target: Google APIs (Google Inc.)\n" + + " Based on: Android 15 Tag/ABI: google_apis/arm64-v8a\n" + + "---------\n" + + " Name: MAUI_Emulator\n" + + " Device: pixel_6 (Google)\n" + + " Path: /Users/test/.android/avd/MAUI_Emulator.avd\n" + + " Target: Google APIs (Google Inc.)\n" + + " Based on: Android 14 Tag/ABI: google_apis/x86_64\n"; + + var avds = AvdManagerRunner.ParseAvdListOutput (output); + + Assert.AreEqual (2, avds.Count); + + Assert.AreEqual ("Pixel_7_API_35", avds [0].Name); + Assert.AreEqual ("pixel_7 (Google)", avds [0].DeviceProfile); + Assert.AreEqual ("/Users/test/.android/avd/Pixel_7_API_35.avd", avds [0].Path); + + Assert.AreEqual ("MAUI_Emulator", avds [1].Name); + Assert.AreEqual ("pixel_6 (Google)", avds [1].DeviceProfile); + Assert.AreEqual ("/Users/test/.android/avd/MAUI_Emulator.avd", avds [1].Path); + } + + [Test] + public void ParseAvdListOutput_WindowsNewlines () + { + var output = + "Available Android Virtual Devices:\r\n" + + " Name: Test_AVD\r\n" + + " Device: Nexus 5X (Google)\r\n" + + " Path: C:\\Users\\test\\.android\\avd\\Test_AVD.avd\r\n" + + " Target: Google APIs (Google Inc.)\r\n"; + + var avds = AvdManagerRunner.ParseAvdListOutput (output); + + Assert.AreEqual (1, avds.Count); + Assert.AreEqual ("Test_AVD", avds [0].Name); + Assert.AreEqual ("Nexus 5X (Google)", avds [0].DeviceProfile); + Assert.AreEqual ("C:\\Users\\test\\.android\\avd\\Test_AVD.avd", avds [0].Path); + } + + [Test] + public void ParseAvdListOutput_EmptyOutput () + { + var avds = AvdManagerRunner.ParseAvdListOutput (""); + Assert.AreEqual (0, avds.Count); + } + + [Test] + public void ParseAvdListOutput_NoAvds () + { + var output = "Available Android Virtual Devices:\n"; + var avds = AvdManagerRunner.ParseAvdListOutput (output); + Assert.AreEqual (0, avds.Count); + } + + [Test] + public void ParseAvdListOutput_SingleAvdNoDevice () + { + var output = + " Name: Minimal_AVD\n" + + " Path: /home/user/.android/avd/Minimal_AVD.avd\n"; + + var avds = AvdManagerRunner.ParseAvdListOutput (output); + + Assert.AreEqual (1, avds.Count); + Assert.AreEqual ("Minimal_AVD", avds [0].Name); + Assert.IsNull (avds [0].DeviceProfile); + Assert.AreEqual ("/home/user/.android/avd/Minimal_AVD.avd", avds [0].Path); + } + + [Test] + public void ParseAvdListOutput_ReturnsIReadOnlyList () + { + var avds = AvdManagerRunner.ParseAvdListOutput (""); + Assert.IsInstanceOf> (avds); + } + + [Test] + public void FindCmdlineTool_FindsVersionedDir () + { + var tempDir = Path.Combine (Path.GetTempPath (), $"avd-test-{Path.GetRandomFileName ()}"); + var binDir = Path.Combine (tempDir, "cmdline-tools", "12.0", "bin"); + Directory.CreateDirectory (binDir); + + try { + var avdMgrName = OS.IsWindows ? "avdmanager.bat" : "avdmanager"; + File.WriteAllText (Path.Combine (binDir, avdMgrName), ""); + + var path = ProcessUtils.FindCmdlineTool (tempDir, "avdmanager", OS.IsWindows ? ".bat" : ""); + Assert.That (path, Does.Contain ("12.0")); + } finally { + Directory.Delete (tempDir, true); + } + } + + [Test] + public void FindCmdlineTool_PrefersHigherVersion () + { + var tempDir = Path.Combine (Path.GetTempPath (), $"avd-test-{Path.GetRandomFileName ()}"); + var avdMgrName = OS.IsWindows ? "avdmanager.bat" : "avdmanager"; + + var binDir10 = Path.Combine (tempDir, "cmdline-tools", "10.0", "bin"); + var binDir12 = Path.Combine (tempDir, "cmdline-tools", "12.0", "bin"); + Directory.CreateDirectory (binDir10); + Directory.CreateDirectory (binDir12); + File.WriteAllText (Path.Combine (binDir10, avdMgrName), ""); + File.WriteAllText (Path.Combine (binDir12, avdMgrName), ""); + + try { + var path = ProcessUtils.FindCmdlineTool (tempDir, "avdmanager", OS.IsWindows ? ".bat" : ""); + Assert.That (path, Does.Contain ("12.0")); + } finally { + Directory.Delete (tempDir, true); + } + } + + [Test] + public void FindCmdlineTool_HandlesPreReleaseVersionDir () + { + var tempDir = Path.Combine (Path.GetTempPath (), $"avd-test-{Path.GetRandomFileName ()}"); + var avdMgrName = OS.IsWindows ? "avdmanager.bat" : "avdmanager"; + + // Pre-release "13.0-rc1" should be preferred over "12.0" + var binDir12 = Path.Combine (tempDir, "cmdline-tools", "12.0", "bin"); + var binDirRc = Path.Combine (tempDir, "cmdline-tools", "13.0-rc1", "bin"); + Directory.CreateDirectory (binDir12); + Directory.CreateDirectory (binDirRc); + File.WriteAllText (Path.Combine (binDir12, avdMgrName), ""); + File.WriteAllText (Path.Combine (binDirRc, avdMgrName), ""); + + try { + var path = ProcessUtils.FindCmdlineTool (tempDir, "avdmanager", OS.IsWindows ? ".bat" : ""); + Assert.That (path, Does.Contain ("13.0-rc1")); + } finally { + Directory.Delete (tempDir, true); + } + } + + [Test] + public void FindCmdlineTool_PrefersLatest () + { + var tempDir = Path.Combine (Path.GetTempPath (), $"avd-test-{Path.GetRandomFileName ()}"); + var binDir = Path.Combine (tempDir, "cmdline-tools", "latest", "bin"); + Directory.CreateDirectory (binDir); + + try { + var avdMgrName = OS.IsWindows ? "avdmanager.bat" : "avdmanager"; + File.WriteAllText (Path.Combine (binDir, avdMgrName), ""); + + var path = ProcessUtils.FindCmdlineTool (tempDir, "avdmanager", OS.IsWindows ? ".bat" : ""); + Assert.That (path, Does.Contain ("latest")); + } finally { + Directory.Delete (tempDir, true); + } + } + + [Test] + public void FindCmdlineTool_MissingSdk_ReturnsNull () + { + var path = ProcessUtils.FindCmdlineTool ("/nonexistent/path", "avdmanager", OS.IsWindows ? ".bat" : ""); + Assert.IsNull (path); + } + + [Test] + public void Constructor_NullPath_ThrowsArgumentException () + { + Assert.Throws (() => new AvdManagerRunner (null!)); + } + + [Test] + public void Constructor_EmptyPath_ThrowsArgumentException () + { + Assert.Throws (() => new AvdManagerRunner ("")); + } + + [Test] + public void Constructor_WhitespacePath_ThrowsArgumentException () + { + Assert.Throws (() => new AvdManagerRunner (" ")); + } + + [Test] + public void Constructor_AcceptsEnvironmentVariables () + { + var env = new Dictionary { { "ANDROID_HOME", "/test/sdk" } }; + var runner = new AvdManagerRunner ("/fake/avdmanager", env); + Assert.IsNotNull (runner); + } + + [Test] + public void GetOrCreateAvdAsync_NullName_ThrowsArgumentException () + { + var runner = new AvdManagerRunner ("/fake/avdmanager"); + Assert.ThrowsAsync (() => runner.GetOrCreateAvdAsync (null!, "system-image")); + } + + [Test] + public void GetOrCreateAvdAsync_EmptyName_ThrowsArgumentException () + { + var runner = new AvdManagerRunner ("/fake/avdmanager"); + Assert.ThrowsAsync (() => runner.GetOrCreateAvdAsync ("", "system-image")); + } + + [Test] + public void GetOrCreateAvdAsync_WhitespaceName_ThrowsArgumentException () + { + var runner = new AvdManagerRunner ("/fake/avdmanager"); + Assert.ThrowsAsync (() => runner.GetOrCreateAvdAsync (" ", "system-image")); + } + + [Test] + public void GetOrCreateAvdAsync_NullSystemImage_ThrowsArgumentException () + { + var runner = new AvdManagerRunner ("/fake/avdmanager"); + Assert.ThrowsAsync (() => runner.GetOrCreateAvdAsync ("test-avd", null!)); + } + + [Test] + public void GetOrCreateAvdAsync_EmptySystemImage_ThrowsArgumentException () + { + var runner = new AvdManagerRunner ("/fake/avdmanager"); + Assert.ThrowsAsync (() => runner.GetOrCreateAvdAsync ("test-avd", "")); + } + + [Test] + public void GetOrCreateAvdAsync_WhitespaceSystemImage_ThrowsArgumentException () + { + var runner = new AvdManagerRunner ("/fake/avdmanager"); + Assert.ThrowsAsync (() => runner.GetOrCreateAvdAsync ("test-avd", " \t ")); + } + + [Test] + public void DeleteAvdAsync_NullName_ThrowsArgumentException () + { + var runner = new AvdManagerRunner ("/fake/avdmanager"); + Assert.ThrowsAsync (() => runner.DeleteAvdAsync (null!)); + } + + [Test] + public void DeleteAvdAsync_EmptyName_ThrowsArgumentException () + { + var runner = new AvdManagerRunner ("/fake/avdmanager"); + Assert.ThrowsAsync (() => runner.DeleteAvdAsync ("")); + } + + [Test] + public void DeleteAvdAsync_WhitespaceName_ThrowsArgumentException () + { + var runner = new AvdManagerRunner ("/fake/avdmanager"); + Assert.ThrowsAsync (() => runner.DeleteAvdAsync (" \t ")); + } + + [Test] + public void FindCmdlineTool_PrefersStableOverPreRelease () + { + var tempDir = Path.Combine (Path.GetTempPath (), $"avd-test-{Path.GetRandomFileName ()}"); + var avdMgrName = OS.IsWindows ? "avdmanager.bat" : "avdmanager"; + + // Both "13.0" (stable) and "13.0-rc1" (prerelease) exist — stable should win + var binDirStable = Path.Combine (tempDir, "cmdline-tools", "13.0", "bin"); + var binDirRc = Path.Combine (tempDir, "cmdline-tools", "13.0-rc1", "bin"); + Directory.CreateDirectory (binDirStable); + Directory.CreateDirectory (binDirRc); + File.WriteAllText (Path.Combine (binDirStable, avdMgrName), ""); + File.WriteAllText (Path.Combine (binDirRc, avdMgrName), ""); + + try { + var path = ProcessUtils.FindCmdlineTool (tempDir, "avdmanager", OS.IsWindows ? ".bat" : ""); + Assert.That (path, Does.Contain (Path.Combine ("13.0", "bin"))); + } finally { + Directory.Delete (tempDir, true); + } + } +} diff --git a/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/RunnerIntegrationTests.cs b/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/RunnerIntegrationTests.cs index 8b1a872f500..5f771026cc4 100644 --- a/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/RunnerIntegrationTests.cs +++ b/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/RunnerIntegrationTests.cs @@ -124,4 +124,43 @@ public void AllRunners_ToolDiscovery_ConsistentWithSdk () Assert.IsTrue (File.Exists (adbPath), $"adb should exist at {adbPath}"); StringAssert.StartsWith (sdkPath, adbPath); } + + [Test] + public void AvdManagerRunner_ToolDiscovery_FindsAvdManager () + { + var ext = OS.IsWindows ? ".bat" : ""; + var avdManagerPath = ProcessUtils.FindCmdlineTool (sdkPath, "avdmanager", ext); + + // avdmanager may not be present if cmdline-tools are not installed + if (avdManagerPath is null) { + Assert.Ignore ("avdmanager not found in SDK — cmdline-tools may not be installed."); + return; + } + + Assert.IsTrue (File.Exists (avdManagerPath), $"avdmanager should exist at {avdManagerPath}"); + TestContext.Progress.WriteLine ($"Found avdmanager at: {avdManagerPath}"); + } + + [Test] + public async Task AvdManagerRunner_ListAvdsAsync_ReturnsWithoutError () + { + var ext = OS.IsWindows ? ".bat" : ""; + var avdManagerPath = ProcessUtils.FindCmdlineTool (sdkPath, "avdmanager", ext); + + if (avdManagerPath is null) { + Assert.Ignore ("avdmanager not found in SDK — cmdline-tools may not be installed."); + return; + } + + var env = new System.Collections.Generic.Dictionary { + { EnvironmentVariableNames.JavaHome, jdkPath }, + { EnvironmentVariableNames.AndroidHome, sdkPath }, + }; + + var runner = new AvdManagerRunner (avdManagerPath, env); + var avds = await runner.ListAvdsAsync (); + + Assert.IsNotNull (avds); + TestContext.Progress.WriteLine ($"ListAvdsAsync returned {avds.Count} AVD(s)"); + } } From 0c1fda1065eab9fa96cda8917fe88556ed361cce Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Thu, 12 Mar 2026 13:07:46 -0500 Subject: [PATCH 252/308] [skills] Add logger pattern rule from PR #282 review Flag use of Debug.WriteLine() and Console.WriteLine() in catch blocks instead of the established Action logger delegate. Debug.WriteLine() only reaches attached debuggers (invisible in CI) and Console.WriteLine() bypasses MSBuild's logging pipeline. Added to section 9 (Patterns & Conventions) and cross-referenced in section 10 (YAGNI & AI Pitfalls). Source: https://github.com/dotnet/android-tools/pull/282#discussion_r2925449030 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../skills/android-tools-reviewer/references/review-rules.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/external/xamarin-android-tools/.github/skills/android-tools-reviewer/references/review-rules.md b/external/xamarin-android-tools/.github/skills/android-tools-reviewer/references/review-rules.md index 4af9bee8139..711e29e5518 100644 --- a/external/xamarin-android-tools/.github/skills/android-tools-reviewer/references/review-rules.md +++ b/external/xamarin-android-tools/.github/skills/android-tools-reviewer/references/review-rules.md @@ -127,6 +127,7 @@ Framework. Every API call must work on both targets. | Check | What to look for | |-------|-----------------| | **Use `ProcessUtils`** | All process creation must go through `ProcessUtils.CreateProcessStartInfo()` and `ProcessUtils.StartProcess()`. No direct `new ProcessStartInfo()` or `Process.Start()`. | +| **Use `Action` logger, not `Debug.WriteLine`** | Diagnostic output must use the existing `Action? logger` delegate pattern — never `System.Diagnostics.Debug.WriteLine()` or `Console.WriteLine()`. `Debug.WriteLine()` only reaches attached debuggers (invisible in CI and production), and `Console.WriteLine()` bypasses MSBuild's logging pipeline. Methods that might be called from MSBuild tasks must accept a `logger` parameter and invoke it with the appropriate `TraceLevel`. See `AndroidSdkInfo.DefaultConsoleLogger` for the canonical implementation. | | **Use `FileUtil`** | File extraction, downloads, checksum verification, and path operations belong in `FileUtil`. Don't duplicate file helpers in domain classes. | | **Null-object pattern** | Methods accepting nullable dependencies (`IProgress?`, `ILogger?`, `Action?`) should assign a null-object sentinel early (e.g., `progress ??= NullProgress.Instance`, `logger ??= NullLogger.Instance`) and then use the dependency without `?.` null checks throughout the method. Scattered `logger?.Log(...)` or `progress?.Report(...)` calls are a code smell — they add noise, invite missed spots, and signal a missing null-object type. If no null-object type exists yet, recommend creating one. | | **Version-based directories** | Install SDK/JDK to versioned paths (`cmdline-tools/19.0/`, not `cmdline-tools/latest/`). Versioned paths are self-documenting and allow side-by-side installs. | @@ -137,7 +138,7 @@ Framework. Every API call must work on both targets. | **Track TODOs as issues** | A `// TODO` hidden in code will be forgotten. File an issue and reference it in the comment. | | **Remove stale comments** | If the code changed, update the comment. Comments that describe old behavior are misleading. | -**Postmortem refs:** #15, #16, #23, #36, #38 +**Postmortem refs:** #15, #16, #23, #36, #38, [#282 comment](https://github.com/dotnet/android-tools/pull/282#discussion_r2925449030) --- @@ -157,6 +158,7 @@ These are patterns that AI-generated code consistently gets wrong: | **Docs describe intent not reality** | AI doc comments often describe what the code *should* do, not what it *actually* does. Review doc comments against the implementation. | | **Unused parameters** | AI adds `CancellationToken` parameters but never observes them, or accepts `additionalArgs` as a string and interpolates it into a command. Unused CancellationToken is a broken contract; string args are injection risks. | | **Null-forgiving operator (`!`)** | The postfix `!` null-forgiving operator (e.g., `foo!.Bar`) is banned. If the value can be null, add a proper null check. If it can't be null, make the parameter/variable non-nullable. AI frequently sprinkles `!` to make the compiler happy — this turns compile-time warnings into runtime `NullReferenceException`s. Use `IsNullOrEmpty()` extension methods or null-coalescing instead. Note: this rule is about the postfix `!` operator, not the logical negation `!` (e.g., `if (!someBool)` or `if (!string.IsNullOrEmpty (s))`). | +| **`Debug.WriteLine` for logging** | AI catch blocks often log with `System.Diagnostics.Debug.WriteLine()` or `Console.WriteLine()` — neither integrates with the codebase logger pattern (`Action`). See rule in §9. | | **`git commit --amend`** | AI uses `--amend` on commits that are already pushed or belong to another author. Always create new commits — the maintainer will squash as needed. | | **Commit messages omit non-obvious choices** | Behavioral decisions and known limitations belong in the commit message, not just the code. | | **Typos in user-visible strings** | Users copy-paste error messages into bug reports. Get them right. | From 5fb58b093663df141c5140024d246faaa1acaa3d Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Fri, 13 Mar 2026 11:06:46 -0500 Subject: [PATCH 253/308] [actions] Add `copilot-setup-steps.yml` for Copilot coding agent (#307) An initial setup that installs .NET 10 and does a restore. Co-authored-by: Jonathan Peppers --- .../.github/workflows/copilot-setup-steps.yml | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 external/xamarin-android-tools/.github/workflows/copilot-setup-steps.yml diff --git a/external/xamarin-android-tools/.github/workflows/copilot-setup-steps.yml b/external/xamarin-android-tools/.github/workflows/copilot-setup-steps.yml new file mode 100644 index 00000000000..c216dd5586a --- /dev/null +++ b/external/xamarin-android-tools/.github/workflows/copilot-setup-steps.yml @@ -0,0 +1,20 @@ +name: Copilot Setup Steps CI + +on: + pull_request: + paths: + - '.github/workflows/copilot-setup-steps.yml' + +permissions: + contents: read + pull-requests: write + +jobs: + validate: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-dotnet@v4 + with: + dotnet-version: "10.0.x" + - run: dotnet restore Xamarin.Android.Tools.sln || true From 5e6f270d7f62308a2b9151ca2dabc2eb27566d96 Mon Sep 17 00:00:00 2001 From: Rui Marinho Date: Thu, 19 Mar 2026 13:05:05 +0000 Subject: [PATCH 254/308] Add EmulatorRunner for emulator CLI operations (#284) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## EmulatorRunner: High-level emulator lifecycle management Adds `EmulatorRunner` — a managed wrapper over the Android SDK `emulator` CLI binary, following the same pattern as `AdbRunner` and `AvdManagerRunner`. ### API Surface | Method | Description | |--------|-------------| | `LaunchEmulator(avdName, options?)` | Fire-and-forget: starts an emulator process and returns the `Process` handle. Caller owns the process lifetime. Validates `avdName` is non-empty. | | `BootEmulatorAsync(avdName, adb, options?, token?)` | Full lifecycle: checks if device is already online → checks if emulator process is running → launches emulator → polls `adb devices` until boot completes or timeout. Returns `EmulatorBootResult` with status and serial. Disposes Process handle on success (emulator keeps running). | | `ListAvdNamesAsync(token?)` | Lists available AVD names via `emulator -list-avds`. Checks exit code for failures. | ### Key Design Decisions - **Naming**: `LaunchEmulator` (fire-and-forget) vs `BootEmulatorAsync` (full lifecycle) — clear verb distinction matching the emulator domain - **Kept `EmulatorRunner` name** (not `AvdRunner`) — follows convention of naming runners after their CLI binary (`emulator` → `EmulatorRunner`, `adb` → `AdbRunner`) - **Process handle management**: `LaunchEmulator` returns `Process` (caller-owned); `BootEmulatorAsync` disposes handle on success (emulator keeps running as detached process), kills+disposes on failure/timeout - **Pipe draining**: `LaunchEmulator` calls `BeginOutputReadLine()`/`BeginErrorReadLine()` after `Start()` to prevent OS pipe buffer deadlock - **`TryKillProcess`**: Instance method, uses typed `catch (Exception ex)` with logger for diagnostics, uses `Kill(entireProcessTree: true)` on .NET 5+ ### AdbRunner Enhancements (in this PR) - Added optional `Action? logger` parameter to constructor - `RunShellCommandAsync(serial, command, ct)` — single-string shell command (⚠️ device shell interprets it — documented in XML doc) - `RunShellCommandAsync(serial, command, args, ct)` — **NEW**: structured overload that passes args as separate tokens, bypassing device shell interpretation via `exec()`. Safer for dynamic input. - `GetShellPropertyAsync` returns first non-empty line (for `getprop` queries) - Shell methods log stderr via logger on non-zero exit codes - Fixed RS0026/RS0027: only the most-params overload has optional `CancellationToken` - **AVD name detection fix**: `GetEmulatorAvdNameAsync` now falls back to `adb shell getprop ro.boot.qemu.avd_name` when `adb emu avd name` returns empty (observed returning empty on some adb/emulator v36 combinations) ### Models - `EmulatorBootOptions` — configurable timeout (default 120s), poll interval (default 2s), cold boot, extra args (`IEnumerable?`) - `EmulatorBootResult` — immutable `record` with `init`-only properties: `Status` (enum), `Serial`, `Message`. Statuses: `Success`, `AlreadyRunning`, `Timeout`, `Error` ### Bug Fix: AVD Name Detection on Emulator v36+ The `adb emu avd name` console command can return **empty output** on some adb/emulator version combinations (observed with adb v36). This caused `BootEmulatorAsync` to never match the running emulator by AVD name, resulting in a perpetual polling loop and eventual timeout. **Root cause**: `GetEmulatorAvdNameAsync` relied solely on `adb -s emu avd name`. On some adb/emulator version combinations this command silently returns empty output (exit code 0, no content). The exact cause is unclear but the `getprop` fallback provides reliable AVD name resolution regardless. **Fix**: Added fallback to `adb shell getprop ro.boot.qemu.avd_name`, which reads the boot property set by the emulator kernel. This property is always available via the standard adb shell interface and does not depend on the emulator console protocol. **Verified**: `BootEmulatorAsync` now completes in ~3s (was timing out at 120s) on emulator v36.4.9 with API 36 image. ### Consumer PR - dotnet/android [#10949](https://github.com/dotnet/android/pull/10949) — replaces `BootAndroidEmulator` MSBuild task (~454 lines) with a ~180-line wrapper delegating to `EmulatorRunner.BootEmulatorAsync()` ### Tests (24 EmulatorRunner + 9 AdbRunner = 33 total) **EmulatorRunner (24)**: - Parse `emulator -list-avds` output (empty, single, multiple, blank lines, Windows newlines) — 4 tests - Constructor validation (null/empty/whitespace tool path) — 3 tests - `LaunchEmulator` argument validation (null, empty, whitespace AVD name) — 3 tests - `BootEmulatorAsync` lifecycle: already online device, already running AVD, successful boot after polling, timeout, launch failure, cancellation token — 6 tests - `BootEmulatorAsync` validation: invalid timeout, invalid poll interval, null AdbRunner, empty device name — 4 tests - Ported from dotnet/android `BootAndroidEmulatorTests`: physical device passthrough, AdditionalArgs forwarding, ColdBoot flag, cancellation abort — 4 tests **AdbRunner (9)**: - `FirstNonEmptyLine` parsing (null, empty, whitespace, single value, multiline, mixed) — 9 tests ### Review Feedback Addressed - ✅ `LaunchEmulator` validates `avdName` parameter (throws `ArgumentException`) - ✅ `LaunchEmulator` drains stdout/stderr pipes via `BeginOutputReadLine()`/`BeginErrorReadLine()` - ✅ `RunShellCommandAsync` returns full stdout (not just first line) - ✅ Added structured `RunShellCommandAsync` overload (no shell interpretation) - ✅ Added 12 new unit tests (LaunchEmulator validation + FirstNonEmptyLine parsing) - ✅ Shell methods log stderr via logger on failure - ✅ Removed TOCTOU `HasExited` guard from `TryKillProcess` - ✅ Process handle disposed on successful boot (no handle leak) - ✅ `ListAvdNamesAsync` checks exit code - ✅ `TryKillProcess` uses typed `catch (Exception ex)` with logging - ✅ `RunShellCommandAsync` XML doc warns about shell interpretation - ✅ Fixed RS0026/RS0027 PublicAPI analyzer warnings - ✅ `EmulatorBootResult` uses `init`-only properties (immutable record) - ✅ Ported 6 additional tests from dotnet/android `BootAndroidEmulatorTests` - ✅ Fixed AVD name detection for emulator v36+ (getprop fallback) ### Fix EmulatorRunner early exit detection and macOS fork handling ### Add early process exit detection in BootEmulatorAsync boot polling loop. Previously, if the emulator failed immediately (e.g., insufficient disk space, missing AVD), the full 300s timeout was wasted before reporting. On macOS, the emulator binary forks the real QEMU process and the parent exits with code 0 immediately. Only non-zero exit codes are treated as immediate failures; exit code 0 continues polling since the real emulator runs as a separate process. Context: dotnet/android#10965 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Models/EmulatorBootOptions.cs | 18 + .../Models/EmulatorBootResult.cs | 41 ++ .../PublicAPI/net10.0/PublicAPI.Unshipped.txt | 37 +- .../netstandard2.0/PublicAPI.Unshipped.txt | 37 +- .../Runners/AdbRunner.cs | 130 +++- .../Runners/AvdManagerRunner.cs | 12 +- .../Runners/EmulatorRunner.cs | 312 ++++++++++ .../Runners/RunnerDefaults.cs | 19 + .../AdbRunnerTests.cs | 59 ++ .../EmulatorRunnerTests.cs | 555 ++++++++++++++++++ 10 files changed, 1196 insertions(+), 24 deletions(-) create mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Models/EmulatorBootOptions.cs create mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Models/EmulatorBootResult.cs create mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Runners/EmulatorRunner.cs create mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Runners/RunnerDefaults.cs create mode 100644 external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/EmulatorRunnerTests.cs diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Models/EmulatorBootOptions.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Models/EmulatorBootOptions.cs new file mode 100644 index 00000000000..698e1a0e9da --- /dev/null +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Models/EmulatorBootOptions.cs @@ -0,0 +1,18 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; + +namespace Xamarin.Android.Tools; + +/// +/// Options for booting an Android emulator. +/// +public record EmulatorBootOptions +{ + public TimeSpan BootTimeout { get; init; } = TimeSpan.FromSeconds (300); + public List? AdditionalArgs { get; init; } + public bool ColdBoot { get; init; } + public TimeSpan PollInterval { get; init; } = TimeSpan.FromMilliseconds (500); +} diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Models/EmulatorBootResult.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Models/EmulatorBootResult.cs new file mode 100644 index 00000000000..e73cb939402 --- /dev/null +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Models/EmulatorBootResult.cs @@ -0,0 +1,41 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Xamarin.Android.Tools; + +/// +/// Classifies the reason an emulator boot operation failed. +/// +public enum EmulatorBootErrorKind +{ + /// No error — the boot succeeded. + None, + + /// The emulator process could not be launched (e.g., binary not found, AVD missing). + LaunchFailed, + + /// The emulator launched but did not finish booting within the allowed timeout. + Timeout, + + /// The boot was cancelled via . + Cancelled, + + /// An unexpected error occurred. + Unknown, +} + +/// +/// Result of an emulator boot operation. +/// +public record EmulatorBootResult +{ + public bool Success { get; init; } + public string? Serial { get; init; } + public string? ErrorMessage { get; init; } + + /// + /// Structured error classification. Consumers should switch on this value + /// instead of parsing strings. + /// + public EmulatorBootErrorKind ErrorKind { get; init; } +} diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/PublicAPI/net10.0/PublicAPI.Unshipped.txt b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/PublicAPI/net10.0/PublicAPI.Unshipped.txt index 03a3352ec70..9d51f08c9ec 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/PublicAPI/net10.0/PublicAPI.Unshipped.txt +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/PublicAPI/net10.0/PublicAPI.Unshipped.txt @@ -1,5 +1,4 @@ #nullable enable -*REMOVED*static Xamarin.Android.Tools.ProcessUtils.StartProcess(System.Diagnostics.ProcessStartInfo! psi, System.IO.TextWriter? stdout, System.IO.TextWriter? stderr, System.Threading.CancellationToken cancellationToken, System.Action? onStarted = null) -> System.Threading.Tasks.Task! Xamarin.Android.Tools.AdbDeviceInfo Xamarin.Android.Tools.AdbDeviceInfo.AdbDeviceInfo() -> void Xamarin.Android.Tools.AdbDeviceInfo.AvdName.get -> string? @@ -32,8 +31,8 @@ Xamarin.Android.Tools.AdbDeviceType Xamarin.Android.Tools.AdbDeviceType.Device = 0 -> Xamarin.Android.Tools.AdbDeviceType Xamarin.Android.Tools.AdbDeviceType.Emulator = 1 -> Xamarin.Android.Tools.AdbDeviceType Xamarin.Android.Tools.AdbRunner -Xamarin.Android.Tools.AdbRunner.AdbRunner(string! adbPath, System.Collections.Generic.IDictionary? environmentVariables = null) -> void -Xamarin.Android.Tools.AdbRunner.ListDevicesAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task!>! +Xamarin.Android.Tools.AdbRunner.AdbRunner(string! adbPath, System.Collections.Generic.IDictionary? environmentVariables = null, System.Action? logger = null) -> void +virtual Xamarin.Android.Tools.AdbRunner.ListDevicesAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task!>! Xamarin.Android.Tools.AdbRunner.StopEmulatorAsync(string! serial, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task! Xamarin.Android.Tools.AdbRunner.WaitForDeviceAsync(string? serial = null, System.TimeSpan? timeout = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task! Xamarin.Android.Tools.ChecksumType @@ -145,3 +144,35 @@ Xamarin.Android.Tools.AvdManagerRunner.AvdManagerRunner(string! avdManagerPath, Xamarin.Android.Tools.AvdManagerRunner.GetOrCreateAvdAsync(string! name, string! systemImage, string? deviceProfile = null, bool force = false, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task! Xamarin.Android.Tools.AvdManagerRunner.DeleteAvdAsync(string! name, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task! Xamarin.Android.Tools.AvdManagerRunner.ListAvdsAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task!>! +virtual Xamarin.Android.Tools.AdbRunner.GetShellPropertyAsync(string! serial, string! propertyName, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task! +virtual Xamarin.Android.Tools.AdbRunner.RunShellCommandAsync(string! serial, string! command, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task! +virtual Xamarin.Android.Tools.AdbRunner.RunShellCommandAsync(string! serial, string! command, string![]! args, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task! +Xamarin.Android.Tools.EmulatorBootOptions +Xamarin.Android.Tools.EmulatorBootOptions.AdditionalArgs.get -> System.Collections.Generic.List? +Xamarin.Android.Tools.EmulatorBootOptions.AdditionalArgs.init -> void +Xamarin.Android.Tools.EmulatorBootOptions.BootTimeout.get -> System.TimeSpan +Xamarin.Android.Tools.EmulatorBootOptions.BootTimeout.init -> void +Xamarin.Android.Tools.EmulatorBootOptions.ColdBoot.get -> bool +Xamarin.Android.Tools.EmulatorBootOptions.ColdBoot.init -> void +Xamarin.Android.Tools.EmulatorBootOptions.PollInterval.get -> System.TimeSpan +Xamarin.Android.Tools.EmulatorBootOptions.PollInterval.init -> void +Xamarin.Android.Tools.EmulatorBootErrorKind +Xamarin.Android.Tools.EmulatorBootErrorKind.None = 0 -> Xamarin.Android.Tools.EmulatorBootErrorKind +Xamarin.Android.Tools.EmulatorBootErrorKind.LaunchFailed = 1 -> Xamarin.Android.Tools.EmulatorBootErrorKind +Xamarin.Android.Tools.EmulatorBootErrorKind.Timeout = 2 -> Xamarin.Android.Tools.EmulatorBootErrorKind +Xamarin.Android.Tools.EmulatorBootErrorKind.Cancelled = 3 -> Xamarin.Android.Tools.EmulatorBootErrorKind +Xamarin.Android.Tools.EmulatorBootErrorKind.Unknown = 4 -> Xamarin.Android.Tools.EmulatorBootErrorKind +Xamarin.Android.Tools.EmulatorBootResult +Xamarin.Android.Tools.EmulatorBootResult.ErrorKind.get -> Xamarin.Android.Tools.EmulatorBootErrorKind +Xamarin.Android.Tools.EmulatorBootResult.ErrorKind.init -> void +Xamarin.Android.Tools.EmulatorBootResult.ErrorMessage.get -> string? +Xamarin.Android.Tools.EmulatorBootResult.ErrorMessage.init -> void +Xamarin.Android.Tools.EmulatorBootResult.Serial.get -> string? +Xamarin.Android.Tools.EmulatorBootResult.Serial.init -> void +Xamarin.Android.Tools.EmulatorBootResult.Success.get -> bool +Xamarin.Android.Tools.EmulatorBootResult.Success.init -> void +Xamarin.Android.Tools.EmulatorRunner +Xamarin.Android.Tools.EmulatorRunner.BootEmulatorAsync(string! deviceOrAvdName, Xamarin.Android.Tools.AdbRunner! adbRunner, Xamarin.Android.Tools.EmulatorBootOptions? options = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task! +Xamarin.Android.Tools.EmulatorRunner.EmulatorRunner(string! emulatorPath, System.Collections.Generic.IDictionary? environmentVariables = null, System.Action? logger = null) -> void +Xamarin.Android.Tools.EmulatorRunner.ListAvdNamesAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task!>! +Xamarin.Android.Tools.EmulatorRunner.LaunchEmulator(string! avdName, bool coldBoot = false, System.Collections.Generic.List? additionalArgs = null) -> System.Diagnostics.Process! diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt index 03a3352ec70..9d51f08c9ec 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt @@ -1,5 +1,4 @@ #nullable enable -*REMOVED*static Xamarin.Android.Tools.ProcessUtils.StartProcess(System.Diagnostics.ProcessStartInfo! psi, System.IO.TextWriter? stdout, System.IO.TextWriter? stderr, System.Threading.CancellationToken cancellationToken, System.Action? onStarted = null) -> System.Threading.Tasks.Task! Xamarin.Android.Tools.AdbDeviceInfo Xamarin.Android.Tools.AdbDeviceInfo.AdbDeviceInfo() -> void Xamarin.Android.Tools.AdbDeviceInfo.AvdName.get -> string? @@ -32,8 +31,8 @@ Xamarin.Android.Tools.AdbDeviceType Xamarin.Android.Tools.AdbDeviceType.Device = 0 -> Xamarin.Android.Tools.AdbDeviceType Xamarin.Android.Tools.AdbDeviceType.Emulator = 1 -> Xamarin.Android.Tools.AdbDeviceType Xamarin.Android.Tools.AdbRunner -Xamarin.Android.Tools.AdbRunner.AdbRunner(string! adbPath, System.Collections.Generic.IDictionary? environmentVariables = null) -> void -Xamarin.Android.Tools.AdbRunner.ListDevicesAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task!>! +Xamarin.Android.Tools.AdbRunner.AdbRunner(string! adbPath, System.Collections.Generic.IDictionary? environmentVariables = null, System.Action? logger = null) -> void +virtual Xamarin.Android.Tools.AdbRunner.ListDevicesAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task!>! Xamarin.Android.Tools.AdbRunner.StopEmulatorAsync(string! serial, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task! Xamarin.Android.Tools.AdbRunner.WaitForDeviceAsync(string? serial = null, System.TimeSpan? timeout = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task! Xamarin.Android.Tools.ChecksumType @@ -145,3 +144,35 @@ Xamarin.Android.Tools.AvdManagerRunner.AvdManagerRunner(string! avdManagerPath, Xamarin.Android.Tools.AvdManagerRunner.GetOrCreateAvdAsync(string! name, string! systemImage, string? deviceProfile = null, bool force = false, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task! Xamarin.Android.Tools.AvdManagerRunner.DeleteAvdAsync(string! name, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task! Xamarin.Android.Tools.AvdManagerRunner.ListAvdsAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task!>! +virtual Xamarin.Android.Tools.AdbRunner.GetShellPropertyAsync(string! serial, string! propertyName, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task! +virtual Xamarin.Android.Tools.AdbRunner.RunShellCommandAsync(string! serial, string! command, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.Task! +virtual Xamarin.Android.Tools.AdbRunner.RunShellCommandAsync(string! serial, string! command, string![]! args, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task! +Xamarin.Android.Tools.EmulatorBootOptions +Xamarin.Android.Tools.EmulatorBootOptions.AdditionalArgs.get -> System.Collections.Generic.List? +Xamarin.Android.Tools.EmulatorBootOptions.AdditionalArgs.init -> void +Xamarin.Android.Tools.EmulatorBootOptions.BootTimeout.get -> System.TimeSpan +Xamarin.Android.Tools.EmulatorBootOptions.BootTimeout.init -> void +Xamarin.Android.Tools.EmulatorBootOptions.ColdBoot.get -> bool +Xamarin.Android.Tools.EmulatorBootOptions.ColdBoot.init -> void +Xamarin.Android.Tools.EmulatorBootOptions.PollInterval.get -> System.TimeSpan +Xamarin.Android.Tools.EmulatorBootOptions.PollInterval.init -> void +Xamarin.Android.Tools.EmulatorBootErrorKind +Xamarin.Android.Tools.EmulatorBootErrorKind.None = 0 -> Xamarin.Android.Tools.EmulatorBootErrorKind +Xamarin.Android.Tools.EmulatorBootErrorKind.LaunchFailed = 1 -> Xamarin.Android.Tools.EmulatorBootErrorKind +Xamarin.Android.Tools.EmulatorBootErrorKind.Timeout = 2 -> Xamarin.Android.Tools.EmulatorBootErrorKind +Xamarin.Android.Tools.EmulatorBootErrorKind.Cancelled = 3 -> Xamarin.Android.Tools.EmulatorBootErrorKind +Xamarin.Android.Tools.EmulatorBootErrorKind.Unknown = 4 -> Xamarin.Android.Tools.EmulatorBootErrorKind +Xamarin.Android.Tools.EmulatorBootResult +Xamarin.Android.Tools.EmulatorBootResult.ErrorKind.get -> Xamarin.Android.Tools.EmulatorBootErrorKind +Xamarin.Android.Tools.EmulatorBootResult.ErrorKind.init -> void +Xamarin.Android.Tools.EmulatorBootResult.ErrorMessage.get -> string? +Xamarin.Android.Tools.EmulatorBootResult.ErrorMessage.init -> void +Xamarin.Android.Tools.EmulatorBootResult.Serial.get -> string? +Xamarin.Android.Tools.EmulatorBootResult.Serial.init -> void +Xamarin.Android.Tools.EmulatorBootResult.Success.get -> bool +Xamarin.Android.Tools.EmulatorBootResult.Success.init -> void +Xamarin.Android.Tools.EmulatorRunner +Xamarin.Android.Tools.EmulatorRunner.BootEmulatorAsync(string! deviceOrAvdName, Xamarin.Android.Tools.AdbRunner! adbRunner, Xamarin.Android.Tools.EmulatorBootOptions? options = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task! +Xamarin.Android.Tools.EmulatorRunner.EmulatorRunner(string! emulatorPath, System.Collections.Generic.IDictionary? environmentVariables = null, System.Action? logger = null) -> void +Xamarin.Android.Tools.EmulatorRunner.ListAvdNamesAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task!>! +Xamarin.Android.Tools.EmulatorRunner.LaunchEmulator(string! avdName, bool coldBoot = false, System.Collections.Generic.List? additionalArgs = null) -> System.Diagnostics.Process! diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Runners/AdbRunner.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Runners/AdbRunner.cs index 24a62cec80a..0dd60476966 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Runners/AdbRunner.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Runners/AdbRunner.cs @@ -21,6 +21,7 @@ public class AdbRunner { readonly string adbPath; readonly IDictionary? environmentVariables; + readonly Action logger; // Pattern to match device lines: [key:value ...] // Uses \s+ to match one or more whitespace characters (spaces or tabs) between fields. @@ -35,19 +36,21 @@ public class AdbRunner /// /// Full path to the adb executable (e.g., "/path/to/sdk/platform-tools/adb"). /// Optional environment variables to pass to adb processes. - public AdbRunner (string adbPath, IDictionary? environmentVariables = null) + /// Optional logger callback for diagnostic messages. + public AdbRunner (string adbPath, IDictionary? environmentVariables = null, Action? logger = null) { if (string.IsNullOrWhiteSpace (adbPath)) throw new ArgumentException ("Path to adb must not be empty.", nameof (adbPath)); this.adbPath = adbPath; this.environmentVariables = environmentVariables; + this.logger = logger ?? RunnerDefaults.NullLogger; } /// /// Lists connected devices using 'adb devices -l'. /// For emulators, queries the AVD name using 'adb -s <serial> emu avd name'. /// - public async Task> ListDevicesAsync (CancellationToken cancellationToken = default) + public virtual async Task> ListDevicesAsync (CancellationToken cancellationToken = default) { using var stdout = new StringWriter (); using var stderr = new StringWriter (); @@ -70,12 +73,16 @@ public async Task> ListDevicesAsync (CancellationTo } /// - /// Queries the emulator for its AVD name using 'adb -s <serial> emu avd name'. - /// Returns null if the query fails or produces no output. - /// Ported from dotnet/android GetAvailableAndroidDevices.GetEmulatorAvdName. + /// Queries the emulator for its AVD name. + /// Tries adb -s <serial> emu avd name first (emulator console protocol), + /// then falls back to adb shell getprop ro.boot.qemu.avd_name which reads the + /// boot property set by the emulator kernel. The fallback is needed because + /// emu avd name can return empty output on some adb/emulator version + /// combinations (observed with adb v36). /// internal async Task GetEmulatorAvdNameAsync (string serial, CancellationToken cancellationToken = default) { + // Try 1: Console command (fast, works on most emulator versions) try { using var stdout = new StringWriter (); var psi = ProcessUtils.CreateProcessStartInfo (adbPath, "-s", serial, "emu", "avd", "name"); @@ -90,8 +97,19 @@ public async Task> ListDevicesAsync (CancellationTo } } catch (OperationCanceledException) { throw; - } catch (Exception ex) { - Trace.WriteLine ($"GetEmulatorAvdNameAsync adb query failed for '{serial}': {ex.Message}"); + } catch { + // Fall through to getprop fallback + } + + // Try 2: Shell property (fallback when 'adb emu avd name' returns empty on some adb/emulator versions) + try { + var avdName = await GetShellPropertyAsync (serial, "ro.boot.qemu.avd_name", cancellationToken).ConfigureAwait (false); + if (avdName is { Length: > 0 } name && !string.IsNullOrWhiteSpace (name)) + return name.Trim (); + } catch (OperationCanceledException) { + throw; + } catch { + // Both methods failed } return null; @@ -135,6 +153,92 @@ public async Task StopEmulatorAsync (string serial, CancellationToken cancellati ProcessUtils.ThrowIfFailed (exitCode, $"adb -s {serial} emu kill", stderr); } + /// + /// Gets a system property from a device via 'adb -s <serial> shell getprop <property>'. + /// Returns the property value (first non-empty line of stdout), or null on failure. + /// + public virtual async Task GetShellPropertyAsync (string serial, string propertyName, CancellationToken cancellationToken = default) + { + using var stdout = new StringWriter (); + using var stderr = new StringWriter (); + var psi = ProcessUtils.CreateProcessStartInfo (adbPath, "-s", serial, "shell", "getprop", propertyName); + var exitCode = await ProcessUtils.StartProcess (psi, stdout, stderr, cancellationToken, environmentVariables).ConfigureAwait (false); + if (exitCode != 0) { + var stderrText = stderr.ToString ().Trim (); + if (stderrText.Length > 0) + logger.Invoke (TraceLevel.Warning, $"adb shell getprop {propertyName} failed (exit {exitCode}): {stderrText}"); + return null; + } + return FirstNonEmptyLine (stdout.ToString ()); + } + + /// + /// Runs a shell command on a device via 'adb -s <serial> shell <command>'. + /// Returns the full stdout output trimmed, or null on failure. + /// + /// + /// The is passed as a single argument to adb shell, + /// which means the device's shell interprets it (shell expansion, pipes, semicolons are active). + /// Do not pass untrusted or user-supplied input without proper validation. + /// + public virtual async Task RunShellCommandAsync (string serial, string command, CancellationToken cancellationToken) + { + using var stdout = new StringWriter (); + using var stderr = new StringWriter (); + var psi = ProcessUtils.CreateProcessStartInfo (adbPath, "-s", serial, "shell", command); + var exitCode = await ProcessUtils.StartProcess (psi, stdout, stderr, cancellationToken, environmentVariables).ConfigureAwait (false); + if (exitCode != 0) { + var stderrText = stderr.ToString ().Trim (); + if (stderrText.Length > 0) + logger.Invoke (TraceLevel.Warning, $"adb shell {command} failed (exit {exitCode}): {stderrText}"); + return null; + } + var output = stdout.ToString ().Trim (); + return output.Length > 0 ? output : null; + } + + /// + /// Runs a shell command on a device via adb -s <serial> shell <command> <args>. + /// Returns the full stdout output trimmed, or null on failure. + /// + /// + /// When adb shell receives the command and arguments as separate tokens, it uses + /// exec() directly on the device — bypassing the device's shell interpreter. + /// This avoids shell expansion, pipes, and injection risks, making it safer for dynamic input. + /// + public virtual async Task RunShellCommandAsync (string serial, string command, string[] args, CancellationToken cancellationToken = default) + { + using var stdout = new StringWriter (); + using var stderr = new StringWriter (); + // Build: adb -s shell ... + var allArgs = new string [3 + 1 + args.Length]; + allArgs [0] = "-s"; + allArgs [1] = serial; + allArgs [2] = "shell"; + allArgs [3] = command; + Array.Copy (args, 0, allArgs, 4, args.Length); + var psi = ProcessUtils.CreateProcessStartInfo (adbPath, allArgs); + var exitCode = await ProcessUtils.StartProcess (psi, stdout, stderr, cancellationToken, environmentVariables).ConfigureAwait (false); + if (exitCode != 0) { + var stderrText = stderr.ToString ().Trim (); + if (stderrText.Length > 0) + logger.Invoke (TraceLevel.Warning, $"adb shell {command} failed (exit {exitCode}): {stderrText}"); + return null; + } + var output = stdout.ToString ().Trim (); + return output.Length > 0 ? output : null; + } + + internal static string? FirstNonEmptyLine (string output) + { + foreach (var line in output.Split ('\n')) { + var trimmed = line.Trim (); + if (trimmed.Length > 0) + return trimmed; + } + return null; + } + /// /// Parses the output lines from 'adb devices -l'. /// Accepts an to avoid allocating a joined string. @@ -219,10 +323,11 @@ public static IReadOnlyList ParseAdbDevicesOutput (IEnumerable public static string BuildDeviceDescription (AdbDeviceInfo device, Action? logger = null) { + logger ??= RunnerDefaults.NullLogger; if (device.Type == AdbDeviceType.Emulator && device.AvdName is { Length: > 0 } avdName) { - logger?.Invoke (TraceLevel.Verbose, $"Emulator {device.Serial}, original AVD name: {avdName}"); + logger.Invoke (TraceLevel.Verbose, $"Emulator {device.Serial}, original AVD name: {avdName}"); var formatted = FormatDisplayName (avdName); - logger?.Invoke (TraceLevel.Verbose, $"Emulator {device.Serial}, formatted AVD display name: {formatted}"); + logger.Invoke (TraceLevel.Verbose, $"Emulator {device.Serial}, formatted AVD display name: {formatted}"); return formatted; } @@ -264,6 +369,7 @@ public static string FormatDisplayName (string avdName) /// public static IReadOnlyList MergeDevicesAndEmulators (IReadOnlyList adbDevices, IReadOnlyList availableEmulators, Action? logger = null) { + logger ??= RunnerDefaults.NullLogger; var result = new List (adbDevices); // Build a set of AVD names that are already running @@ -273,12 +379,12 @@ public static IReadOnlyList MergeDevicesAndEmulators (IReadOnlyLi runningAvdNames.Add (avdName); } - logger?.Invoke (TraceLevel.Verbose, $"Running emulators AVD names: {string.Join (", ", runningAvdNames)}"); + logger.Invoke (TraceLevel.Verbose, $"Running emulators AVD names: {string.Join (", ", runningAvdNames)}"); // Add non-running emulators foreach (var avdName in availableEmulators) { if (runningAvdNames.Contains (avdName)) { - logger?.Invoke (TraceLevel.Verbose, $"Emulator '{avdName}' is already running, skipping"); + logger.Invoke (TraceLevel.Verbose, $"Emulator '{avdName}' is already running, skipping"); continue; } @@ -290,7 +396,7 @@ public static IReadOnlyList MergeDevicesAndEmulators (IReadOnlyLi Status = AdbDeviceStatus.NotRunning, AvdName = avdName, }); - logger?.Invoke (TraceLevel.Verbose, $"Added non-running emulator: {avdName}"); + logger.Invoke (TraceLevel.Verbose, $"Added non-running emulator: {avdName}"); } // Sort: online devices first, then not-running emulators, alphabetically by description diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Runners/AvdManagerRunner.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Runners/AvdManagerRunner.cs index d678991b262..b233ae38b28 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Runners/AvdManagerRunner.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Runners/AvdManagerRunner.cs @@ -18,7 +18,7 @@ public class AvdManagerRunner { readonly string avdManagerPath; readonly IDictionary? environmentVariables; - readonly Action? logger; + readonly Action logger; /// /// Creates a new AvdManagerRunner with the full path to the avdmanager executable. @@ -32,7 +32,7 @@ public AvdManagerRunner (string avdManagerPath, IDictionary? env throw new ArgumentException ("Path to avdmanager must not be empty.", nameof (avdManagerPath)); this.avdManagerPath = avdManagerPath; this.environmentVariables = environmentVariables; - this.logger = logger; + this.logger = logger ?? RunnerDefaults.NullLogger; } public async Task> ListAvdsAsync (CancellationToken cancellationToken = default) @@ -40,7 +40,7 @@ public async Task> ListAvdsAsync (CancellationToken cance using var stdout = new StringWriter (); using var stderr = new StringWriter (); var psi = ProcessUtils.CreateProcessStartInfo (avdManagerPath, "list", "avd"); - logger?.Invoke (TraceLevel.Verbose, "Running: avdmanager list avd"); + logger.Invoke (TraceLevel.Verbose, "Running: avdmanager list avd"); var exitCode = await ProcessUtils.StartProcess (psi, stdout, stderr, cancellationToken, environmentVariables).ConfigureAwait (false); ProcessUtils.ThrowIfFailed (exitCode, "avdmanager list avd", stderr); @@ -65,7 +65,7 @@ public async Task GetOrCreateAvdAsync (string name, string systemImage, var existing = (await ListAvdsAsync (cancellationToken).ConfigureAwait (false)) .FirstOrDefault (a => string.Equals (a.Name, name, StringComparison.OrdinalIgnoreCase)); if (existing is not null) { - logger?.Invoke (TraceLevel.Verbose, $"AVD '{name}' already exists, returning existing"); + logger.Invoke (TraceLevel.Verbose, $"AVD '{name}' already exists, returning existing"); return existing; } } @@ -88,7 +88,7 @@ public async Task GetOrCreateAvdAsync (string name, string systemImage, p.StandardInput.WriteLine ("no"); p.StandardInput.Close (); } catch (IOException ex) { - logger?.Invoke (TraceLevel.Warning, $"Failed to write to avdmanager stdin: {ex.Message}"); + logger.Invoke (TraceLevel.Warning, $"Failed to write to avdmanager stdin: {ex.Message}"); } }).ConfigureAwait (false); @@ -111,7 +111,7 @@ public async Task DeleteAvdAsync (string name, CancellationToken cancellationTok // Idempotent: if the AVD doesn't exist, treat as success var avds = await ListAvdsAsync (cancellationToken).ConfigureAwait (false); if (!avds.Any (a => string.Equals (a.Name, name, StringComparison.OrdinalIgnoreCase))) { - logger?.Invoke (TraceLevel.Verbose, $"AVD '{name}' does not exist, nothing to delete"); + logger.Invoke (TraceLevel.Verbose, $"AVD '{name}' does not exist, nothing to delete"); return; } diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Runners/EmulatorRunner.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Runners/EmulatorRunner.cs new file mode 100644 index 00000000000..4a4bd6e8baf --- /dev/null +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Runners/EmulatorRunner.cs @@ -0,0 +1,312 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Xamarin.Android.Tools; + +/// +/// Runs Android Emulator commands. +/// +public class EmulatorRunner +{ + readonly string emulatorPath; + readonly IDictionary? environmentVariables; + readonly Action logger; + + /// + /// Creates a new EmulatorRunner with the full path to the emulator executable. + /// + /// Full path to the emulator executable (e.g., "/path/to/sdk/emulator/emulator"). + /// Optional environment variables to pass to emulator processes. + /// Optional logger callback for diagnostic messages. + public EmulatorRunner (string emulatorPath, IDictionary? environmentVariables = null, Action? logger = null) + { + if (string.IsNullOrWhiteSpace (emulatorPath)) + throw new ArgumentException ("Path to emulator must not be empty.", nameof (emulatorPath)); + this.emulatorPath = emulatorPath; + this.environmentVariables = environmentVariables; + this.logger = logger ?? RunnerDefaults.NullLogger; + } + + /// + /// Launches an emulator process for the specified AVD and returns immediately. + /// The returned represents the running emulator — the caller + /// is responsible for managing its lifetime (e.g., killing it on shutdown). + /// This method does not wait for the emulator to finish booting. + /// To launch and wait until the device is fully booted, use instead. + /// + /// Name of the AVD to launch (as shown by emulator -list-avds). + /// When true, forces a cold boot by passing -no-snapshot-load. + /// Optional extra arguments to pass to the emulator command line. + /// The running the emulator. Stdout/stderr are redirected and forwarded to the logger. + public Process LaunchEmulator (string avdName, bool coldBoot = false, List? additionalArgs = null) + { + if (string.IsNullOrWhiteSpace (avdName)) + throw new ArgumentException ("AVD name must not be empty.", nameof (avdName)); + + var args = new List { "-avd", avdName }; + if (coldBoot) + args.Add ("-no-snapshot-load"); + if (additionalArgs != null) + args.AddRange (additionalArgs); + + var psi = ProcessUtils.CreateProcessStartInfo (emulatorPath, args.ToArray ()); + + if (environmentVariables != null) { + foreach (var kvp in environmentVariables) + psi.EnvironmentVariables[kvp.Key] = kvp.Value; + } + + // Redirect stdout/stderr so the emulator process doesn't inherit the + // caller's pipes. Without this, parent processes (e.g. VS Code spawn) + // never see the 'close' event because the emulator holds the pipes open. + psi.RedirectStandardOutput = true; + psi.RedirectStandardError = true; + + logger.Invoke (TraceLevel.Verbose, $"Launching emulator AVD '{avdName}'"); + + var process = new Process { StartInfo = psi }; + + // Forward emulator output to the logger so crash messages (e.g. "HAX is + // not working", "image not found") are captured instead of silently lost. + process.OutputDataReceived += (_, e) => { + if (e.Data != null) + logger.Invoke (TraceLevel.Verbose, $"[emulator] {e.Data}"); + }; + process.ErrorDataReceived += (_, e) => { + if (e.Data != null) + logger.Invoke (TraceLevel.Warning, $"[emulator] {e.Data}"); + }; + + process.Start (); + + // Drain redirected streams asynchronously to prevent pipe buffer deadlocks + process.BeginOutputReadLine (); + process.BeginErrorReadLine (); + + return process; + } + + public async Task> ListAvdNamesAsync (CancellationToken cancellationToken = default) + { + using var stdout = new StringWriter (); + using var stderr = new StringWriter (); + var psi = ProcessUtils.CreateProcessStartInfo (emulatorPath, "-list-avds"); + + logger.Invoke (TraceLevel.Verbose, "Running: emulator -list-avds"); + var exitCode = await ProcessUtils.StartProcess (psi, stdout, stderr, cancellationToken, environmentVariables).ConfigureAwait (false); + ProcessUtils.ThrowIfFailed (exitCode, "emulator -list-avds", stderr); + + return ParseListAvdsOutput (stdout.ToString ()); + } + + internal static List ParseListAvdsOutput (string output) + { + var avds = new List (); + foreach (var line in output.Split ('\n')) { + var trimmed = line.Trim (); + if (!string.IsNullOrEmpty (trimmed)) + avds.Add (trimmed); + } + return avds; + } + + /// + /// Boots an emulator for the specified AVD and waits until it is fully ready to accept commands. + /// + /// Unlike , which only spawns the emulator process, this method + /// handles the full lifecycle: it checks whether the device is already online, launches + /// the emulator if needed, then polls sys.boot_completed and pm path android + /// until the Android OS is fully booted and the package manager is responsive. + /// + /// Ported from the dotnet/android BootAndroidEmulator MSBuild task. + /// + /// + /// Either an ADB device serial (e.g., emulator-5554) to wait for, + /// or an AVD name (e.g., Pixel_7_API_35) to launch and boot. + /// + /// An used to query device status and boot properties. + /// Optional boot configuration (timeout, poll interval, cold boot, extra args). + /// Cancellation token to abort the operation. + /// + /// An indicating success or failure, including the device serial on success + /// or an error message on timeout/failure. + /// + public async Task BootEmulatorAsync ( + string deviceOrAvdName, + AdbRunner adbRunner, + EmulatorBootOptions? options = null, + CancellationToken cancellationToken = default) + { + if (string.IsNullOrWhiteSpace (deviceOrAvdName)) + throw new ArgumentException ("Device or AVD name must not be empty.", nameof (deviceOrAvdName)); + if (adbRunner == null) + throw new ArgumentNullException (nameof (adbRunner)); + + options ??= new EmulatorBootOptions (); + if (options.BootTimeout <= TimeSpan.Zero) + throw new ArgumentOutOfRangeException (nameof (options), "BootTimeout must be positive."); + if (options.PollInterval <= TimeSpan.Zero) + throw new ArgumentOutOfRangeException (nameof (options), "PollInterval must be positive."); + + logger.Invoke (TraceLevel.Info, $"Booting emulator for '{deviceOrAvdName}'..."); + + // Phase 1: Check if deviceOrAvdName is already an online ADB device by serial + var devices = await adbRunner.ListDevicesAsync (cancellationToken).ConfigureAwait (false); + var onlineDevice = devices.FirstOrDefault (d => + d.Status == AdbDeviceStatus.Online && + string.Equals (d.Serial, deviceOrAvdName, StringComparison.OrdinalIgnoreCase)); + + if (onlineDevice != null) { + logger.Invoke (TraceLevel.Info, $"Device '{deviceOrAvdName}' is already online."); + return new EmulatorBootResult { Success = true, Serial = onlineDevice.Serial, ErrorKind = EmulatorBootErrorKind.None }; + } + + // Single timeout CTS for the entire boot operation (covers Phase 2 and Phase 3). + using var timeoutCts = CancellationTokenSource.CreateLinkedTokenSource (cancellationToken); + timeoutCts.CancelAfter (options.BootTimeout); + + // Phase 2: Check if AVD is already running (possibly still booting) + var runningSerial = FindRunningAvdSerial (devices, deviceOrAvdName); + if (runningSerial != null) { + logger.Invoke (TraceLevel.Info, $"AVD '{deviceOrAvdName}' is already running as '{runningSerial}', waiting for full boot..."); + try { + return await WaitForFullBootAsync (adbRunner, runningSerial, options, timeoutCts.Token).ConfigureAwait (false); + } catch (OperationCanceledException) when (!cancellationToken.IsCancellationRequested) { + return new EmulatorBootResult { + Success = false, + ErrorKind = EmulatorBootErrorKind.Timeout, + ErrorMessage = $"Timed out waiting for emulator '{deviceOrAvdName}' to boot within {options.BootTimeout.TotalSeconds}s.", + }; + } + } + + // Phase 3: Launch the emulator + logger.Invoke (TraceLevel.Info, $"Launching AVD '{deviceOrAvdName}'..."); + Process emulatorProcess; + try { + emulatorProcess = LaunchEmulator (deviceOrAvdName, options.ColdBoot, options.AdditionalArgs); + } catch (Exception ex) { + return new EmulatorBootResult { + Success = false, + ErrorKind = EmulatorBootErrorKind.LaunchFailed, + ErrorMessage = $"Failed to launch emulator: {ex.Message}", + }; + } + + // Poll for the new emulator serial to appear. + // If the boot times out or is cancelled, terminate the process we spawned + // to avoid leaving orphan emulator processes. + // + // On macOS, the emulator binary may fork the real QEMU process and exit with + // code 0 immediately. The real emulator continues as a separate process and + // will eventually appear in 'adb devices'. We only treat non-zero exit codes + // as immediate failures; exit code 0 means we continue polling. + try { + string? newSerial = null; + bool processExitedWithZero = false; + while (newSerial == null) { + timeoutCts.Token.ThrowIfCancellationRequested (); + + // Detect early process exit for fast failure + if (emulatorProcess.HasExited && !processExitedWithZero) { + if (emulatorProcess.ExitCode != 0) { + emulatorProcess.Dispose (); + return new EmulatorBootResult { + Success = false, + ErrorKind = EmulatorBootErrorKind.LaunchFailed, + ErrorMessage = $"Emulator process for '{deviceOrAvdName}' exited with code {emulatorProcess.ExitCode} before becoming available.", + }; + } + // Exit code 0: emulator likely forked (common on macOS). + // The real emulator runs as a separate process — keep polling. + logger.Invoke (TraceLevel.Verbose, $"Emulator launcher process exited with code 0 (likely forked). Continuing to poll adb devices."); + processExitedWithZero = true; + } + + await Task.Delay (options.PollInterval, timeoutCts.Token).ConfigureAwait (false); + + devices = await adbRunner.ListDevicesAsync (timeoutCts.Token).ConfigureAwait (false); + newSerial = FindRunningAvdSerial (devices, deviceOrAvdName); + } + + logger.Invoke (TraceLevel.Info, $"Emulator appeared as '{newSerial}', waiting for full boot..."); + var result = await WaitForFullBootAsync (adbRunner, newSerial, options, timeoutCts.Token).ConfigureAwait (false); + + // Release the Process handle — the emulator process itself keeps running. + // We no longer need stdout/stderr forwarding since boot is complete. + emulatorProcess.Dispose (); + return result; + } catch (OperationCanceledException) when (!cancellationToken.IsCancellationRequested) { + TryKillProcess (emulatorProcess); + return new EmulatorBootResult { + Success = false, + ErrorKind = EmulatorBootErrorKind.Timeout, + ErrorMessage = $"Timed out waiting for emulator '{deviceOrAvdName}' to boot within {options.BootTimeout.TotalSeconds}s.", + }; + } catch { + TryKillProcess (emulatorProcess); + throw; + } + } + + static string? FindRunningAvdSerial (IReadOnlyList devices, string avdName) + { + foreach (var d in devices) { + if (d.Type == AdbDeviceType.Emulator && + !string.IsNullOrEmpty (d.AvdName) && + string.Equals (d.AvdName, avdName, StringComparison.OrdinalIgnoreCase)) { + return d.Serial; + } + } + return null; + } + + void TryKillProcess (Process process) + { + try { + process.Kill (); + } catch (Exception ex) { + // Best-effort: process may have already exited + logger.Invoke (TraceLevel.Verbose, $"Failed to stop emulator process: {ex.Message}"); + } finally { + process.Dispose (); + } + } + + async Task WaitForFullBootAsync ( + AdbRunner adbRunner, + string serial, + EmulatorBootOptions options, + CancellationToken cancellationToken) + { + // The caller is responsible for enforcing the overall boot timeout via + // cancellationToken (a linked CTS with CancelAfter). This method simply + // polls until boot completes or the token is cancelled. + while (!cancellationToken.IsCancellationRequested) { + cancellationToken.ThrowIfCancellationRequested (); + + var bootCompleted = await adbRunner.GetShellPropertyAsync (serial, "sys.boot_completed", cancellationToken).ConfigureAwait (false); + if (string.Equals (bootCompleted, "1", StringComparison.Ordinal)) { + var pmResult = await adbRunner.RunShellCommandAsync (serial, "pm path android", cancellationToken).ConfigureAwait (false); + if (pmResult != null && pmResult.StartsWith ("package:", StringComparison.Ordinal)) { + logger.Invoke (TraceLevel.Info, $"Emulator '{serial}' is fully booted."); + return new EmulatorBootResult { Success = true, Serial = serial, ErrorKind = EmulatorBootErrorKind.None }; + } + } + + await Task.Delay (options.PollInterval, cancellationToken).ConfigureAwait (false); + } + + cancellationToken.ThrowIfCancellationRequested (); + return new EmulatorBootResult { Success = false, ErrorKind = EmulatorBootErrorKind.Cancelled, ErrorMessage = "Boot cancelled." }; + } +} + diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Runners/RunnerDefaults.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Runners/RunnerDefaults.cs new file mode 100644 index 00000000000..e71b2df1b09 --- /dev/null +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Runners/RunnerDefaults.cs @@ -0,0 +1,19 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Diagnostics; + +namespace Xamarin.Android.Tools; + +/// +/// Shared defaults for Android SDK tool runners. +/// +static class RunnerDefaults +{ + /// + /// A no-op logger that discards all messages. Used as the default + /// when callers don't provide a logger callback. + /// + internal static readonly Action NullLogger = static (_, _) => { }; +} diff --git a/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/AdbRunnerTests.cs b/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/AdbRunnerTests.cs index 1e9a232afd7..c9536cf93d3 100644 --- a/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/AdbRunnerTests.cs +++ b/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/AdbRunnerTests.cs @@ -655,4 +655,63 @@ public void WaitForDeviceAsync_ZeroTimeout_ThrowsArgumentOutOfRange () Assert.ThrowsAsync ( async () => await runner.WaitForDeviceAsync (timeout: System.TimeSpan.Zero)); } + + // --- FirstNonEmptyLine tests --- + + [Test] + public void FirstNonEmptyLine_ReturnsFirstLine () + { + Assert.AreEqual ("hello", AdbRunner.FirstNonEmptyLine ("hello\nworld\n")); + } + + [Test] + public void FirstNonEmptyLine_SkipsBlankLines () + { + Assert.AreEqual ("hello", AdbRunner.FirstNonEmptyLine ("\n\nhello\nworld\n")); + } + + [Test] + public void FirstNonEmptyLine_TrimsWhitespace () + { + Assert.AreEqual ("hello", AdbRunner.FirstNonEmptyLine (" hello \n")); + } + + [Test] + public void FirstNonEmptyLine_HandlesWindowsNewlines () + { + Assert.AreEqual ("hello", AdbRunner.FirstNonEmptyLine ("\r\nhello\r\nworld\r\n")); + } + + [Test] + public void FirstNonEmptyLine_EmptyString_ReturnsNull () + { + Assert.IsNull (AdbRunner.FirstNonEmptyLine ("")); + } + + [Test] + public void FirstNonEmptyLine_OnlyNewlines_ReturnsNull () + { + Assert.IsNull (AdbRunner.FirstNonEmptyLine ("\n\n\n")); + } + + [Test] + public void FirstNonEmptyLine_SingleValue_ReturnsIt () + { + Assert.AreEqual ("1", AdbRunner.FirstNonEmptyLine ("1\n")); + } + + [Test] + public void FirstNonEmptyLine_TypicalGetpropOutput () + { + // adb shell getprop sys.boot_completed returns "1\n" or "1\r\n" + Assert.AreEqual ("1", AdbRunner.FirstNonEmptyLine ("1\r\n")); + } + + [Test] + public void FirstNonEmptyLine_PmPathOutput () + { + // adb shell pm path android returns "package:/system/framework/framework-res.apk\n" + var output = "package:/system/framework/framework-res.apk\n"; + Assert.AreEqual ("package:/system/framework/framework-res.apk", AdbRunner.FirstNonEmptyLine (output)); + } } diff --git a/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/EmulatorRunnerTests.cs b/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/EmulatorRunnerTests.cs new file mode 100644 index 00000000000..39fdca5b79c --- /dev/null +++ b/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/EmulatorRunnerTests.cs @@ -0,0 +1,555 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#nullable enable + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using NUnit.Framework; + +namespace Xamarin.Android.Tools.Tests; + +[TestFixture] +public class EmulatorRunnerTests +{ + [Test] + public void ParseListAvdsOutput_MultipleAvds () + { + var output = "Pixel_7_API_35\nMAUI_Emulator\nNexus_5X\n"; + + var avds = EmulatorRunner.ParseListAvdsOutput (output); + + Assert.AreEqual (3, avds.Count); + Assert.AreEqual ("Pixel_7_API_35", avds [0]); + Assert.AreEqual ("MAUI_Emulator", avds [1]); + Assert.AreEqual ("Nexus_5X", avds [2]); + } + + [Test] + public void ParseListAvdsOutput_EmptyOutput () + { + var avds = EmulatorRunner.ParseListAvdsOutput (""); + Assert.AreEqual (0, avds.Count); + } + + [Test] + public void ParseListAvdsOutput_WindowsNewlines () + { + var output = "Pixel_7_API_35\r\nMAUI_Emulator\r\n"; + + var avds = EmulatorRunner.ParseListAvdsOutput (output); + + Assert.AreEqual (2, avds.Count); + Assert.AreEqual ("Pixel_7_API_35", avds [0]); + Assert.AreEqual ("MAUI_Emulator", avds [1]); + } + + [Test] + public void ParseListAvdsOutput_BlankLines () + { + var output = "\nPixel_7_API_35\n\n\nMAUI_Emulator\n\n"; + + var avds = EmulatorRunner.ParseListAvdsOutput (output); + + Assert.AreEqual (2, avds.Count); + } + + [Test] + public void Constructor_ThrowsOnNullPath () + { + Assert.Throws (() => new EmulatorRunner (null!)); + } + + [Test] + public void Constructor_ThrowsOnEmptyPath () + { + Assert.Throws (() => new EmulatorRunner ("")); + } + + [Test] + public void Constructor_ThrowsOnWhitespacePath () + { + Assert.Throws (() => new EmulatorRunner (" ")); + } + + [Test] + public void LaunchEmulator_ThrowsOnNullAvdName () + { + var runner = new EmulatorRunner ("/fake/emulator"); + Assert.Throws (() => runner.LaunchEmulator (null!)); + } + + [Test] + public void LaunchEmulator_ThrowsOnEmptyAvdName () + { + var runner = new EmulatorRunner ("/fake/emulator"); + Assert.Throws (() => runner.LaunchEmulator ("")); + } + + [Test] + public void LaunchEmulator_ThrowsOnWhitespaceAvdName () + { + var runner = new EmulatorRunner ("/fake/emulator"); + Assert.Throws (() => runner.LaunchEmulator (" ")); + } + + // --- BootEmulatorAsync tests (ported from dotnet/android BootAndroidEmulatorTests) --- + + [Test] + public async Task AlreadyOnlineDevice_PassesThrough () + { + var devices = new List { + new AdbDeviceInfo { + Serial = "emulator-5554", + Type = AdbDeviceType.Emulator, + Status = AdbDeviceStatus.Online, + AvdName = "Pixel_7_API_35", + }, + }; + + var mockAdb = new MockAdbRunner (devices); + var runner = new EmulatorRunner ("/fake/emulator"); + + var result = await runner.BootEmulatorAsync ("emulator-5554", mockAdb); + + Assert.IsTrue (result.Success); + Assert.AreEqual ("emulator-5554", result.Serial); + Assert.IsNull (result.ErrorMessage); + } + + [Test] + public async Task AvdAlreadyRunning_WaitsForFullBoot () + { + var devices = new List { + new AdbDeviceInfo { + Serial = "emulator-5554", + Type = AdbDeviceType.Emulator, + Status = AdbDeviceStatus.Online, + AvdName = "Pixel_7_API_35", + }, + }; + + var mockAdb = new MockAdbRunner (devices); + mockAdb.ShellProperties ["sys.boot_completed"] = "1"; + mockAdb.ShellCommands ["pm path android"] = "package:/system/framework/framework-res.apk"; + + var runner = new EmulatorRunner ("/fake/emulator"); + var options = new EmulatorBootOptions { BootTimeout = TimeSpan.FromSeconds (5), PollInterval = TimeSpan.FromMilliseconds (50) }; + + var result = await runner.BootEmulatorAsync ("Pixel_7_API_35", mockAdb, options); + + Assert.IsTrue (result.Success); + Assert.AreEqual ("emulator-5554", result.Serial); + } + + [Test] + public async Task BootEmulator_AppearsAfterPolling () + { + var devices = new List (); + var mockAdb = new MockAdbRunner (devices); + mockAdb.ShellProperties ["sys.boot_completed"] = "1"; + mockAdb.ShellCommands ["pm path android"] = "package:/system/framework/framework-res.apk"; + + int pollCount = 0; + mockAdb.OnListDevices = () => { + pollCount++; + if (pollCount >= 2) { + devices.Add (new AdbDeviceInfo { + Serial = "emulator-5554", + Type = AdbDeviceType.Emulator, + Status = AdbDeviceStatus.Online, + AvdName = "Pixel_7_API_35", + }); + } + }; + + var (tempDir, emuPath) = CreateFakeEmulatorSdk (); + Process? emulatorProcess = null; + try { + var runner = new EmulatorRunner (emuPath); + var options = new EmulatorBootOptions { + BootTimeout = TimeSpan.FromSeconds (10), + PollInterval = TimeSpan.FromMilliseconds (50), + }; + + var result = await runner.BootEmulatorAsync ("Pixel_7_API_35", mockAdb, options); + + Assert.IsTrue (result.Success); + Assert.AreEqual ("emulator-5554", result.Serial); + Assert.IsTrue (pollCount >= 2); + } finally { + // Kill any emulator process spawned by the test + try { + emulatorProcess = FindEmulatorProcess (emuPath); + emulatorProcess?.Kill (); + emulatorProcess?.WaitForExit (1000); + } catch { } + Directory.Delete (tempDir, true); + } + } + + [Test] + public async Task LaunchFailure_ReturnsError () + { + var devices = new List (); + var mockAdb = new MockAdbRunner (devices); + + // Nonexistent path → LaunchAvd throws → error result + var runner = new EmulatorRunner ("/nonexistent/emulator"); + var options = new EmulatorBootOptions { BootTimeout = TimeSpan.FromSeconds (2) }; + + var result = await runner.BootEmulatorAsync ("Pixel_7_API_35", mockAdb, options); + + Assert.IsFalse (result.Success); + Assert.That (result.ErrorMessage, Does.Contain ("Failed to launch")); + } + + [Test] + public async Task BootTimeout_BootCompletedNeverReaches1 () + { + var devices = new List { + new AdbDeviceInfo { + Serial = "emulator-5554", + Type = AdbDeviceType.Emulator, + Status = AdbDeviceStatus.Online, + AvdName = "Pixel_7_API_35", + }, + }; + + var mockAdb = new MockAdbRunner (devices); + // boot_completed never returns "1" + mockAdb.ShellProperties ["sys.boot_completed"] = "0"; + + var runner = new EmulatorRunner ("/fake/emulator"); + var options = new EmulatorBootOptions { + BootTimeout = TimeSpan.FromMilliseconds (200), + PollInterval = TimeSpan.FromMilliseconds (50), + }; + + var result = await runner.BootEmulatorAsync ("Pixel_7_API_35", mockAdb, options); + + Assert.IsFalse (result.Success); + Assert.That (result.ErrorMessage, Does.Contain ("Timed out")); + } + + [Test] + public void BootEmulatorAsync_InvalidBootTimeout_Throws () + { + var runner = new EmulatorRunner ("/fake/emulator"); + var mockAdb = new MockAdbRunner (new List ()); + var options = new EmulatorBootOptions { BootTimeout = TimeSpan.Zero }; + + Assert.ThrowsAsync (() => + runner.BootEmulatorAsync ("test", mockAdb, options)); + } + + [Test] + public void BootEmulatorAsync_InvalidPollInterval_Throws () + { + var runner = new EmulatorRunner ("/fake/emulator"); + var mockAdb = new MockAdbRunner (new List ()); + var options = new EmulatorBootOptions { PollInterval = TimeSpan.FromMilliseconds (-1) }; + + Assert.ThrowsAsync (() => + runner.BootEmulatorAsync ("test", mockAdb, options)); + } + + [Test] + public async Task MultipleEmulators_FindsCorrectAvd () + { + var devices = new List { + new AdbDeviceInfo { + Serial = "emulator-5554", + Type = AdbDeviceType.Emulator, + Status = AdbDeviceStatus.Online, + AvdName = "Pixel_5_API_30", + }, + new AdbDeviceInfo { + Serial = "emulator-5556", + Type = AdbDeviceType.Emulator, + Status = AdbDeviceStatus.Online, + AvdName = "Pixel_7_API_35", + }, + new AdbDeviceInfo { + Serial = "emulator-5558", + Type = AdbDeviceType.Emulator, + Status = AdbDeviceStatus.Online, + AvdName = "Nexus_5X_API_28", + }, + }; + + var mockAdb = new MockAdbRunner (devices); + mockAdb.ShellProperties ["sys.boot_completed"] = "1"; + mockAdb.ShellCommands ["pm path android"] = "package:/system/framework/framework-res.apk"; + + var runner = new EmulatorRunner ("/fake/emulator"); + var options = new EmulatorBootOptions { BootTimeout = TimeSpan.FromSeconds (5), PollInterval = TimeSpan.FromMilliseconds (50) }; + + var result = await runner.BootEmulatorAsync ("Pixel_7_API_35", mockAdb, options); + + Assert.IsTrue (result.Success); + Assert.AreEqual ("emulator-5556", result.Serial, "Should find the correct AVD among multiple emulators"); + } + + // --- Tests ported from dotnet/android BootAndroidEmulatorTests --- + + [Test] + public async Task AlreadyOnlinePhysicalDevice_PassesThrough () + { + // Physical devices have non-emulator serials (e.g., USB serial numbers). + // BootEmulatorAsync should recognise them as already-online devices and + // return immediately without attempting to launch an emulator. + var devices = new List { + new AdbDeviceInfo { + Serial = "0A041FDD400327", + Type = AdbDeviceType.Device, + Status = AdbDeviceStatus.Online, + }, + }; + + var mockAdb = new MockAdbRunner (devices); + var runner = new EmulatorRunner ("/fake/emulator"); + + var result = await runner.BootEmulatorAsync ("0A041FDD400327", mockAdb); + + Assert.IsTrue (result.Success); + Assert.AreEqual ("0A041FDD400327", result.Serial); + Assert.IsNull (result.ErrorMessage); + } + + [Test] + public async Task AdditionalArgs_PassedToLaunchEmulator () + { + // Verify that AdditionalArgs from EmulatorBootOptions are forwarded + // to the emulator process. We use a fake emulator script that logs + // its arguments so we can inspect them after the boot times out. + var (tempDir, emuPath) = CreateFakeEmulatorSdk (); + var argsLogPath = Path.Combine (tempDir, "args.log"); + + // Rewrite the fake emulator to log its arguments + if (OS.IsWindows) { + File.WriteAllText (emuPath, $"@echo off\r\necho %* > \"{argsLogPath}\"\r\nping -n 60 127.0.0.1 >nul\r\n"); + } else { + File.WriteAllText (emuPath, $"#!/bin/sh\necho \"$@\" > \"{argsLogPath}\"\nsleep 60\n"); + } + + try { + var devices = new List (); + var mockAdb = new MockAdbRunner (devices); + + var runner = new EmulatorRunner (emuPath); + var options = new EmulatorBootOptions { + BootTimeout = TimeSpan.FromMilliseconds (500), + PollInterval = TimeSpan.FromMilliseconds (50), + AdditionalArgs = new List { "-gpu", "auto", "-no-audio" }, + }; + + // Boot will time out (no device appears), but the emulator process + // should have been launched with the additional args. + var result = await runner.BootEmulatorAsync ("Test_AVD", mockAdb, options); + + Assert.IsFalse (result.Success, "Boot should time out"); + + // Give the script a moment to flush args.log + await Task.Delay (200); + + if (File.Exists (argsLogPath)) { + var logged = File.ReadAllText (argsLogPath); + Assert.That (logged, Does.Contain ("-gpu"), "Should contain -gpu arg"); + Assert.That (logged, Does.Contain ("auto"), "Should contain auto value"); + Assert.That (logged, Does.Contain ("-no-audio"), "Should contain -no-audio arg"); + Assert.That (logged, Does.Contain ("-avd"), "Should contain -avd flag"); + Assert.That (logged, Does.Contain ("Test_AVD"), "Should contain AVD name"); + } + } finally { + // Clean up any spawned processes + try { + foreach (var p in Process.GetProcessesByName ("sleep")) { + try { p.Kill (); p.WaitForExit (1000); } catch { } + } + } catch { } + Directory.Delete (tempDir, true); + } + } + + [Test] + public async Task CancellationToken_AbortsBoot () + { + // Verify that cancelling the token during the polling phase causes + // BootEmulatorAsync to return promptly rather than blocking for the + // full BootTimeout duration. We need a real fake emulator script so + // LaunchEmulator succeeds (starts the process), then cancel while polling. + var (tempDir, emuPath) = CreateFakeEmulatorSdk (); + + try { + var devices = new List (); + var mockAdb = new MockAdbRunner (devices); + + var runner = new EmulatorRunner (emuPath); + var options = new EmulatorBootOptions { + BootTimeout = TimeSpan.FromSeconds (30), + PollInterval = TimeSpan.FromMilliseconds (50), + }; + + using var cts = new CancellationTokenSource (); + cts.CancelAfter (TimeSpan.FromMilliseconds (300)); + + var sw = Stopwatch.StartNew (); + try { + await runner.BootEmulatorAsync ("Nonexistent_AVD", mockAdb, options, cts.Token); + Assert.Fail ("Should have thrown OperationCanceledException"); + } catch (OperationCanceledException) { + sw.Stop (); + // Should abort well before the 30s BootTimeout + Assert.That (sw.Elapsed.TotalSeconds, Is.LessThan (5), + "Cancellation should abort within a few seconds, not wait for full timeout"); + } + } finally { + try { + foreach (var p in Process.GetProcessesByName ("sleep")) { + try { p.Kill (); p.WaitForExit (1000); } catch { } + } + } catch { } + Directory.Delete (tempDir, true); + } + } + + [Test] + public async Task ColdBoot_PassesNoSnapshotLoad () + { + // Verify that ColdBoot = true causes -no-snapshot-load to be passed. + var (tempDir, emuPath) = CreateFakeEmulatorSdk (); + var argsLogPath = Path.Combine (tempDir, "args.log"); + + if (OS.IsWindows) { + File.WriteAllText (emuPath, $"@echo off\r\necho %* > \"{argsLogPath}\"\r\nping -n 60 127.0.0.1 >nul\r\n"); + } else { + File.WriteAllText (emuPath, $"#!/bin/sh\necho \"$@\" > \"{argsLogPath}\"\nsleep 60\n"); + } + + try { + var devices = new List (); + var mockAdb = new MockAdbRunner (devices); + + var runner = new EmulatorRunner (emuPath); + var options = new EmulatorBootOptions { + BootTimeout = TimeSpan.FromMilliseconds (500), + PollInterval = TimeSpan.FromMilliseconds (50), + ColdBoot = true, + }; + + var result = await runner.BootEmulatorAsync ("Test_AVD", mockAdb, options); + + Assert.IsFalse (result.Success, "Boot should time out"); + await Task.Delay (200); + + if (File.Exists (argsLogPath)) { + var logged = File.ReadAllText (argsLogPath); + Assert.That (logged, Does.Contain ("-no-snapshot-load"), "ColdBoot should pass -no-snapshot-load"); + } + } finally { + try { + foreach (var p in Process.GetProcessesByName ("sleep")) { + try { p.Kill (); p.WaitForExit (1000); } catch { } + } + } catch { } + Directory.Delete (tempDir, true); + } + } + + [Test] + public void BootEmulatorAsync_NullAdbRunner_Throws () + { + var runner = new EmulatorRunner ("/fake/emulator"); + + Assert.ThrowsAsync (() => + runner.BootEmulatorAsync ("test", null!)); + } + + [Test] + public void BootEmulatorAsync_EmptyDeviceName_Throws () + { + var runner = new EmulatorRunner ("/fake/emulator"); + var mockAdb = new MockAdbRunner (new List ()); + + Assert.ThrowsAsync (() => + runner.BootEmulatorAsync ("", mockAdb)); + } + + // --- Helpers --- + + static (string tempDir, string emulatorPath) CreateFakeEmulatorSdk () + { + var tempDir = Path.Combine (Path.GetTempPath (), $"emu-boot-test-{Path.GetRandomFileName ()}"); + var emulatorDir = Path.Combine (tempDir, "emulator"); + Directory.CreateDirectory (emulatorDir); + + var emuName = OS.IsWindows ? "emulator.bat" : "emulator"; + var emuPath = Path.Combine (emulatorDir, emuName); + if (OS.IsWindows) { + File.WriteAllText (emuPath, "@echo off\r\nping -n 60 127.0.0.1 >nul\r\n"); + } else { + File.WriteAllText (emuPath, "#!/bin/sh\nsleep 60\n"); + var psi = ProcessUtils.CreateProcessStartInfo ("chmod", "+x", emuPath); + using var chmod = new Process { StartInfo = psi }; + chmod.Start (); + chmod.WaitForExit (); + } + + return (tempDir, emuPath); + } + + static Process? FindEmulatorProcess (string emuPath) + { + // Best-effort: find the process by matching the command line + try { + foreach (var p in Process.GetProcessesByName ("emulator")) { + return p; + } + foreach (var p in Process.GetProcessesByName ("sleep")) { + return p; + } + } catch { } + return null; + } + + /// + /// Mock AdbRunner for testing BootEmulatorAsync without real adb commands. + /// + class MockAdbRunner : AdbRunner + { + readonly List devices; + + public Dictionary ShellProperties { get; } = new Dictionary (StringComparer.OrdinalIgnoreCase); + public Dictionary ShellCommands { get; } = new Dictionary (StringComparer.OrdinalIgnoreCase); + public Action? OnListDevices { get; set; } + + public MockAdbRunner (List devices) + : base ("/fake/adb") + { + this.devices = devices; + } + + public override Task> ListDevicesAsync (CancellationToken cancellationToken = default) + { + OnListDevices?.Invoke (); + return Task.FromResult> (devices); + } + + public override Task GetShellPropertyAsync (string serial, string propertyName, CancellationToken cancellationToken = default) + { + ShellProperties.TryGetValue (propertyName, out var value); + return Task.FromResult (value); + } + + public override Task RunShellCommandAsync (string serial, string command, CancellationToken cancellationToken) + { + ShellCommands.TryGetValue (command, out var value); + return Task.FromResult (value); + } + } +} From 6b978c714788055759497938bfabe81f30974009 Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Thu, 19 Mar 2026 08:43:17 -0500 Subject: [PATCH 255/308] [build] Append job attempt number to artifact names on retry (#314) Fixes: `##[error]Artifact Artifacts - windows already exists for build 1341680` When a build job is retried, the PublishPipelineArtifact task fails with 'Artifact already exists' because the artifact name from the first attempt is still present. Fix by computing an '(Attempt N)' suffix when System.JobAttempt > 1, matching the pattern used in dotnet/android. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- external/xamarin-android-tools/azure-pipelines.yaml | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/external/xamarin-android-tools/azure-pipelines.yaml b/external/xamarin-android-tools/azure-pipelines.yaml index b2fd39123c4..e6beef609d1 100644 --- a/external/xamarin-android-tools/azure-pipelines.yaml +++ b/external/xamarin-android-tools/azure-pipelines.yaml @@ -65,15 +65,21 @@ jobs: arguments: -p:Version=$(xat.nuget.version) -p:PackageOutputPath=$(Build.ArtifactStagingDirectory) -bl:$(Build.ArtifactStagingDirectory)/pack.binlog condition: and(succeeded(), eq(variables['agent.os'], 'Windows_NT')) + - powershell: | + $suffix = if ($(System.JobAttempt) -gt 1) { " (Attempt $(System.JobAttempt))" } else { "" } + Write-Host "##vso[task.setvariable variable=ArtifactSuffix;]$suffix" + displayName: Set artifact suffix for retries + condition: always() + - task: PublishPipelineArtifact@1 displayName: Upload Build Output inputs: path: bin/Debug - artifactName: Output - $(System.JobName) + artifactName: Output - $(System.JobName)$(ArtifactSuffix) - task: PublishPipelineArtifact@1 displayName: Upload Artifacts inputs: path: $(Build.ArtifactStagingDirectory) - artifactName: Artifacts - $(System.JobName) + artifactName: Artifacts - $(System.JobName)$(ArtifactSuffix) condition: always() From 28e82ef3b3ba62a6774ef478a2aaf788829161c5 Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Thu, 19 Mar 2026 08:52:55 -0500 Subject: [PATCH 256/308] [copilot] Rename copilot-setup-steps job to required name (#316) The Copilot coding agent requires the job in copilot-setup-steps.yml to be named 'copilot-setup-steps'. The job was named 'validate', causing an error when Copilot tried to use the custom setup steps. See: https://gh.io/copilot/actions-setup-steps Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../.github/workflows/copilot-setup-steps.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/xamarin-android-tools/.github/workflows/copilot-setup-steps.yml b/external/xamarin-android-tools/.github/workflows/copilot-setup-steps.yml index c216dd5586a..a843671128d 100644 --- a/external/xamarin-android-tools/.github/workflows/copilot-setup-steps.yml +++ b/external/xamarin-android-tools/.github/workflows/copilot-setup-steps.yml @@ -10,7 +10,7 @@ permissions: pull-requests: write jobs: - validate: + copilot-setup-steps: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 From 48fa2769a99b75afc208d02f6edbde183dc1b762 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Thu, 19 Mar 2026 09:15:56 -0500 Subject: [PATCH 257/308] [build] Fix `RS0017` warnings and enable `$(TreatWarningsAsErrors)` for `Release` (#315) ## Summary Fixes RS0017 warnings caused by a stale `ProcessUtils.StartProcess` signature in the `PublicAPI.Shipped.txt` files, and enables `TreatWarningsAsErrors` for `Release` builds to prevent similar regressions going forward. ## Changes 1. **Restore old `StartProcess` overload** in `ProcessUtils.cs`: The old `StartProcess` signature (without `environmentVariables` parameter) was a shipped API that was inadvertently removed when `environmentVariables` was added to the method. Restored it as a backward-compatible overload that delegates to the new method. Suppressed RS0027 on the shim since the shipped API already declares the optional parameter. 2. **Add convenience `StartProcess` overload** in `ProcessUtils.cs`: Added a new overload that accepts `environmentVariables` without requiring `onStarted`, so callers don't need to pass a trailing `null`. This overload delegates to the full 6-parameter method. 3. **Update `PublicAPI.Unshipped.txt`** (both `netstandard2.0` and `net10.0`): Added entries for both the 6-parameter `StartProcess` overload (with `environmentVariables` and `onStarted`) and the 5-parameter convenience overload (with `environmentVariables` only). 4. **Enable `TreatWarningsAsErrors` for `Release` configuration** in `Directory.Build.props`: CI will now fail on any warnings in Release builds, preventing future API tracking issues from going unnoticed. Co-authored-by: Jonathan Peppers --- .../xamarin-android-tools/Directory.Build.props | 1 + .../ProcessUtils.cs | 16 +++++++++++++++- .../PublicAPI/net10.0/PublicAPI.Unshipped.txt | 3 ++- .../netstandard2.0/PublicAPI.Unshipped.txt | 3 ++- 4 files changed, 20 insertions(+), 3 deletions(-) diff --git a/external/xamarin-android-tools/Directory.Build.props b/external/xamarin-android-tools/Directory.Build.props index 2612a3976fc..f8082d48618 100644 --- a/external/xamarin-android-tools/Directory.Build.props +++ b/external/xamarin-android-tools/Directory.Build.props @@ -7,6 +7,7 @@ true 10.0 net$(DotNetTargetFrameworkVersion) + true StartProcess (ProcessStartInfo psi, TextWriter? stdout, TextWriter? stderr, CancellationToken cancellationToken, IDictionary? environmentVariables = null, Action? onStarted = null) + /// Backward-compatible overload matching the original shipped API (without environmentVariables). +#pragma warning disable RS0027 // Public API with optional parameter(s) should have the most parameters amongst its public overloads + public static Task StartProcess (ProcessStartInfo psi, TextWriter? stdout, TextWriter? stderr, CancellationToken cancellationToken, Action? onStarted = null) +#pragma warning restore RS0027 + { + return StartProcess (psi, stdout, stderr, cancellationToken, null, onStarted); + } + + /// Convenience overload accepting environmentVariables without requiring onStarted. + public static Task StartProcess (ProcessStartInfo psi, TextWriter? stdout, TextWriter? stderr, CancellationToken cancellationToken, IDictionary? environmentVariables) + { + return StartProcess (psi, stdout, stderr, cancellationToken, environmentVariables, null); + } + + public static async Task StartProcess (ProcessStartInfo psi, TextWriter? stdout, TextWriter? stderr, CancellationToken cancellationToken, IDictionary? environmentVariables, Action? onStarted) { cancellationToken.ThrowIfCancellationRequested (); psi.UseShellExecute = false; diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/PublicAPI/net10.0/PublicAPI.Unshipped.txt b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/PublicAPI/net10.0/PublicAPI.Unshipped.txt index 9d51f08c9ec..e8f26266966 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/PublicAPI/net10.0/PublicAPI.Unshipped.txt +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/PublicAPI/net10.0/PublicAPI.Unshipped.txt @@ -130,7 +130,8 @@ static Xamarin.Android.Tools.JdkInstaller.RecommendedMajorVersion.get -> int static Xamarin.Android.Tools.JdkInstaller.SupportedVersions.get -> System.Collections.Generic.IReadOnlyList! static Xamarin.Android.Tools.ProcessUtils.CreateProcessStartInfo(string! fileName, params string![]! args) -> System.Diagnostics.ProcessStartInfo! static Xamarin.Android.Tools.ProcessUtils.IsElevated() -> bool -static Xamarin.Android.Tools.ProcessUtils.StartProcess(System.Diagnostics.ProcessStartInfo! psi, System.IO.TextWriter? stdout, System.IO.TextWriter? stderr, System.Threading.CancellationToken cancellationToken, System.Collections.Generic.IDictionary? environmentVariables = null, System.Action? onStarted = null) -> System.Threading.Tasks.Task! +static Xamarin.Android.Tools.ProcessUtils.StartProcess(System.Diagnostics.ProcessStartInfo! psi, System.IO.TextWriter? stdout, System.IO.TextWriter? stderr, System.Threading.CancellationToken cancellationToken, System.Collections.Generic.IDictionary? environmentVariables, System.Action? onStarted) -> System.Threading.Tasks.Task! +static Xamarin.Android.Tools.ProcessUtils.StartProcess(System.Diagnostics.ProcessStartInfo! psi, System.IO.TextWriter? stdout, System.IO.TextWriter? stderr, System.Threading.CancellationToken cancellationToken, System.Collections.Generic.IDictionary? environmentVariables) -> System.Threading.Tasks.Task! Xamarin.Android.Tools.AvdInfo Xamarin.Android.Tools.AvdInfo.AvdInfo(string! Name, string? DeviceProfile = null, string? Path = null) -> void Xamarin.Android.Tools.AvdInfo.DeviceProfile.get -> string? diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt index 9d51f08c9ec..e8f26266966 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt @@ -130,7 +130,8 @@ static Xamarin.Android.Tools.JdkInstaller.RecommendedMajorVersion.get -> int static Xamarin.Android.Tools.JdkInstaller.SupportedVersions.get -> System.Collections.Generic.IReadOnlyList! static Xamarin.Android.Tools.ProcessUtils.CreateProcessStartInfo(string! fileName, params string![]! args) -> System.Diagnostics.ProcessStartInfo! static Xamarin.Android.Tools.ProcessUtils.IsElevated() -> bool -static Xamarin.Android.Tools.ProcessUtils.StartProcess(System.Diagnostics.ProcessStartInfo! psi, System.IO.TextWriter? stdout, System.IO.TextWriter? stderr, System.Threading.CancellationToken cancellationToken, System.Collections.Generic.IDictionary? environmentVariables = null, System.Action? onStarted = null) -> System.Threading.Tasks.Task! +static Xamarin.Android.Tools.ProcessUtils.StartProcess(System.Diagnostics.ProcessStartInfo! psi, System.IO.TextWriter? stdout, System.IO.TextWriter? stderr, System.Threading.CancellationToken cancellationToken, System.Collections.Generic.IDictionary? environmentVariables, System.Action? onStarted) -> System.Threading.Tasks.Task! +static Xamarin.Android.Tools.ProcessUtils.StartProcess(System.Diagnostics.ProcessStartInfo! psi, System.IO.TextWriter? stdout, System.IO.TextWriter? stderr, System.Threading.CancellationToken cancellationToken, System.Collections.Generic.IDictionary? environmentVariables) -> System.Threading.Tasks.Task! Xamarin.Android.Tools.AvdInfo Xamarin.Android.Tools.AvdInfo.AvdInfo(string! Name, string? DeviceProfile = null, string? Path = null) -> void Xamarin.Android.Tools.AvdInfo.DeviceProfile.get -> string? From 7d5d3ad9ddb2b6a2cd8fa83415cfca86defcbd4d Mon Sep 17 00:00:00 2001 From: Rui Marinho Date: Thu, 19 Mar 2026 15:50:44 +0000 Subject: [PATCH 258/308] Fix AdbRunner AVD detection: use getprop first, skip offline emulators (#312) Two fixes for AdbRunner emulator detection reliability: 1. Swap AVD name detection order in GetEmulatorAvdNameAsync: use 'adb shell getprop ro.boot.qemu.avd_name' first (reliable on all modern emulators), fall back to 'adb emu avd name' for older versions. The 'emu avd name' command returns empty output on emulator 36.4.10+. 2. Skip AVD name queries for offline emulators in ListDevicesAsync: neither getprop nor 'emu avd name' works on offline devices, causing unnecessary delays during boot polling loops. Context: dotnet/android#10965 --- .../Runners/AdbRunner.cs | 51 ++++--- .../AdbRunnerTests.cs | 142 ++++++++++++++++++ 2 files changed, 170 insertions(+), 23 deletions(-) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Runners/AdbRunner.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Runners/AdbRunner.cs index 0dd60476966..9f91b2e3bd9 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Runners/AdbRunner.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Runners/AdbRunner.cs @@ -36,7 +36,7 @@ public class AdbRunner /// /// Full path to the adb executable (e.g., "/path/to/sdk/platform-tools/adb"). /// Optional environment variables to pass to adb processes. - /// Optional logger callback for diagnostic messages. + /// Optional logger callback receiving a and message string. public AdbRunner (string adbPath, IDictionary? environmentVariables = null, Action? logger = null) { if (string.IsNullOrWhiteSpace (adbPath)) @@ -48,7 +48,8 @@ public AdbRunner (string adbPath, IDictionary? environmentVariab /// /// Lists connected devices using 'adb devices -l'. - /// For emulators, queries the AVD name using 'adb -s <serial> emu avd name'. + /// For online emulators, queries the AVD name via getprop / emu avd name. + /// Offline emulators are included but without AVD names (querying them would fail). /// public virtual async Task> ListDevicesAsync (CancellationToken cancellationToken = default) { @@ -61,11 +62,15 @@ public virtual async Task> ListDevicesAsync (Cancel var devices = ParseAdbDevicesOutput (stdout.ToString ().Split ('\n')); - // For each emulator, try to get the AVD name + // For each online emulator, try to get the AVD name. + // Skip offline emulators — neither getprop nor 'emu avd name' work on them + // and attempting these commands causes unnecessary delays during boot polling. foreach (var device in devices) { - if (device.Type == AdbDeviceType.Emulator) { + if (device.Type == AdbDeviceType.Emulator && device.Status == AdbDeviceStatus.Online) { device.AvdName = await GetEmulatorAvdNameAsync (device.Serial, cancellationToken).ConfigureAwait (false); device.Description = BuildDeviceDescription (device); + } else if (device.Type == AdbDeviceType.Emulator) { + logger.Invoke (TraceLevel.Verbose, $"Skipping AVD name query for {device.Status} emulator {device.Serial}"); } } @@ -74,15 +79,26 @@ public virtual async Task> ListDevicesAsync (Cancel /// /// Queries the emulator for its AVD name. - /// Tries adb -s <serial> emu avd name first (emulator console protocol), - /// then falls back to adb shell getprop ro.boot.qemu.avd_name which reads the - /// boot property set by the emulator kernel. The fallback is needed because - /// emu avd name can return empty output on some adb/emulator version - /// combinations (observed with adb v36). + /// Tries adb shell getprop ro.boot.qemu.avd_name first (reliable on all modern + /// emulators), then falls back to adb -s <serial> emu avd name (emulator + /// console protocol) for older emulator versions. The emu avd name command returns + /// empty output on emulator 36.4.10+ (observed with adb v36), so getprop is the + /// preferred method. /// internal async Task GetEmulatorAvdNameAsync (string serial, CancellationToken cancellationToken = default) { - // Try 1: Console command (fast, works on most emulator versions) + // Try 1: Shell property (reliable on modern emulators, always set by the emulator kernel) + try { + var avdName = await GetShellPropertyAsync (serial, "ro.boot.qemu.avd_name", cancellationToken).ConfigureAwait (false); + if (avdName is { Length: > 0 } name && !string.IsNullOrWhiteSpace (name)) + return name.Trim (); + } catch (OperationCanceledException) { + throw; + } catch (Exception ex) { + logger.Invoke (TraceLevel.Warning, $"GetEmulatorAvdNameAsync: getprop failed for {serial}: {ex.Message}"); + } + + // Try 2: Console command (fallback for older emulators where getprop may not be available) try { using var stdout = new StringWriter (); var psi = ProcessUtils.CreateProcessStartInfo (adbPath, "-s", serial, "emu", "avd", "name"); @@ -97,19 +113,8 @@ public virtual async Task> ListDevicesAsync (Cancel } } catch (OperationCanceledException) { throw; - } catch { - // Fall through to getprop fallback - } - - // Try 2: Shell property (fallback when 'adb emu avd name' returns empty on some adb/emulator versions) - try { - var avdName = await GetShellPropertyAsync (serial, "ro.boot.qemu.avd_name", cancellationToken).ConfigureAwait (false); - if (avdName is { Length: > 0 } name && !string.IsNullOrWhiteSpace (name)) - return name.Trim (); - } catch (OperationCanceledException) { - throw; - } catch { - // Both methods failed + } catch (Exception ex) { + logger.Invoke (TraceLevel.Warning, $"GetEmulatorAvdNameAsync: both methods failed for {serial}: {ex.Message}"); } return null; diff --git a/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/AdbRunnerTests.cs b/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/AdbRunnerTests.cs index c9536cf93d3..49b56db6f00 100644 --- a/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/AdbRunnerTests.cs +++ b/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/AdbRunnerTests.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Threading.Tasks; using NUnit.Framework; namespace Xamarin.Android.Tools.Tests; @@ -714,4 +715,145 @@ public void FirstNonEmptyLine_PmPathOutput () var output = "package:/system/framework/framework-res.apk\n"; Assert.AreEqual ("package:/system/framework/framework-res.apk", AdbRunner.FirstNonEmptyLine (output)); } + + // --- GetEmulatorAvdNameAsync + ListDevicesAsync tests --- + // These tests use a fake 'adb' script to control process output, + // verifying AVD detection order and offline emulator handling. + + static string CreateFakeAdb (string scriptBody) + { + if (OS.IsWindows) + Assert.Ignore ("Fake adb tests use bash scripts and are not supported on Windows."); + + var dir = Path.Combine (Path.GetTempPath (), $"fake-adb-{Guid.NewGuid ():N}"); + Directory.CreateDirectory (dir); + var path = Path.Combine (dir, "adb"); + File.WriteAllText (path, "#!/bin/bash\n" + scriptBody); + FileUtil.Chmod (path, 0x1ED); // 0755 + + return path; + } + + static void CleanupFakeAdb (string adbPath) + { + var dir = Path.GetDirectoryName (adbPath); + if (dir is { Length: > 0 }) { + File.Delete (adbPath); + Directory.Delete (dir); + } + } + + [Test] + public async Task GetEmulatorAvdNameAsync_PrefersGetprop () + { + // getprop returns a value — should be used, emu avd name should NOT be needed + var adbPath = CreateFakeAdb (""" + if [[ "$3" == "shell" && "$4" == "getprop" ]]; then + echo "My_AVD_Name" + exit 0 + fi + if [[ "$3" == "emu" ]]; then + echo "WRONG_NAME" + echo "OK" + exit 0 + fi + exit 1 + """); + + try { + var runner = new AdbRunner (adbPath); + var name = await runner.GetEmulatorAvdNameAsync ("emulator-5554"); + Assert.AreEqual ("My_AVD_Name", name, "Should return getprop result"); + } finally { + CleanupFakeAdb (adbPath); + } + } + + [Test] + public async Task GetEmulatorAvdNameAsync_FallsBackToEmuAvdName () + { + // getprop returns empty — should fall back to emu avd name + var adbPath = CreateFakeAdb (""" + if [[ "$3" == "shell" && "$4" == "getprop" ]]; then + echo "" + exit 0 + fi + if [[ "$3" == "emu" ]]; then + echo "Fallback_AVD" + echo "OK" + exit 0 + fi + exit 1 + """); + + try { + var runner = new AdbRunner (adbPath); + var name = await runner.GetEmulatorAvdNameAsync ("emulator-5554"); + Assert.AreEqual ("Fallback_AVD", name, "Should fall back to emu avd name"); + } finally { + CleanupFakeAdb (adbPath); + } + } + + [Test] + public async Task GetEmulatorAvdNameAsync_BothFail_ReturnsNull () + { + // Both getprop and emu avd name return empty + var adbPath = CreateFakeAdb (""" + echo "" + exit 0 + """); + + try { + var runner = new AdbRunner (adbPath); + var name = await runner.GetEmulatorAvdNameAsync ("emulator-5554"); + Assert.IsNull (name, "Should return null when both methods fail"); + } finally { + CleanupFakeAdb (adbPath); + } + } + + [Test] + public async Task ListDevicesAsync_SkipsAvdQueryForOfflineEmulators () + { + // adb devices returns one online emulator and one offline emulator. + // Only the online one should get an AVD name query. + var adbPath = CreateFakeAdb (""" + if [[ "$1" == "devices" ]]; then + echo "List of devices attached" + echo "emulator-5554 device product:sdk_gphone64_arm64 model:sdk_gphone64_arm64 device:emu64a transport_id:1" + echo "emulator-5556 offline" + exit 0 + fi + if [[ "$1" == "-s" && "$2" == "emulator-5554" && "$3" == "shell" && "$4" == "getprop" ]]; then + echo "Online_AVD" + exit 0 + fi + if [[ "$1" == "-s" && "$2" == "emulator-5556" ]]; then + # This should NOT be called for offline emulators. + # Return a name anyway so we can detect if it was incorrectly queried. + echo "OFFLINE_SHOULD_NOT_APPEAR" + exit 0 + fi + exit 1 + """); + + try { + var runner = new AdbRunner (adbPath); + var devices = await runner.ListDevicesAsync (); + + Assert.AreEqual (2, devices.Count, "Should return both emulators"); + + var online = devices.First (d => d.Serial == "emulator-5554"); + var offline = devices.First (d => d.Serial == "emulator-5556"); + + Assert.AreEqual (AdbDeviceStatus.Online, online.Status); + Assert.AreEqual ("Online_AVD", online.AvdName, "Online emulator should have AVD name"); + + Assert.AreEqual (AdbDeviceStatus.Offline, offline.Status); + Assert.IsNull (offline.AvdName, "Offline emulator should NOT have AVD name queried"); + } finally { + CleanupFakeAdb (adbPath); + } + } } From 30e6bddda0d0abdd8cd1f916664c33e856ba1ab9 Mon Sep 17 00:00:00 2001 From: Rui Marinho Date: Thu, 19 Mar 2026 19:03:15 +0000 Subject: [PATCH 259/308] [copilot-instructions] API design rules from PR #305 review (#317) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add three rules learned from PR #305 review feedback: - Prefer strongly-typed APIs (enum+record) over string parameters - Avoid convenience overloads (string, int, typed) — pick one - Include stdout in ProcessUtils.ThrowIfFailed error diagnostics Co-authored-by: Jonathan Peppers --- external/xamarin-android-tools/.github/copilot-instructions.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/external/xamarin-android-tools/.github/copilot-instructions.md b/external/xamarin-android-tools/.github/copilot-instructions.md index a18916c4e37..004834875e3 100644 --- a/external/xamarin-android-tools/.github/copilot-instructions.md +++ b/external/xamarin-android-tools/.github/copilot-instructions.md @@ -48,6 +48,9 @@ When setting environment variables for SDK tools (e.g. `sdkmanager`, `avdmanager - **One type per file**: each public class, struct, enum, or interface must be in its own `.cs` file named after the type (e.g. `JdkVersionInfo` → `JdkVersionInfo.cs`). Do not combine multiple top-level types in a single file. - **Minimal public API**: prefer `internal` for new methods/classes unless they are consumed by external projects (dotnet/android, IDE extensions). Use `InternalsVisibleTo` for test access. +- **Strongly-typed APIs over strings**: when an API parameter has a finite set of valid forms (e.g. `"tcp:5000"`), use a record/enum pair (e.g. `AdbPortSpec(AdbProtocol.Tcp, 5000)`) instead of raw strings. Callers get compile-time safety, IntelliSense, and pattern matching. +- **Avoid convenience overloads**: don't add `string`, `int`, and strongly-typed overloads for the same method. Pick the strongly-typed signature and let callers construct the type. Fewer overloads = smaller API surface, fewer RS0027 suppressions, and less maintenance. +- **Include stdout in error diagnostics**: when a method captures stdout (e.g. `ListReversePortsAsync`), pass it to `ProcessUtils.ThrowIfFailed(exitCode, command, stderr, stdout)` so failure messages include all output, not just stderr. - **Update PublicAPI files**: when adding or removing `public` API surface, update the `PublicAPI.Unshipped.txt` files under `src/Xamarin.Android.Tools.AndroidSdk/PublicAPI/net10.0/` and `src/Xamarin.Android.Tools.AndroidSdk/PublicAPI/netstandard2.0/`. New types and members go in the unshipped file. Build with `--no-incremental` and verify zero `RS0016` (missing) or `RS0017` (removed) warnings. See `PublicAPI.Shipped.txt` for the expected entry format. - **Use `ProcessUtils`**: never use `System.Diagnostics.Process` directly. Use the existing helpers such as `ProcessUtils.CreateProcessStartInfo()`, `ProcessUtils.StartProcess()`, and `ProcessUtils.ExecuteToolAsync()` for launching external tools. This ensures consistent logging, timeout handling, and cancellation. - **Process arguments**: use `ProcessUtils.CreateProcessStartInfo()` and pass arguments as separate strings instead of building a single arguments string yourself. `ProcessUtils` will use `ProcessStartInfo.ArgumentList` when available and fall back to `Arguments` on `netstandard2.0`. From 781d86d206256f21d1d6ee681d83e72c3ffc2c05 Mon Sep 17 00:00:00 2001 From: Rui Marinho Date: Mon, 23 Mar 2026 13:40:29 +0000 Subject: [PATCH 260/308] Add ADB reverse port forwarding support (#305) Add ReversePortAsync, RemoveReversePortAsync, RemoveAllReversePortsAsync, and ListReversePortsAsync methods to AdbRunner for managing reverse port forwarding rules. These APIs enable the MAUI DevTools CLI to manage hot-reload tunnels without going through ServiceHub. New type AdbReversePortRule represents entries from 'adb reverse --list'. Internal ParseReverseListOutput handles parsing the output format. Includes 14 new tests covering parsing and parameter validation. Closes #303 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: Jonathan Peppers --- .../PublicAPI/net10.0/PublicAPI.Unshipped.txt | 22 ++ .../netstandard2.0/PublicAPI.Unshipped.txt | 22 ++ .../Runners/AdbPortRule.cs | 9 + .../Runners/AdbPortSpec.cs | 49 +++ .../Runners/AdbProtocol.cs | 15 + .../Runners/AdbRunner.cs | 111 ++++++ .../AdbRunnerTests.cs | 353 ++++++++++++++++++ 7 files changed, 581 insertions(+) create mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Runners/AdbPortRule.cs create mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Runners/AdbPortSpec.cs create mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Runners/AdbProtocol.cs diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/PublicAPI/net10.0/PublicAPI.Unshipped.txt b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/PublicAPI/net10.0/PublicAPI.Unshipped.txt index e8f26266966..61fc0e0bdd2 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/PublicAPI/net10.0/PublicAPI.Unshipped.txt +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/PublicAPI/net10.0/PublicAPI.Unshipped.txt @@ -177,3 +177,25 @@ Xamarin.Android.Tools.EmulatorRunner.BootEmulatorAsync(string! deviceOrAvdName, Xamarin.Android.Tools.EmulatorRunner.EmulatorRunner(string! emulatorPath, System.Collections.Generic.IDictionary? environmentVariables = null, System.Action? logger = null) -> void Xamarin.Android.Tools.EmulatorRunner.ListAvdNamesAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task!>! Xamarin.Android.Tools.EmulatorRunner.LaunchEmulator(string! avdName, bool coldBoot = false, System.Collections.Generic.List? additionalArgs = null) -> System.Diagnostics.Process! +Xamarin.Android.Tools.AdbPortRule +Xamarin.Android.Tools.AdbPortRule.AdbPortRule(Xamarin.Android.Tools.AdbPortSpec! Remote, Xamarin.Android.Tools.AdbPortSpec! Local) -> void +Xamarin.Android.Tools.AdbPortRule.Local.get -> Xamarin.Android.Tools.AdbPortSpec! +Xamarin.Android.Tools.AdbPortRule.Local.init -> void +Xamarin.Android.Tools.AdbPortRule.Remote.get -> Xamarin.Android.Tools.AdbPortSpec! +Xamarin.Android.Tools.AdbPortRule.Remote.init -> void +Xamarin.Android.Tools.AdbPortSpec +Xamarin.Android.Tools.AdbPortSpec.AdbPortSpec(Xamarin.Android.Tools.AdbProtocol Protocol, int Port) -> void +Xamarin.Android.Tools.AdbPortSpec.Port.get -> int +Xamarin.Android.Tools.AdbPortSpec.Port.init -> void +Xamarin.Android.Tools.AdbPortSpec.Protocol.get -> Xamarin.Android.Tools.AdbProtocol +Xamarin.Android.Tools.AdbPortSpec.Protocol.init -> void +Xamarin.Android.Tools.AdbPortSpec.ToSocketSpec() -> string! +Xamarin.Android.Tools.AdbProtocol + +Xamarin.Android.Tools.AdbProtocol.Tcp = 0 -> Xamarin.Android.Tools.AdbProtocol +override Xamarin.Android.Tools.AdbPortSpec.ToString() -> string! +static Xamarin.Android.Tools.AdbPortSpec.TryParse(string? socketSpec) -> Xamarin.Android.Tools.AdbPortSpec? +virtual Xamarin.Android.Tools.AdbRunner.ListReversePortsAsync(string! serial, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task!>! +virtual Xamarin.Android.Tools.AdbRunner.RemoveAllReversePortsAsync(string! serial, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task! +virtual Xamarin.Android.Tools.AdbRunner.RemoveReversePortAsync(string! serial, Xamarin.Android.Tools.AdbPortSpec! remote, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task! +virtual Xamarin.Android.Tools.AdbRunner.ReversePortAsync(string! serial, Xamarin.Android.Tools.AdbPortSpec! remote, Xamarin.Android.Tools.AdbPortSpec! local, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task! diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt index e8f26266966..61fc0e0bdd2 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt @@ -177,3 +177,25 @@ Xamarin.Android.Tools.EmulatorRunner.BootEmulatorAsync(string! deviceOrAvdName, Xamarin.Android.Tools.EmulatorRunner.EmulatorRunner(string! emulatorPath, System.Collections.Generic.IDictionary? environmentVariables = null, System.Action? logger = null) -> void Xamarin.Android.Tools.EmulatorRunner.ListAvdNamesAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task!>! Xamarin.Android.Tools.EmulatorRunner.LaunchEmulator(string! avdName, bool coldBoot = false, System.Collections.Generic.List? additionalArgs = null) -> System.Diagnostics.Process! +Xamarin.Android.Tools.AdbPortRule +Xamarin.Android.Tools.AdbPortRule.AdbPortRule(Xamarin.Android.Tools.AdbPortSpec! Remote, Xamarin.Android.Tools.AdbPortSpec! Local) -> void +Xamarin.Android.Tools.AdbPortRule.Local.get -> Xamarin.Android.Tools.AdbPortSpec! +Xamarin.Android.Tools.AdbPortRule.Local.init -> void +Xamarin.Android.Tools.AdbPortRule.Remote.get -> Xamarin.Android.Tools.AdbPortSpec! +Xamarin.Android.Tools.AdbPortRule.Remote.init -> void +Xamarin.Android.Tools.AdbPortSpec +Xamarin.Android.Tools.AdbPortSpec.AdbPortSpec(Xamarin.Android.Tools.AdbProtocol Protocol, int Port) -> void +Xamarin.Android.Tools.AdbPortSpec.Port.get -> int +Xamarin.Android.Tools.AdbPortSpec.Port.init -> void +Xamarin.Android.Tools.AdbPortSpec.Protocol.get -> Xamarin.Android.Tools.AdbProtocol +Xamarin.Android.Tools.AdbPortSpec.Protocol.init -> void +Xamarin.Android.Tools.AdbPortSpec.ToSocketSpec() -> string! +Xamarin.Android.Tools.AdbProtocol + +Xamarin.Android.Tools.AdbProtocol.Tcp = 0 -> Xamarin.Android.Tools.AdbProtocol +override Xamarin.Android.Tools.AdbPortSpec.ToString() -> string! +static Xamarin.Android.Tools.AdbPortSpec.TryParse(string? socketSpec) -> Xamarin.Android.Tools.AdbPortSpec? +virtual Xamarin.Android.Tools.AdbRunner.ListReversePortsAsync(string! serial, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task!>! +virtual Xamarin.Android.Tools.AdbRunner.RemoveAllReversePortsAsync(string! serial, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task! +virtual Xamarin.Android.Tools.AdbRunner.RemoveReversePortAsync(string! serial, Xamarin.Android.Tools.AdbPortSpec! remote, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task! +virtual Xamarin.Android.Tools.AdbRunner.ReversePortAsync(string! serial, Xamarin.Android.Tools.AdbPortSpec! remote, Xamarin.Android.Tools.AdbPortSpec! local, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task! diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Runners/AdbPortRule.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Runners/AdbPortRule.cs new file mode 100644 index 00000000000..190e6661196 --- /dev/null +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Runners/AdbPortRule.cs @@ -0,0 +1,9 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Xamarin.Android.Tools; + +/// +/// Represents an adb port forwarding rule as reported by 'adb reverse --list' or 'adb forward --list'. +/// +public record AdbPortRule (AdbPortSpec Remote, AdbPortSpec Local); diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Runners/AdbPortSpec.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Runners/AdbPortSpec.cs new file mode 100644 index 00000000000..765c6ad29c3 --- /dev/null +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Runners/AdbPortSpec.cs @@ -0,0 +1,49 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; + +namespace Xamarin.Android.Tools; + +/// +/// Represents a port and protocol pair for adb forwarding/reverse operations. +/// +public record AdbPortSpec (AdbProtocol Protocol, int Port) +{ + /// + /// Returns the adb socket spec string, e.g. "tcp:5000". + /// + public string ToSocketSpec () => Protocol switch { + AdbProtocol.Tcp => FormattableString.Invariant ($"tcp:{Port}"), + _ => throw new ArgumentOutOfRangeException (nameof (Protocol), Protocol, $"Unsupported ADB protocol: {Protocol}"), + }; + + /// + /// Parses an adb socket spec string like "tcp:5000" into an . + /// Returns null if the format is unrecognized. + /// + public static AdbPortSpec? TryParse (string? socketSpec) + { + if (socketSpec is not { Length: > 0 } value || string.IsNullOrWhiteSpace (value)) + return null; + + var colonIndex = value.IndexOf (':'); + if (colonIndex <= 0 || colonIndex >= value.Length - 1) + return null; + + var protocolStr = value.Substring (0, colonIndex); + var portStr = value.Substring (colonIndex + 1); + + if (!int.TryParse (portStr, out var port) || port <= 0 || port > 65535) + return null; + + var protocol = protocolStr.ToLowerInvariant () switch { + "tcp" => (AdbProtocol?) AdbProtocol.Tcp, + _ => null, + }; + + return protocol.HasValue ? new AdbPortSpec (protocol.Value, port) : null; + } + + public override string ToString () => ToSocketSpec (); +} diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Runners/AdbProtocol.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Runners/AdbProtocol.cs new file mode 100644 index 00000000000..8b2c2dc4817 --- /dev/null +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Runners/AdbProtocol.cs @@ -0,0 +1,15 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Xamarin.Android.Tools; + +/// +/// Protocol types supported by adb port forwarding and reverse port forwarding. +/// +public enum AdbProtocol +{ + /// + /// TCP socket spec, e.g. "tcp:5000". + /// + Tcp, +} diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Runners/AdbRunner.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Runners/AdbRunner.cs index 9f91b2e3bd9..0eadc70e379 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Runners/AdbRunner.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Runners/AdbRunner.cs @@ -244,6 +244,117 @@ public async Task StopEmulatorAsync (string serial, CancellationToken cancellati return null; } + /// + /// Sets up reverse port forwarding via 'adb -s <serial> reverse <remote> <local>'. + /// + /// Device serial number. + /// Remote (device-side) port spec. + /// Local (host-side) port spec. + /// Cancellation token. + public virtual async Task ReversePortAsync (string serial, AdbPortSpec remote, AdbPortSpec local, CancellationToken cancellationToken = default) + { + if (string.IsNullOrWhiteSpace (serial)) + throw new ArgumentException ("Serial must not be empty.", nameof (serial)); + if (remote is null) + throw new ArgumentNullException (nameof (remote)); + if (local is null) + throw new ArgumentNullException (nameof (local)); + if (remote.Port <= 0 || remote.Port > 65535) + throw new ArgumentOutOfRangeException (nameof (remote), remote.Port, "Port must be between 1 and 65535."); + if (local.Port <= 0 || local.Port > 65535) + throw new ArgumentOutOfRangeException (nameof (local), local.Port, "Port must be between 1 and 65535."); + + var psi = ProcessUtils.CreateProcessStartInfo (adbPath, "-s", serial, "reverse", remote.ToSocketSpec (), local.ToSocketSpec ()); + using var stderr = new StringWriter (); + var exitCode = await ProcessUtils.StartProcess (psi, null, stderr, cancellationToken, environmentVariables).ConfigureAwait (false); + ProcessUtils.ThrowIfFailed (exitCode, $"adb -s {serial} reverse {remote} {local}", stderr); + } + + /// + /// Removes a specific reverse port forwarding rule via + /// 'adb -s <serial> reverse --remove <remote>'. + /// + /// Device serial number. + /// Remote (device-side) port spec to remove. + /// Cancellation token. + public virtual async Task RemoveReversePortAsync (string serial, AdbPortSpec remote, CancellationToken cancellationToken = default) + { + if (string.IsNullOrWhiteSpace (serial)) + throw new ArgumentException ("Serial must not be empty.", nameof (serial)); + if (remote is null) + throw new ArgumentNullException (nameof (remote)); + if (remote.Port <= 0 || remote.Port > 65535) + throw new ArgumentOutOfRangeException (nameof (remote), remote.Port, "Port must be between 1 and 65535."); + + var psi = ProcessUtils.CreateProcessStartInfo (adbPath, "-s", serial, "reverse", "--remove", remote.ToSocketSpec ()); + using var stderr = new StringWriter (); + var exitCode = await ProcessUtils.StartProcess (psi, null, stderr, cancellationToken, environmentVariables).ConfigureAwait (false); + ProcessUtils.ThrowIfFailed (exitCode, $"adb -s {serial} reverse --remove {remote}", stderr); + } + + /// + /// Removes all reverse port forwarding rules via + /// 'adb -s <serial> reverse --remove-all'. + /// + public virtual async Task RemoveAllReversePortsAsync (string serial, CancellationToken cancellationToken = default) + { + if (string.IsNullOrWhiteSpace (serial)) + throw new ArgumentException ("Serial must not be empty.", nameof (serial)); + + var psi = ProcessUtils.CreateProcessStartInfo (adbPath, "-s", serial, "reverse", "--remove-all"); + using var stderr = new StringWriter (); + var exitCode = await ProcessUtils.StartProcess (psi, null, stderr, cancellationToken, environmentVariables).ConfigureAwait (false); + ProcessUtils.ThrowIfFailed (exitCode, $"adb -s {serial} reverse --remove-all", stderr); + } + + /// + /// Lists all active reverse port forwarding rules via + /// 'adb -s <serial> reverse --list'. + /// + public virtual async Task> ListReversePortsAsync (string serial, CancellationToken cancellationToken = default) + { + if (string.IsNullOrWhiteSpace (serial)) + throw new ArgumentException ("Serial must not be empty.", nameof (serial)); + + using var stdout = new StringWriter (); + using var stderr = new StringWriter (); + var psi = ProcessUtils.CreateProcessStartInfo (adbPath, "-s", serial, "reverse", "--list"); + var exitCode = await ProcessUtils.StartProcess (psi, stdout, stderr, cancellationToken, environmentVariables).ConfigureAwait (false); + ProcessUtils.ThrowIfFailed (exitCode, $"adb -s {serial} reverse --list", stderr, stdout); + + return ParseReverseListOutput (stdout.ToString ().Split ('\n')); + } + + /// + /// Parses the output of 'adb reverse --list'. + /// Each line is "(reverse) <remote> <local>", e.g. "(reverse) tcp:5000 tcp:5000". + /// Lines with unparseable socket specs are skipped. + /// + internal static IReadOnlyList ParseReverseListOutput (IEnumerable lines) + { + var rules = new List (); + + foreach (var line in lines) { + var trimmed = line.Trim (); + if (string.IsNullOrEmpty (trimmed)) + continue; + + // Expected format: "(reverse) tcp:5000 tcp:5000" + if (!trimmed.StartsWith ("(reverse)", StringComparison.Ordinal)) + continue; + + var parts = trimmed.Substring ("(reverse)".Length).Trim ().Split ((char[]?) null, StringSplitOptions.RemoveEmptyEntries); + if (parts.Length >= 2) { + var remote = AdbPortSpec.TryParse (parts [0]); + var local = AdbPortSpec.TryParse (parts [1]); + if (remote is { } r && local is { } l) + rules.Add (new AdbPortRule (r, l)); + } + } + + return rules; + } + /// /// Parses the output lines from 'adb devices -l'. /// Accepts an to avoid allocating a joined string. diff --git a/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/AdbRunnerTests.cs b/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/AdbRunnerTests.cs index 49b56db6f00..00b5ccc643a 100644 --- a/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/AdbRunnerTests.cs +++ b/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/AdbRunnerTests.cs @@ -716,6 +716,359 @@ public void FirstNonEmptyLine_PmPathOutput () Assert.AreEqual ("package:/system/framework/framework-res.apk", AdbRunner.FirstNonEmptyLine (output)); } + // --- ParseReverseListOutput tests --- + // Consumer: MAUI DevTools (via ListReversePortsAsync), vscode-maui ServiceHub replacement + + [Test] + public void ParseReverseListOutput_SingleRule () + { + var output = new [] { + "(reverse) tcp:5000 tcp:5000", + }; + + var rules = AdbRunner.ParseReverseListOutput (output); + + Assert.AreEqual (1, rules.Count); + Assert.AreEqual (AdbProtocol.Tcp, rules [0].Remote.Protocol); + Assert.AreEqual (5000, rules [0].Remote.Port); + Assert.AreEqual (AdbProtocol.Tcp, rules [0].Local.Protocol); + Assert.AreEqual (5000, rules [0].Local.Port); + } + + [Test] + public void ParseReverseListOutput_MultipleRules () + { + var output = new [] { + "(reverse) tcp:5000 tcp:5000", + "(reverse) tcp:8081 tcp:8081", + "(reverse) tcp:19000 tcp:19001", + }; + + var rules = AdbRunner.ParseReverseListOutput (output); + + Assert.AreEqual (3, rules.Count); + Assert.AreEqual (AdbProtocol.Tcp, rules [0].Remote.Protocol); + Assert.AreEqual (5000, rules [0].Remote.Port); + Assert.AreEqual (5000, rules [0].Local.Port); + Assert.AreEqual (8081, rules [1].Remote.Port); + Assert.AreEqual (8081, rules [1].Local.Port); + Assert.AreEqual (19000, rules [2].Remote.Port); + Assert.AreEqual (19001, rules [2].Local.Port); + } + + [Test] + public void ParseReverseListOutput_EmptyOutput () + { + var output = new [] { "", " " }; + var rules = AdbRunner.ParseReverseListOutput (output); + Assert.AreEqual (0, rules.Count); + } + + [Test] + public void ParseReverseListOutput_NoLines () + { + var rules = AdbRunner.ParseReverseListOutput (Array.Empty ()); + Assert.AreEqual (0, rules.Count); + } + + [Test] + public void ParseReverseListOutput_IgnoresNonReverseLines () + { + var output = new [] { + "some random header", + "(reverse) tcp:5000 tcp:5000", + "* daemon started successfully", + "(reverse) tcp:8081 tcp:8081", + "", + }; + + var rules = AdbRunner.ParseReverseListOutput (output); + + Assert.AreEqual (2, rules.Count); + Assert.AreEqual (5000, rules [0].Remote.Port); + Assert.AreEqual (8081, rules [1].Remote.Port); + } + + [Test] + public void ParseReverseListOutput_MalformedLine_InsufficientParts () + { + var output = new [] { + "(reverse) tcp:5000", // missing local spec + }; + + var rules = AdbRunner.ParseReverseListOutput (output); + Assert.AreEqual (0, rules.Count); + } + + [Test] + public void ParseReverseListOutput_DifferentRemoteAndLocalPorts () + { + var output = new [] { + "(reverse) tcp:8080 tcp:3000", + }; + + var rules = AdbRunner.ParseReverseListOutput (output); + + Assert.AreEqual (1, rules.Count); + Assert.AreEqual (AdbProtocol.Tcp, rules [0].Remote.Protocol); + Assert.AreEqual (8080, rules [0].Remote.Port); + Assert.AreEqual (AdbProtocol.Tcp, rules [0].Local.Protocol); + Assert.AreEqual (3000, rules [0].Local.Port); + } + + [Test] + public void ParseReverseListOutput_NonTcpSpecs_SkipsUnparseable () + { + var output = new [] { + "(reverse) localabstract:chrome_devtools_remote tcp:9222", + "(reverse) tcp:5000 tcp:5000", + }; + + var rules = AdbRunner.ParseReverseListOutput (output); + + // localabstract:chrome_devtools_remote has a non-numeric port, so it is skipped + Assert.AreEqual (1, rules.Count); + Assert.AreEqual (AdbProtocol.Tcp, rules [0].Remote.Protocol); + Assert.AreEqual (5000, rules [0].Remote.Port); + } + + [Test] + public void ParseReverseListOutput_WindowsLineEndings () + { + // Simulate \r\n line endings (split on \n leaves trailing \r) + var output = new [] { + "(reverse) tcp:5000 tcp:5000\r", + "(reverse) tcp:8081 tcp:8081\r", + }; + + var rules = AdbRunner.ParseReverseListOutput (output); + + Assert.AreEqual (2, rules.Count); + Assert.AreEqual (5000, rules [0].Remote.Port); + Assert.AreEqual (8081, rules [1].Remote.Port); + } + + [Test] + public void ParseReverseListOutput_TabSeparated () + { + var output = new [] { + "(reverse)\ttcp:5000\ttcp:5000", + "(reverse)\ttcp:8081\ttcp:8081", + }; + + var rules = AdbRunner.ParseReverseListOutput (output); + + Assert.AreEqual (2, rules.Count); + Assert.AreEqual (5000, rules [0].Remote.Port); + Assert.AreEqual (8081, rules [1].Remote.Port); + } + + // --- AdbPortSpec tests --- + + [Test] + public void AdbPortSpec_TryParse_ValidTcp () + { + var spec = AdbPortSpec.TryParse ("tcp:5000"); + if (spec is null) { + Assert.Fail ("Expected non-null AdbPortSpec"); + return; + } + Assert.AreEqual (AdbProtocol.Tcp, spec.Protocol); + Assert.AreEqual (5000, spec.Port); + } + + [Test] + public void AdbPortSpec_TryParse_NonTcpProtocol_ReturnsNull () + { + Assert.IsNull (AdbPortSpec.TryParse ("localabstract:9222")); + } + + [Test] + public void AdbPortSpec_TryParse_Null_ReturnsNull () + { + Assert.IsNull (AdbPortSpec.TryParse (default)); + } + + [Test] + public void AdbPortSpec_TryParse_Empty_ReturnsNull () + { + Assert.IsNull (AdbPortSpec.TryParse ("")); + } + + [Test] + public void AdbPortSpec_TryParse_NoColon_ReturnsNull () + { + Assert.IsNull (AdbPortSpec.TryParse ("tcp5000")); + } + + [Test] + public void AdbPortSpec_TryParse_NonNumericPort_ReturnsNull () + { + Assert.IsNull (AdbPortSpec.TryParse ("localabstract:chrome_devtools_remote")); + } + + [Test] + public void AdbPortSpec_TryParse_ZeroPort_ReturnsNull () + { + Assert.IsNull (AdbPortSpec.TryParse ("tcp:0")); + } + + [Test] + public void AdbPortSpec_TryParse_PortAbove65535_ReturnsNull () + { + Assert.IsNull (AdbPortSpec.TryParse ("tcp:70000")); + } + + [Test] + public void AdbPortSpec_TryParse_UnknownProtocol_ReturnsNull () + { + Assert.IsNull (AdbPortSpec.TryParse ("udp:5000")); + } + + [Test] + public void AdbPortSpec_ToSocketSpec_Tcp () + { + var spec = new AdbPortSpec (AdbProtocol.Tcp, 5000); + Assert.AreEqual ("tcp:5000", spec.ToSocketSpec ()); + } + + [Test] + public void AdbPortSpec_ToSocketSpec_HighPort () + { + var spec = new AdbPortSpec (AdbProtocol.Tcp, 65535); + Assert.AreEqual ("tcp:65535", spec.ToSocketSpec ()); + } + + [Test] + public void AdbPortSpec_ToSocketSpec_LowPort () + { + var spec = new AdbPortSpec (AdbProtocol.Tcp, 1); + Assert.AreEqual ("tcp:1", spec.ToSocketSpec ()); + } + + [Test] + public void AdbPortSpec_ToSocketSpec_InvalidProtocol_Throws () + { + var spec = new AdbPortSpec ((AdbProtocol) 99, 5000); + Assert.Throws (() => spec.ToSocketSpec ()); + } + + [Test] + public void AdbPortSpec_ToString_MatchesSocketSpec () + { + var spec = new AdbPortSpec (AdbProtocol.Tcp, 8080); + Assert.AreEqual ("tcp:8080", spec.ToString ()); + } + + [Test] + public void AdbPortSpec_TryParse_Roundtrip () + { + var original = new AdbPortSpec (AdbProtocol.Tcp, 3000); + var parsed = AdbPortSpec.TryParse (original.ToSocketSpec ()); + Assert.AreEqual (original, parsed); + } + + // --- AdbPortRule tests --- + + [Test] + public void AdbPortRule_ValueEquality () + { + var rule1 = new AdbPortRule (new AdbPortSpec (AdbProtocol.Tcp, 5000), new AdbPortSpec (AdbProtocol.Tcp, 5000)); + var rule2 = new AdbPortRule (new AdbPortSpec (AdbProtocol.Tcp, 5000), new AdbPortSpec (AdbProtocol.Tcp, 5000)); + var rule3 = new AdbPortRule (new AdbPortSpec (AdbProtocol.Tcp, 5000), new AdbPortSpec (AdbProtocol.Tcp, 3000)); + + Assert.AreEqual (rule1, rule2); + Assert.AreNotEqual (rule1, rule3); + Assert.IsTrue (rule1 == rule2); + Assert.IsFalse (rule1 == rule3); + } + + [Test] + public void AdbPortRule_Deconstruct () + { + var rule = new AdbPortRule (new AdbPortSpec (AdbProtocol.Tcp, 5000), new AdbPortSpec (AdbProtocol.Tcp, 3000)); + var (remote, local) = rule; + + Assert.AreEqual (AdbProtocol.Tcp, remote.Protocol); + Assert.AreEqual (5000, remote.Port); + Assert.AreEqual (AdbProtocol.Tcp, local.Protocol); + Assert.AreEqual (3000, local.Port); + } + + [Test] + public void AdbPortRule_ToString () + { + var rule = new AdbPortRule (new AdbPortSpec (AdbProtocol.Tcp, 5000), new AdbPortSpec (AdbProtocol.Tcp, 3000)); + var str = rule.ToString (); + + Assert.That (str, Does.Contain ("tcp:5000")); + Assert.That (str, Does.Contain ("tcp:3000")); + } + + // --- ReversePortAsync parameter validation tests --- + + [Test] + public void ReversePortAsync_EmptySerial_ThrowsArgumentException () + { + var runner = new AdbRunner ("/fake/sdk/platform-tools/adb"); + Assert.ThrowsAsync ( + async () => await runner.ReversePortAsync ("", new AdbPortSpec (AdbProtocol.Tcp, 5000), new AdbPortSpec (AdbProtocol.Tcp, 5000))); + } + + [Test] + public void ReversePortAsync_NullRemote_ThrowsArgumentNull () + { + var runner = new AdbRunner ("/fake/sdk/platform-tools/adb"); + Assert.ThrowsAsync ( + async () => await runner.ReversePortAsync ("emulator-5554", (AdbPortSpec) null, new AdbPortSpec (AdbProtocol.Tcp, 5000))); + } + + [Test] + public void ReversePortAsync_NullLocal_ThrowsArgumentNull () + { + var runner = new AdbRunner ("/fake/sdk/platform-tools/adb"); + Assert.ThrowsAsync ( + async () => await runner.ReversePortAsync ("emulator-5554", new AdbPortSpec (AdbProtocol.Tcp, 5000), (AdbPortSpec) null)); + } + + // --- RemoveReversePortAsync parameter validation tests --- + + [Test] + public void RemoveReversePortAsync_EmptySerial_ThrowsArgumentException () + { + var runner = new AdbRunner ("/fake/sdk/platform-tools/adb"); + Assert.ThrowsAsync ( + async () => await runner.RemoveReversePortAsync ("", new AdbPortSpec (AdbProtocol.Tcp, 5000))); + } + + [Test] + public void RemoveReversePortAsync_NullRemote_ThrowsArgumentNull () + { + var runner = new AdbRunner ("/fake/sdk/platform-tools/adb"); + Assert.ThrowsAsync ( + async () => await runner.RemoveReversePortAsync ("emulator-5554", (AdbPortSpec) null)); + } + + // --- RemoveAllReversePortsAsync parameter validation tests --- + + [Test] + public void RemoveAllReversePortsAsync_EmptySerial_ThrowsArgumentException () + { + var runner = new AdbRunner ("/fake/sdk/platform-tools/adb"); + Assert.ThrowsAsync ( + async () => await runner.RemoveAllReversePortsAsync ("")); + } + + // --- ListReversePortsAsync parameter validation tests --- + + [Test] + public void ListReversePortsAsync_EmptySerial_ThrowsArgumentException () + { + var runner = new AdbRunner ("/fake/sdk/platform-tools/adb"); + Assert.ThrowsAsync ( + async () => await runner.ListReversePortsAsync ("")); + } + // --- GetEmulatorAvdNameAsync + ListDevicesAsync tests --- // These tests use a fake 'adb' script to control process output, // verifying AVD detection order and offline emulator handling. From 78bc962bf7820245704183b2416b20d8cef73e4b Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Mon, 23 Mar 2026 10:21:18 -0500 Subject: [PATCH 261/308] Create CODEOWNERS file for pull request assignments (#318) Added CODEOWNERS file to define code ownership for reviews. --- external/xamarin-android-tools/.github/CODEOWNERS | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 external/xamarin-android-tools/.github/CODEOWNERS diff --git a/external/xamarin-android-tools/.github/CODEOWNERS b/external/xamarin-android-tools/.github/CODEOWNERS new file mode 100644 index 00000000000..94a55112c29 --- /dev/null +++ b/external/xamarin-android-tools/.github/CODEOWNERS @@ -0,0 +1,12 @@ +# android-tools Code Owners File +# +# GitHub uses this file to determine who to assign for reviews +# on pull requests. Please keep this file alphabetically sorted. +# +# References: +# +# https://github.com/blog/2392-introducing-code-owners +# https://help.github.com/articles/about-codeowners + +# These owners will be the default owners for everything in the repo. +* @jonathanpeppers @simonrozsival From cf3266d841c75ad2998cbb9cf7c5f0bd311d95ed Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Thu, 2 Apr 2026 16:32:57 -0500 Subject: [PATCH 262/308] [Xamarin.Android.Tools.AndroidSdk] fall back to `android-{major}.0` (#320) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes: https://github.com/dotnet/android-tools/issues/319 Starting with API 37, Google's SDK Manager installs platform directories as `android-37.0` instead of `android-37`. Both `TryGetPlatformDirectoryFromApiLevel` and `IsPlatformInstalled` only tried `android-{major}`, so projects targeting these newer API levels would fail with **XA5207**. This adds a `.0` fallback to both methods: when the integer-only directory doesn't exist, also try `android-{id}.0`. This complements the data-side fix in dotnet/android#11072 (which sets the `Id` to `"37.0"` directly) by providing a safety net when the `Id` is just `"37"`. ### Changes - **`AndroidSdkInfo.TryGetPlatformDirectoryFromApiLevel`** — After existing lookups fail, try `GetPlatformDirectoryFromId(id + ".0")` when `id` is an integer. - **`AndroidSdkInfo.IsPlatformInstalled`** — Apply the same `.0` fallback so it stays consistent with `TryGetPlatformDirectoryFromApiLevel`. - **`AndroidSdkInfoTests`** — New test `TryGetPlatformDirectoryFromApiLevel_MajorFallsBackToMajorDotZero` creates only `android-37.0` and verifies both APIs find it. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../AndroidSdkInfo.cs | 13 ++++++- .../AndroidSdkInfoTests.cs | 38 +++++++++++++++++++ 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidSdkInfo.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidSdkInfo.cs index d6913e656ad..1e843a01828 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidSdkInfo.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidSdkInfo.cs @@ -120,12 +120,23 @@ public string GetPlatformDirectoryFromId (string id) if (dir != null && Directory.Exists (dir)) return dir; + // Starting with API 37, Google's SDK Manager installs platforms to + // "android-37.0" instead of "android-37". Try the "{major}.0" fallback. + // See: https://github.com/dotnet/android-tools/issues/319 + if (int.TryParse (id, out _)) { + dir = GetPlatformDirectoryFromId (id + ".0"); + if (Directory.Exists (dir)) + return dir; + } + return null; } public bool IsPlatformInstalled (int apiLevel) { - return apiLevel != 0 && Directory.Exists (GetPlatformDirectory (apiLevel)); + return apiLevel != 0 && + (Directory.Exists (GetPlatformDirectory (apiLevel)) || + Directory.Exists (GetPlatformDirectoryFromId (apiLevel + ".0"))); } public string? AndroidNdkPath { diff --git a/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/AndroidSdkInfoTests.cs b/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/AndroidSdkInfoTests.cs index e7a25217c6e..9461f1cde33 100644 --- a/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/AndroidSdkInfoTests.cs +++ b/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/AndroidSdkInfoTests.cs @@ -633,6 +633,44 @@ public void TryGetPlatformDirectoryFromApiLevel_MinorVersionDoesNotFallback () } } + [Test] + public void TryGetPlatformDirectoryFromApiLevel_MajorFallsBackToMajorDotZero () + { + // Starting with API 37, Google installs platforms to "android-37.0" + // instead of "android-37". Verify the fallback works. + // See: https://github.com/dotnet/android-tools/issues/319 + CreateSdks (out string root, out string jdk, out string ndk, out string sdk); + + // Only create android-37.0, not android-37 + var platformsPath = Path.Combine (sdk, "platforms"); + var platform370Path = Path.Combine (platformsPath, "android-37.0"); + Directory.CreateDirectory (platform370Path); + File.WriteAllText (Path.Combine (platform370Path, "android.jar"), ""); + + var logs = new StringWriter (); + Action logger = (level, message) => { + logs.WriteLine ($"[{level}] {message}"); + }; + + try { + var info = new AndroidSdkInfo (logger, androidSdkPath: sdk, androidNdkPath: ndk, javaSdkPath: jdk); + var versions = new AndroidVersions (new [] { + new AndroidVersion (37, "17.0"), + }); + + // Requesting "37" should fall back to android-37.0 + var dir37 = info.TryGetPlatformDirectoryFromApiLevel ("37", versions); + Assert.IsNotNull (dir37, "Should fall back from android-37 to android-37.0"); + Assert.AreEqual (platform370Path, dir37); + + // IsPlatformInstalled should also find android-37.0 + Assert.IsTrue (info.IsPlatformInstalled (37), "IsPlatformInstalled should find android-37.0"); + } + finally { + Directory.Delete (root, recursive: true); + } + } + [Test] public void GetBuildToolsPaths_StableVersionsFirst () { From 82357af5f4353a335bd29554eb4741dbde46607c Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Tue, 14 Apr 2026 08:15:24 -0500 Subject: [PATCH 263/308] [Build.Tasks] Validate extracted zip entry paths in `Files.ExtractAll()` (#328) Ensure resolved output paths stay within the destination directory before extracting zip entries. This is just for correctness; there's no security enforcement here. Entries with path traversal (e.g. via modifyCallback introducing '../') are skipped with a warning logged via the new `TaskLoggingHelper` parameter. Note that libZipSharp already normalizes entry names on read: var stream = new MemoryStream(); using (var sysZip = new System.IO.Compression.ZipArchive(stream, System.IO.Compression.ZipArchiveMode.Create, leaveOpen: true)) { sysZip.CreateEntry("../evil.txt"); } stream.Position = 0; using (var zip = Xamarin.Tools.Zip.ZipArchive.Open(stream)) { foreach (var entry in zip) Console.WriteLine(entry.FullName); // prints 'evil.txt' } The original overload without `TaskLoggingHelper` is preserved and marked `[Obsolete]` to maintain binary compatibility. --- .../Files.cs | 14 +++- .../FilesTests.cs | 70 +++++++++++++++++++ 2 files changed, 83 insertions(+), 1 deletion(-) diff --git a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Files.cs b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Files.cs index 77b6e313311..709ee515242 100644 --- a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Files.cs +++ b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Files.cs @@ -441,14 +441,22 @@ public static bool ZipAny (string filename, Func filter) } } + [Obsolete ("Use the overload that accepts a TaskLoggingHelper parameter.")] + public static bool ExtractAll (ZipArchive zip, string destination, Action progressCallback, + Func modifyCallback, Func deleteCallback, Func skipCallback) + { + return ExtractAll (zip, destination, progressCallback, modifyCallback, deleteCallback, skipCallback, log: null); + } + public static bool ExtractAll (ZipArchive zip, string destination, Action progressCallback = null, Func modifyCallback = null, - Func deleteCallback = null, Func skipCallback = null) + Func deleteCallback = null, Func skipCallback = null, TaskLoggingHelper log = null) { int i = 0; int total = (int)zip.EntryCount; bool updated = false; var files = new HashSet (); var memoryStream = MemoryStreamPool.Shared.Rent (); + var fullDestination = Path.GetFullPath (destination + Path.DirectorySeparatorChar); try { foreach (var entry in zip) { progressCallback?.Invoke (i++, total); @@ -463,6 +471,10 @@ public static bool ExtractAll (ZipArchive zip, string destination, Action "../" + e); + Assert.IsFalse (changes, "ExtractAll should not report changes for skipped entries."); + } + FileAssert.DoesNotExist (Path.Combine (tempDir, "a.txt")); + } + + [Test] + public void ExtractAll_SkipsPathTraversal_ExtractsValidEntries () + { + using (var zip = ZipArchive.Create (stream)) { + zip.AddEntry ("good.txt", "good", encoding); + zip.AddEntry ("evil.txt", "evil", encoding); + } + + var destinationDir = Path.Combine (tempDir, "dest"); + stream.Position = 0; + using (var zip = ZipArchive.Open (stream)) { + // Only evil.txt gets a traversal prefix + bool changes = Files.ExtractAll (zip, destinationDir, modifyCallback: e => + e == "evil.txt" ? "../" + e : e); + Assert.IsTrue (changes, "ExtractAll should report changes for the valid entry."); + } + AssertFile (Path.Combine ("dest", "good.txt"), "good"); + FileAssert.DoesNotExist (Path.Combine (tempDir, "evil.txt")); + } + + [TestCase ("../../")] + [TestCase ("foo/../../../")] + public void ExtractAll_SkipsPathTraversal_ForwardSlash (string prefix) + { + using (var zip = ZipArchive.Create (stream)) { + zip.AddEntry ("a.txt", "a", encoding); + } + + var destinationDir = Path.Combine (tempDir, "dest"); + stream.Position = 0; + using (var zip = ZipArchive.Open (stream)) { + bool changes = Files.ExtractAll (zip, destinationDir, modifyCallback: e => prefix + e); + Assert.IsFalse (changes, $"Entry with prefix '{prefix}' should be skipped."); + } + } + + [TestCase ("..\\")] + [TestCase ("..\\..\\")] + [Platform ("Win")] + public void ExtractAll_SkipsPathTraversal_BackSlash (string prefix) + { + using (var zip = ZipArchive.Create (stream)) { + zip.AddEntry ("a.txt", "a", encoding); + } + + var destinationDir = Path.Combine (tempDir, "dest"); + stream.Position = 0; + using (var zip = ZipArchive.Open (stream)) { + bool changes = Files.ExtractAll (zip, destinationDir, modifyCallback: e => prefix + e); + Assert.IsFalse (changes, $"Entry with prefix '{prefix}' should be skipped."); + } + } + [Test] public void ToHashString () { From be78c1daa878a30f91416090b85ba71355eb84f0 Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Tue, 14 Apr 2026 10:33:38 -0500 Subject: [PATCH 264/308] [Build.Tasks] Use `LogDebugMessage` instead of `LogWarning` in `ExtractAll()` (#329) The path-traversal skip message in Files.ExtractAll() used LogWarning, but this is a diagnostic/informational message that should use LogDebugMessage (MessageImportance.Low) instead. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../src/Microsoft.Android.Build.BaseTasks/Files.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Files.cs b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Files.cs index 709ee515242..439363665d3 100644 --- a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Files.cs +++ b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Files.cs @@ -472,7 +472,7 @@ public static bool ExtractAll (ZipArchive zip, string destination, Action Date: Wed, 22 Apr 2026 20:03:03 +0200 Subject: [PATCH 265/308] [test] Rename test cases to `relative.txt` in test files (#333) This is just for correctness; there's no security enforcement here. Renames to `relative.txt` in path-traversal test entries in `FilesTests.cs` and `DownloadUtilsTests.cs` to avoid any misperception of a security issue. Co-authored-by: Jonathan Peppers --- .../Microsoft.Android.Build.BaseTasks-Tests/FilesTests.cs | 8 ++++---- .../DownloadUtilsTests.cs | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/external/xamarin-android-tools/tests/Microsoft.Android.Build.BaseTasks-Tests/FilesTests.cs b/external/xamarin-android-tools/tests/Microsoft.Android.Build.BaseTasks-Tests/FilesTests.cs index b0341370c6f..54678ca2c76 100644 --- a/external/xamarin-android-tools/tests/Microsoft.Android.Build.BaseTasks-Tests/FilesTests.cs +++ b/external/xamarin-android-tools/tests/Microsoft.Android.Build.BaseTasks-Tests/FilesTests.cs @@ -647,19 +647,19 @@ public void ExtractAll_SkipsPathTraversal_ExtractsValidEntries () { using (var zip = ZipArchive.Create (stream)) { zip.AddEntry ("good.txt", "good", encoding); - zip.AddEntry ("evil.txt", "evil", encoding); + zip.AddEntry ("relative.txt", "relative", encoding); } var destinationDir = Path.Combine (tempDir, "dest"); stream.Position = 0; using (var zip = ZipArchive.Open (stream)) { - // Only evil.txt gets a traversal prefix + // Only relative.txt gets a traversal prefix bool changes = Files.ExtractAll (zip, destinationDir, modifyCallback: e => - e == "evil.txt" ? "../" + e : e); + e == "relative.txt" ? "../" + e : e); Assert.IsTrue (changes, "ExtractAll should report changes for the valid entry."); } AssertFile (Path.Combine ("dest", "good.txt"), "good"); - FileAssert.DoesNotExist (Path.Combine (tempDir, "evil.txt")); + FileAssert.DoesNotExist (Path.Combine (tempDir, "relative.txt")); } [TestCase ("../../")] diff --git a/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/DownloadUtilsTests.cs b/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/DownloadUtilsTests.cs index 995870df252..67b05125436 100644 --- a/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/DownloadUtilsTests.cs +++ b/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/DownloadUtilsTests.cs @@ -172,7 +172,7 @@ public void ExtractZipSafe_ZipSlip_Throws () using (var archive = ZipFile.Open (zipPath, ZipArchiveMode.Create)) { // Create an entry with a path traversal - var entry = archive.CreateEntry ("../evil.txt"); + var entry = archive.CreateEntry ("../relative.txt"); using var writer = new StreamWriter (entry.Open ()); writer.Write ("malicious"); } From 5aba236cece80573ea8b5d0af97dae7bdfb3d3eb Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Thu, 23 Apr 2026 09:56:53 -0500 Subject: [PATCH 266/308] [tests] Add BenchmarkDotNet project for `Files` hash APIs (#334) Add tests/Xamarin.Android.Tools.Benchmarks with benchmarks for HashBytes, HashStream, HashFile, and HasFileChanged using the existing Crc64-based hashing in Files.cs. ### Add scripts/benchmarks.ps1 to run Files hash benchmarks Builds in Release, runs BenchmarkDotNet with GitHub markdown exporter, and copies the report to tests/Xamarin.Android.Tools.Benchmarks/README.md. - Add BenchmarkDotNet.Artifacts to .gitignore - Add benchmark results README Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- external/xamarin-android-tools/.gitignore | 1 + .../Directory.Build.targets | 1 + .../Xamarin.Android.Tools.sln | 6 ++ .../scripts/benchmarks.ps1 | 37 +++++++++++++ .../FilesHashBenchmarks.cs | 55 +++++++++++++++++++ .../Program.cs | 3 + .../README.md | 16 ++++++ .../Xamarin.Android.Tools.Benchmarks.csproj | 20 +++++++ 8 files changed, 139 insertions(+) create mode 100644 external/xamarin-android-tools/scripts/benchmarks.ps1 create mode 100644 external/xamarin-android-tools/tests/Xamarin.Android.Tools.Benchmarks/FilesHashBenchmarks.cs create mode 100644 external/xamarin-android-tools/tests/Xamarin.Android.Tools.Benchmarks/Program.cs create mode 100644 external/xamarin-android-tools/tests/Xamarin.Android.Tools.Benchmarks/README.md create mode 100644 external/xamarin-android-tools/tests/Xamarin.Android.Tools.Benchmarks/Xamarin.Android.Tools.Benchmarks.csproj diff --git a/external/xamarin-android-tools/.gitignore b/external/xamarin-android-tools/.gitignore index aae837edfc5..09939c2b0d7 100644 --- a/external/xamarin-android-tools/.gitignore +++ b/external/xamarin-android-tools/.gitignore @@ -15,3 +15,4 @@ TestResult-*.xml **/TestResults/*.trx .vs/ *.nupkg +BenchmarkDotNet.Artifacts/ diff --git a/external/xamarin-android-tools/Directory.Build.targets b/external/xamarin-android-tools/Directory.Build.targets index 36b61edf1ee..16a3a380ef8 100644 --- a/external/xamarin-android-tools/Directory.Build.targets +++ b/external/xamarin-android-tools/Directory.Build.targets @@ -27,6 +27,7 @@ + diff --git a/external/xamarin-android-tools/Xamarin.Android.Tools.sln b/external/xamarin-android-tools/Xamarin.Android.Tools.sln index 13ebd9aece8..ed193ca9617 100644 --- a/external/xamarin-android-tools/Xamarin.Android.Tools.sln +++ b/external/xamarin-android-tools/Xamarin.Android.Tools.sln @@ -14,6 +14,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tools", "tools", "{19FE1B44 EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ls-jdks", "tools\ls-jdks\ls-jdks.csproj", "{3D2ADF77-62C7-4E0E-AB29-BDD266B7D56D}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Xamarin.Android.Tools.Benchmarks", "tests\Xamarin.Android.Tools.Benchmarks\Xamarin.Android.Tools.Benchmarks.csproj", "{F1A2B3C4-D5E6-4F7A-8B9C-0D1E2F3A4B5C}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -40,6 +42,10 @@ Global {3D2ADF77-62C7-4E0E-AB29-BDD266B7D56D}.Debug|Any CPU.Build.0 = Debug|Any CPU {3D2ADF77-62C7-4E0E-AB29-BDD266B7D56D}.Release|Any CPU.ActiveCfg = Release|Any CPU {3D2ADF77-62C7-4E0E-AB29-BDD266B7D56D}.Release|Any CPU.Build.0 = Release|Any CPU + {F1A2B3C4-D5E6-4F7A-8B9C-0D1E2F3A4B5C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F1A2B3C4-D5E6-4F7A-8B9C-0D1E2F3A4B5C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F1A2B3C4-D5E6-4F7A-8B9C-0D1E2F3A4B5C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F1A2B3C4-D5E6-4F7A-8B9C-0D1E2F3A4B5C}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/external/xamarin-android-tools/scripts/benchmarks.ps1 b/external/xamarin-android-tools/scripts/benchmarks.ps1 new file mode 100644 index 00000000000..9c413e77f9c --- /dev/null +++ b/external/xamarin-android-tools/scripts/benchmarks.ps1 @@ -0,0 +1,37 @@ +<# +.SYNOPSIS + Runs the Files hash benchmarks and produces a markdown report. + +.DESCRIPTION + Builds and runs Xamarin.Android.Tools.Benchmarks in Release mode, + then copies the GitHub-flavored markdown results to + tests/Xamarin.Android.Tools.Benchmarks/README.md. +#> + +$ErrorActionPreference = 'Stop' +$repoRoot = Split-Path -Parent $PSScriptRoot +$projectDir = Join-Path (Join-Path $repoRoot 'tests') 'Xamarin.Android.Tools.Benchmarks' +$csproj = Join-Path $projectDir 'Xamarin.Android.Tools.Benchmarks.csproj' +$artifactsDir = Join-Path $projectDir 'BenchmarkDotNet.Artifacts' +$readme = Join-Path $projectDir 'README.md' + +Write-Host "Building benchmarks in Release..." +dotnet build $csproj -c Release --nologo -v quiet +if ($LASTEXITCODE -ne 0) { throw "Build failed." } + +Write-Host "Running benchmarks..." +dotnet run --project $csproj -c Release --no-build -- --filter '*' --exporters github --artifacts $artifactsDir +if ($LASTEXITCODE -ne 0) { throw "Benchmarks failed." } + +# Find the generated markdown report +$mdFile = Get-ChildItem -Path (Join-Path $artifactsDir 'results') -Filter '*-report-github.md' | + Sort-Object -Property LastWriteTime -Descending | + Select-Object -First 1 +if (-not $mdFile) { throw "No markdown report found in $artifactsDir\results" } + +Copy-Item $mdFile.FullName $readme -Force +Write-Host "Results written to: $readme" + +# Clean up artifacts +Remove-Item $artifactsDir -Recurse -Force +Write-Host "Cleaned up BenchmarkDotNet.Artifacts." diff --git a/external/xamarin-android-tools/tests/Xamarin.Android.Tools.Benchmarks/FilesHashBenchmarks.cs b/external/xamarin-android-tools/tests/Xamarin.Android.Tools.Benchmarks/FilesHashBenchmarks.cs new file mode 100644 index 00000000000..5c119144ca4 --- /dev/null +++ b/external/xamarin-android-tools/tests/Xamarin.Android.Tools.Benchmarks/FilesHashBenchmarks.cs @@ -0,0 +1,55 @@ +using System; +using System.IO; +using BenchmarkDotNet.Attributes; +using Microsoft.Android.Build.Tasks; + +namespace Xamarin.Android.Tools.Benchmarks; + +[MemoryDiagnoser] +public class FilesHashBenchmarks +{ + const int OneMB = 1024 * 1024; + + byte [] _data = Array.Empty (); + MemoryStream _stream = new MemoryStream (); + string _tempFile1 = string.Empty; + string _tempFile2 = string.Empty; + + [GlobalSetup] + public void Setup () + { + // 1MB byte array with reproducible random data + _data = new byte [OneMB]; + new Random (42).NextBytes (_data); + _stream.Dispose (); + _stream = new MemoryStream (_data); + + // Two identical 1MB temp files + _tempFile1 = Path.GetTempFileName (); + _tempFile2 = Path.GetTempFileName (); + File.WriteAllBytes (_tempFile1, _data); + File.WriteAllBytes (_tempFile2, _data); + } + + [GlobalCleanup] + public void Cleanup () + { + _stream?.Dispose (); + if (File.Exists (_tempFile1)) + File.Delete (_tempFile1); + if (File.Exists (_tempFile2)) + File.Delete (_tempFile2); + } + + [Benchmark] + public string HashBytes () => Files.HashBytes (_data); + + [Benchmark] + public string HashStream () => Files.HashStream (_stream); + + [Benchmark] + public string HashFile () => Files.HashFile (_tempFile1); + + [Benchmark] + public bool HasFileChanged () => Files.HasFileChanged (_tempFile1, _tempFile2); +} diff --git a/external/xamarin-android-tools/tests/Xamarin.Android.Tools.Benchmarks/Program.cs b/external/xamarin-android-tools/tests/Xamarin.Android.Tools.Benchmarks/Program.cs new file mode 100644 index 00000000000..7da2c3c4580 --- /dev/null +++ b/external/xamarin-android-tools/tests/Xamarin.Android.Tools.Benchmarks/Program.cs @@ -0,0 +1,3 @@ +using BenchmarkDotNet.Running; + +BenchmarkSwitcher.FromAssembly (typeof (Program).Assembly).Run (args); diff --git a/external/xamarin-android-tools/tests/Xamarin.Android.Tools.Benchmarks/README.md b/external/xamarin-android-tools/tests/Xamarin.Android.Tools.Benchmarks/README.md new file mode 100644 index 00000000000..dc38a4d615d --- /dev/null +++ b/external/xamarin-android-tools/tests/Xamarin.Android.Tools.Benchmarks/README.md @@ -0,0 +1,16 @@ +``` + +BenchmarkDotNet v0.14.0, Windows 11 (10.0.26200.8246) +Intel Core i9-14900KF, 1 CPU, 32 logical and 24 physical cores +.NET SDK 10.0.202 + [Host] : .NET 10.0.6 (10.0.626.17701), X64 RyuJIT AVX2 + DefaultJob : .NET 10.0.6 (10.0.626.17701), X64 RyuJIT AVX2 + + +``` +| Method | Mean | Error | StdDev | Allocated | +|--------------- |-----------:|--------:|--------:|----------:| +| HashBytes | 468.2 μs | 2.01 μs | 1.57 μs | 232 B | +| HashStream | 482.9 μs | 7.47 μs | 6.99 μs | 232 B | +| HashFile | 764.9 μs | 8.08 μs | 7.56 μs | 474 B | +| HasFileChanged | 1,496.6 μs | 5.74 μs | 5.08 μs | 945 B | diff --git a/external/xamarin-android-tools/tests/Xamarin.Android.Tools.Benchmarks/Xamarin.Android.Tools.Benchmarks.csproj b/external/xamarin-android-tools/tests/Xamarin.Android.Tools.Benchmarks/Xamarin.Android.Tools.Benchmarks.csproj new file mode 100644 index 00000000000..0fd2ed52a06 --- /dev/null +++ b/external/xamarin-android-tools/tests/Xamarin.Android.Tools.Benchmarks/Xamarin.Android.Tools.Benchmarks.csproj @@ -0,0 +1,20 @@ + + + + Exe + $(DotNetTargetFramework) + false + $(TestOutputFullPath) + false + Major + + + + + + + + + + + From c022501340049c5e8148cc699040ed77dce1602d Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Thu, 23 Apr 2026 10:57:40 -0500 Subject: [PATCH 267/308] [BaseTasks] Replace `Crc64` with `XxHash64` in `Files` class (#335) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replaces the software `Crc64` implementation with hardware-accelerated `XxHash64` from `System.IO.Hashing` 10.0.7 for all hashing in the `Files` class (`HashBytes`, `HashFile`, `HashStream`). The `Crc64` class is retained — only its usage in `Files` is replaced. ## Changes - **`Files.cs`**: `HashBytes`, `HashFile(string)`, and `HashStream` now use `XxHash64` instead of `Crc64`. Uses `stackalloc` with Span-based APIs for zero-allocation hashing. `HashFile(string, HashAlgorithm)` overload is unchanged for backward compat. - **`Files.cs`**: `ToHexString(ReadOnlySpan)` is now `public`. Uses `stackalloc` for small hashes (≤64 bytes) to avoid `char[]` allocation. The `byte[]` overload delegates to it. - **`Files.cs`**: `HashFile` overloads simplified to use `File.OpenRead()`. - **`Microsoft.Android.Build.BaseTasks.csproj`**: Added `System.IO.Hashing` package reference. - **`Directory.Build.targets`**: Pinned `System.IO.Hashing` version to 10.0.7 (centralized, matching other packages). - **`FilesTests.cs`**: Fixed pre-existing bug where tests reassigned `tempDir` from a directory to a file path, causing `TearDown` to skip cleanup. All tests now use `tempDir` as a directory and write to local `tempFile` variables inside it. ## Benchmark results (1 MB data, Intel i9-14900KF) | Method | Before (Crc64) | Alloc | After (XxHash64) | Alloc | Speedup | |--------------- |---------------:|------:|------------------:|------:|--------:| | HashBytes | 468.2 μs | 232 B | 46.00 μs | 56 B | ~10x | | HashStream | 482.9 μs | 232 B | 44.98 μs | 184 B | ~11x | | HashFile | 764.9 μs | 474 B | 75.83 μs | 424 B | ~10x | | HasFileChanged | 1,496.6 μs | 945 B | 163.23 μs | 848 B | ~9x | Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Directory.Build.targets | 1 + .../Files.cs | 49 +++++--- .../Microsoft.Android.Build.BaseTasks.csproj | 1 + .../FilesTests.cs | 112 +++++++++++------- .../README.md | 12 +- 5 files changed, 109 insertions(+), 66 deletions(-) diff --git a/external/xamarin-android-tools/Directory.Build.targets b/external/xamarin-android-tools/Directory.Build.targets index 16a3a380ef8..56abd64c2b8 100644 --- a/external/xamarin-android-tools/Directory.Build.targets +++ b/external/xamarin-android-tools/Directory.Build.targets @@ -28,6 +28,7 @@ + diff --git a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Files.cs b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Files.cs index 439363665d3..5e0549a92ce 100644 --- a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Files.cs +++ b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Files.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.IO.Hashing; using System.Linq; using System.Security.Cryptography; using System.Text; @@ -25,6 +26,8 @@ public static class Files const int DEFAULT_FILE_WRITE_RETRY_DELAY_MS = 1000; + const int XXHASH64_SIZE_IN_BYTES = 8; + static int fileWriteRetry = -1; static int fileWriteRetryDelay = -1; @@ -531,22 +534,25 @@ public static string HashString (string s) public static string HashBytes (byte [] bytes) { - using (HashAlgorithm hashAlg = new Crc64 ()) { - byte [] hash = hashAlg.ComputeHash (bytes); - return ToHexString (hash); - } + Span hash = stackalloc byte[XXHASH64_SIZE_IN_BYTES]; + XxHash64.Hash (bytes, hash); + return ToHexString (hash); } public static string HashFile (string filename) { - using (HashAlgorithm hashAlg = new Crc64 ()) { - return HashFile (filename, hashAlg); + var hasher = new XxHash64 (); + using (var file = File.OpenRead (filename)) { + hasher.Append (file); } + Span hash = stackalloc byte[XXHASH64_SIZE_IN_BYTES]; + hasher.GetCurrentHash (hash); + return ToHexString (hash); } public static string HashFile (string filename, HashAlgorithm hashAlg) { - using (Stream file = new FileStream (filename, FileMode.Open, FileAccess.Read, FileShare.Read)) { + using (var file = File.OpenRead (filename)) { byte[] hash = hashAlg.ComputeHash (file); return ToHexString (hash); } @@ -555,22 +561,33 @@ public static string HashFile (string filename, HashAlgorithm hashAlg) public static string HashStream (Stream stream) { stream.Position = 0; - - using (HashAlgorithm hashAlg = new Crc64 ()) { - byte[] hash = hashAlg.ComputeHash (stream); - return ToHexString (hash); - } + var hasher = new XxHash64 (); + hasher.Append (stream); + Span hash = stackalloc byte[XXHASH64_SIZE_IN_BYTES]; + hasher.GetCurrentHash (hash); + return ToHexString (hash); } public static string ToHexString (byte[] hash) { - char [] array = new char [hash.Length * 2]; + if (hash == null) + throw new ArgumentNullException (nameof (hash)); + return ToHexString ((ReadOnlySpan) hash); + } + + public static string ToHexString (ReadOnlySpan hash) + { + const int MaxStackCharLength = 128; + int charLength = hash.Length * 2; + Span chars = charLength <= MaxStackCharLength + ? stackalloc char[charLength] + : new char[charLength]; for (int i = 0, j = 0; i < hash.Length; i += 1, j += 2) { byte b = hash [i]; - array [j] = GetHexValue (b / 16); - array [j + 1] = GetHexValue (b % 16); + chars [j] = GetHexValue (b / 16); + chars [j + 1] = GetHexValue (b % 16); } - return new string (array); + return ((ReadOnlySpan) chars).ToString (); } static char GetHexValue (int i) => (char) (i < 10 ? i + 48 : i - 10 + 65); diff --git a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Microsoft.Android.Build.BaseTasks.csproj b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Microsoft.Android.Build.BaseTasks.csproj index 7787a299e7e..6e5cfe6fef3 100644 --- a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Microsoft.Android.Build.BaseTasks.csproj +++ b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Microsoft.Android.Build.BaseTasks.csproj @@ -26,6 +26,7 @@ + diff --git a/external/xamarin-android-tools/tests/Microsoft.Android.Build.BaseTasks-Tests/FilesTests.cs b/external/xamarin-android-tools/tests/Microsoft.Android.Build.BaseTasks-Tests/FilesTests.cs index 54678ca2c76..b1e50e1f1a7 100644 --- a/external/xamarin-android-tools/tests/Microsoft.Android.Build.BaseTasks-Tests/FilesTests.cs +++ b/external/xamarin-android-tools/tests/Microsoft.Android.Build.BaseTasks-Tests/FilesTests.cs @@ -43,89 +43,104 @@ public void TearDown () [Test] public void CopyIfStringChanged () { + Directory.CreateDirectory (tempDir); + var tempFile = Path.Combine (tempDir, "foo.txt"); + var foo = "bar"; - Assert.IsTrue (Files.CopyIfStringChanged (foo, tempDir), "Should write on new file."); - FileAssert.Exists (tempDir); - Assert.IsFalse (Files.CopyIfStringChanged (foo, tempDir), "Should *not* write unless changed."); + Assert.IsTrue (Files.CopyIfStringChanged (foo, tempFile), "Should write on new file."); + FileAssert.Exists (tempFile); + Assert.IsFalse (Files.CopyIfStringChanged (foo, tempFile), "Should *not* write unless changed."); foo += "\n"; - Assert.IsTrue (Files.CopyIfStringChanged (foo, tempDir), "Should write when changed."); + Assert.IsTrue (Files.CopyIfStringChanged (foo, tempFile), "Should write when changed."); } [Test] public void CopyIfBytesChanged () { + Directory.CreateDirectory (tempDir); + var tempFile = Path.Combine (tempDir, "foo.bin"); + var foo = new byte [32]; - Assert.IsTrue (Files.CopyIfBytesChanged (foo, tempDir), "Should write on new file."); - FileAssert.Exists (tempDir); - Assert.IsFalse (Files.CopyIfBytesChanged (foo, tempDir), "Should *not* write unless changed."); + Assert.IsTrue (Files.CopyIfBytesChanged (foo, tempFile), "Should write on new file."); + FileAssert.Exists (tempFile); + Assert.IsFalse (Files.CopyIfBytesChanged (foo, tempFile), "Should *not* write unless changed."); foo [0] = 0xFF; - Assert.IsTrue (Files.CopyIfBytesChanged (foo, tempDir), "Should write when changed."); + Assert.IsTrue (Files.CopyIfBytesChanged (foo, tempFile), "Should write when changed."); } [Test] public void CopyIfStreamChanged () { + Directory.CreateDirectory (tempDir); + var tempFile = Path.Combine (tempDir, "foo.txt"); + using (var foo = new MemoryStream ()) using (var writer = new StreamWriter (foo)) { writer.WriteLine ("bar"); writer.Flush (); - Assert.IsTrue (Files.CopyIfStreamChanged (foo, tempDir), "Should write on new file."); - FileAssert.Exists (tempDir); - Assert.IsFalse (Files.CopyIfStreamChanged (foo, tempDir), "Should *not* write unless changed."); + Assert.IsTrue (Files.CopyIfStreamChanged (foo, tempFile), "Should write on new file."); + FileAssert.Exists (tempFile); + Assert.IsFalse (Files.CopyIfStreamChanged (foo, tempFile), "Should *not* write unless changed."); writer.WriteLine (); writer.Flush (); - Assert.IsTrue (Files.CopyIfStreamChanged (foo, tempDir), "Should write when changed."); + Assert.IsTrue (Files.CopyIfStreamChanged (foo, tempFile), "Should write when changed."); } } [Test] public void CopyIfStringChanged_NewDirectory () { - tempDir = Path.Combine (tempDir, "foo.txt"); + Directory.CreateDirectory (tempDir); + var tempFile = Path.Combine (tempDir, "foo.txt"); var foo = "bar"; - Assert.IsTrue (Files.CopyIfStringChanged (foo, tempDir), "Should write on new file."); - FileAssert.Exists (tempDir); + Assert.IsTrue (Files.CopyIfStringChanged (foo, tempFile), "Should write on new file."); + FileAssert.Exists (tempFile); } [Test] public void CopyIfBytesChanged_NewDirectory () { - tempDir = Path.Combine (tempDir, "foo.bin"); + Directory.CreateDirectory (tempDir); + var tempFile = Path.Combine (tempDir, "foo.bin"); var foo = new byte [32]; - Assert.IsTrue (Files.CopyIfBytesChanged (foo, tempDir), "Should write on new file."); - FileAssert.Exists (tempDir); + Assert.IsTrue (Files.CopyIfBytesChanged (foo, tempFile), "Should write on new file."); + FileAssert.Exists (tempFile); } [Test] public void CopyIfStreamChanged_NewDirectory () { - tempDir = Path.Combine (tempDir, "foo.txt"); + Directory.CreateDirectory (tempDir); + var tempFile = Path.Combine (tempDir, "foo.txt"); using (var foo = new MemoryStream ()) using (var writer = new StreamWriter (foo)) { writer.WriteLine ("bar"); writer.Flush (); - Assert.IsTrue (Files.CopyIfStreamChanged (foo, tempDir), "Should write on new file."); - FileAssert.Exists (tempDir); + Assert.IsTrue (Files.CopyIfStreamChanged (foo, tempFile), "Should write on new file."); + FileAssert.Exists (tempFile); } } [Test] public void CopyIfBytesChanged_Readonly () { - if (File.Exists (tempDir)) { - File.SetAttributes (tempDir, FileAttributes.Normal); + Directory.CreateDirectory (tempDir); + var tempFile = Path.Combine (tempDir, "foo.bin"); + + if (File.Exists (tempFile)) { + File.SetAttributes (tempFile, FileAttributes.Normal); } - File.WriteAllText (tempDir, ""); - File.SetAttributes (tempDir, FileAttributes.ReadOnly); + File.WriteAllText (tempFile, ""); + File.SetAttributes (tempFile, FileAttributes.ReadOnly); var foo = new byte [32]; - Assert.IsTrue (Files.CopyIfBytesChanged (foo, tempDir), "Should write on new file."); - FileAssert.Exists (tempDir); + Assert.IsTrue (Files.CopyIfBytesChanged (foo, tempFile), "Should write on new file."); + FileAssert.Exists (tempFile); } [Test] @@ -133,18 +148,18 @@ public void CleanBOM_Readonly () { var encoding = Encoding.UTF8; Directory.CreateDirectory (tempDir); - tempDir = Path.Combine (tempDir, "foo.txt"); - if (File.Exists (tempDir)) { - File.SetAttributes (tempDir, FileAttributes.Normal); + var tempFile = Path.Combine (tempDir, "foo.txt"); + if (File.Exists (tempFile)) { + File.SetAttributes (tempFile, FileAttributes.Normal); } - using (var stream = File.Create (tempDir)) + using (var stream = File.Create (tempFile)) using (var writer = new StreamWriter (stream, encoding)) { writer.Write ("This will have a BOM"); } - File.SetAttributes (tempDir, FileAttributes.ReadOnly); - var before = File.ReadAllBytes (tempDir); - Files.CleanBOM (tempDir); - var after = File.ReadAllBytes (tempDir); + File.SetAttributes (tempFile, FileAttributes.ReadOnly); + var before = File.ReadAllBytes (tempFile); + Files.CleanBOM (tempFile); + var after = File.ReadAllBytes (tempFile); var preamble = encoding.GetPreamble (); Assert.AreEqual (before.Length, after.Length + preamble.Length, "BOM should be removed!"); } @@ -152,6 +167,9 @@ public void CleanBOM_Readonly () [Test] public void CopyIfStreamChanged_MemoryStreamPool_StreamWriter () { + Directory.CreateDirectory (tempDir); + var tempFile = Path.Combine (tempDir, "foo.txt"); + var pool = new MemoryStreamPool (); var expected = pool.Rent (); pool.Return (expected); @@ -160,8 +178,8 @@ public void CopyIfStreamChanged_MemoryStreamPool_StreamWriter () writer.WriteLine ("bar"); writer.Flush (); - Assert.IsTrue (Files.CopyIfStreamChanged (writer.BaseStream, tempDir), "Should write on new file."); - FileAssert.Exists (tempDir); + Assert.IsTrue (Files.CopyIfStreamChanged (writer.BaseStream, tempFile), "Should write on new file."); + FileAssert.Exists (tempFile); } var actual = pool.Rent (); @@ -172,6 +190,9 @@ public void CopyIfStreamChanged_MemoryStreamPool_StreamWriter () [Test] public void CopyIfStreamChanged_MemoryStreamPool_BinaryWriter () { + Directory.CreateDirectory (tempDir); + var tempFile = Path.Combine (tempDir, "foo.bin"); + var pool = new MemoryStreamPool (); var expected = pool.Rent (); pool.Return (expected); @@ -180,8 +201,8 @@ public void CopyIfStreamChanged_MemoryStreamPool_BinaryWriter () writer.Write (42); writer.Flush (); - Assert.IsTrue (Files.CopyIfStreamChanged (writer.BaseStream, tempDir), "Should write on new file."); - FileAssert.Exists (tempDir); + Assert.IsTrue (Files.CopyIfStreamChanged (writer.BaseStream, tempFile), "Should write on new file."); + FileAssert.Exists (tempFile); } var actual = pool.Rent (); @@ -192,14 +213,17 @@ public void CopyIfStreamChanged_MemoryStreamPool_BinaryWriter () [Test] public void SetWriteable () { - File.WriteAllText (tempDir, contents: "foo"); - File.SetAttributes (tempDir, FileAttributes.ReadOnly); + Directory.CreateDirectory (tempDir); + var tempFile = Path.Combine (tempDir, "foo.txt"); + + File.WriteAllText (tempFile, contents: "foo"); + File.SetAttributes (tempFile, FileAttributes.ReadOnly); - Files.SetWriteable (tempDir); + Files.SetWriteable (tempFile); - var attributes = File.GetAttributes (tempDir); + var attributes = File.GetAttributes (tempFile); Assert.AreEqual (FileAttributes.Normal, attributes); - File.WriteAllText (tempDir, contents: "bar"); + File.WriteAllText (tempFile, contents: "bar"); } [Test] diff --git a/external/xamarin-android-tools/tests/Xamarin.Android.Tools.Benchmarks/README.md b/external/xamarin-android-tools/tests/Xamarin.Android.Tools.Benchmarks/README.md index dc38a4d615d..96543b4666a 100644 --- a/external/xamarin-android-tools/tests/Xamarin.Android.Tools.Benchmarks/README.md +++ b/external/xamarin-android-tools/tests/Xamarin.Android.Tools.Benchmarks/README.md @@ -8,9 +8,9 @@ Intel Core i9-14900KF, 1 CPU, 32 logical and 24 physical cores ``` -| Method | Mean | Error | StdDev | Allocated | -|--------------- |-----------:|--------:|--------:|----------:| -| HashBytes | 468.2 μs | 2.01 μs | 1.57 μs | 232 B | -| HashStream | 482.9 μs | 7.47 μs | 6.99 μs | 232 B | -| HashFile | 764.9 μs | 8.08 μs | 7.56 μs | 474 B | -| HasFileChanged | 1,496.6 μs | 5.74 μs | 5.08 μs | 945 B | +| Method | Mean | Error | StdDev | Allocated | +|--------------- |----------:|---------:|---------:|----------:| +| HashBytes | 46.00 μs | 0.401 μs | 0.375 μs | 56 B | +| HashStream | 44.98 μs | 0.117 μs | 0.104 μs | 184 B | +| HashFile | 75.83 μs | 0.605 μs | 0.566 μs | 424 B | +| HasFileChanged | 163.23 μs | 0.705 μs | 0.589 μs | 848 B | From 18639b7394b2c44030654e73e0aaa4c23c3dc46d Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Mon, 27 Apr 2026 04:19:03 -0500 Subject: [PATCH 268/308] [BaseTasks] Replace `XxHash64` with `System.IO.Hashing.Crc64` in `Files` class (#338) Go *back* to Crc64, but use System.IO.Hashing implementation! ## Benchmark Results | Method | XxHash64 (before) | Crc64 (after) | Speedup | Alloc Delta | |---|--:|--:|--:|--:| | `HashBytes` | 46.00 us | 23.34 us | **~2x faster** | 56 B -> 56 B | | `HashStream` | 44.98 us | 23.07 us | **~2x faster** | 184 B -> 120 B | | `HashFile` | 75.83 us | 52.98 us | **~1.4x faster** | 424 B -> 360 B | | `HasFileChanged` | 163.23 us | 118.44 us | **~1.4x faster** | 848 B -> 720 B | ## Changes Replaced all `XxHash64` usage in `Files.cs` with `System.IO.Hashing.Crc64`: - `HashBytes` -- `XxHash64.Hash()` to `Crc64.Hash()` - `HashFile` -- `new XxHash64()` to `new Crc64()` - `HashStream` -- `new XxHash64()` to `new Crc64()` - Renamed constant `XXHASH64_SIZE_IN_BYTES` to `CRC64_SIZE_IN_BYTES` Used fully-qualified `System.IO.Hashing.Crc64` to avoid ambiguity with the existing `Crc64 : HashAlgorithm` class in the same namespace (which uses a different CRC-64-Jones variant). ### XorLength Standard CRC64 maps all-zero inputs (of any length, including empty) to `0000000000000000`. This caused a hash collision in `HasBytesChanged` when comparing an empty file against zero-filled byte arrays. To prevent this, we XOR the data length into the final hash -- the same technique the repo's custom `Crc64` class uses in its `HashFinal()` method. This ensures that inputs with identical CRC values but different lengths produce distinct hashes. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Files.cs | 32 ++++++++++++++----- .../README.md | 8 ++--- 2 files changed, 28 insertions(+), 12 deletions(-) diff --git a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Files.cs b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Files.cs index 5e0549a92ce..77af2f2ef06 100644 --- a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Files.cs +++ b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Files.cs @@ -4,8 +4,8 @@ using System; using System.Collections.Generic; using System.IO; -using System.IO.Hashing; using System.Linq; +using System.Runtime.CompilerServices; using System.Security.Cryptography; using System.Text; using Xamarin.Tools.Zip; @@ -26,7 +26,8 @@ public static class Files const int DEFAULT_FILE_WRITE_RETRY_DELAY_MS = 1000; - const int XXHASH64_SIZE_IN_BYTES = 8; + // NOTE: System.IO.Hashing.Crc64 produces different output than the Crc64 class in this repo + const int CRC64_SIZE_IN_BYTES = 8; static int fileWriteRetry = -1; static int fileWriteRetryDelay = -1; @@ -534,19 +535,25 @@ public static string HashString (string s) public static string HashBytes (byte [] bytes) { - Span hash = stackalloc byte[XXHASH64_SIZE_IN_BYTES]; - XxHash64.Hash (bytes, hash); + Span hash = stackalloc byte[CRC64_SIZE_IN_BYTES]; + // NOTE: System.IO.Hashing.Crc64 produces different output than the Crc64 class in this repo + System.IO.Hashing.Crc64.Hash (bytes, hash); + XorLength (hash, (ulong) bytes.Length); return ToHexString (hash); } public static string HashFile (string filename) { - var hasher = new XxHash64 (); + // NOTE: System.IO.Hashing.Crc64 produces different output than the Crc64 class in this repo + var hasher = new System.IO.Hashing.Crc64 (); + long length; using (var file = File.OpenRead (filename)) { hasher.Append (file); + length = file.Length; } - Span hash = stackalloc byte[XXHASH64_SIZE_IN_BYTES]; + Span hash = stackalloc byte[CRC64_SIZE_IN_BYTES]; hasher.GetCurrentHash (hash); + XorLength (hash, (ulong) length); return ToHexString (hash); } @@ -561,13 +568,22 @@ public static string HashFile (string filename, HashAlgorithm hashAlg) public static string HashStream (Stream stream) { stream.Position = 0; - var hasher = new XxHash64 (); + // NOTE: System.IO.Hashing.Crc64 produces different output than the Crc64 class in this repo + var hasher = new System.IO.Hashing.Crc64 (); hasher.Append (stream); - Span hash = stackalloc byte[XXHASH64_SIZE_IN_BYTES]; + Span hash = stackalloc byte[CRC64_SIZE_IN_BYTES]; hasher.GetCurrentHash (hash); + XorLength (hash, (ulong) stream.Length); return ToHexString (hash); } + /// XOR the data length into the hash to avoid collisions on zero-filled inputs. + static void XorLength (Span hash, ulong length) + { + ref var crc = ref Unsafe.As (ref hash [0]); + crc ^= length; + } + public static string ToHexString (byte[] hash) { if (hash == null) diff --git a/external/xamarin-android-tools/tests/Xamarin.Android.Tools.Benchmarks/README.md b/external/xamarin-android-tools/tests/Xamarin.Android.Tools.Benchmarks/README.md index 96543b4666a..30da3f69ae6 100644 --- a/external/xamarin-android-tools/tests/Xamarin.Android.Tools.Benchmarks/README.md +++ b/external/xamarin-android-tools/tests/Xamarin.Android.Tools.Benchmarks/README.md @@ -10,7 +10,7 @@ Intel Core i9-14900KF, 1 CPU, 32 logical and 24 physical cores ``` | Method | Mean | Error | StdDev | Allocated | |--------------- |----------:|---------:|---------:|----------:| -| HashBytes | 46.00 μs | 0.401 μs | 0.375 μs | 56 B | -| HashStream | 44.98 μs | 0.117 μs | 0.104 μs | 184 B | -| HashFile | 75.83 μs | 0.605 μs | 0.566 μs | 424 B | -| HasFileChanged | 163.23 μs | 0.705 μs | 0.589 μs | 848 B | +| HashBytes | 23.34 μs | 0.123 μs | 0.115 μs | 56 B | +| HashStream | 23.07 μs | 0.075 μs | 0.070 μs | 120 B | +| HashFile | 52.98 μs | 0.766 μs | 0.716 μs | 360 B | +| HasFileChanged | 118.44 μs | 2.285 μs | 2.138 μs | 720 B | From d558aca3a478a208cc46d5dbde5ec1ef77c83a33 Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Mon, 27 Apr 2026 08:59:49 -0500 Subject: [PATCH 269/308] [JdkInfo] Remove dead code: `GetLibJvmJdks` and `GetLibJvmJdkPaths` (#341) These two methods in `JdkInfo.cs` were never called. `GetKnownSystemJdkInfos` uses `GetJavaAlternativesJdks` for Linux JDK discovery instead, making `GetLibJvmJdks` and `GetLibJvmJdkPaths` dead code. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../JdkInfo.cs | 28 ------------------- 1 file changed, 28 deletions(-) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/JdkInfo.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/JdkInfo.cs index 0da0880a02b..c4429661808 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/JdkInfo.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/JdkInfo.cs @@ -467,34 +467,6 @@ static IEnumerable GetJavaAlternativesJdkPaths () return paths; } - // Linux; Fedora - static IEnumerable GetLibJvmJdks (Action logger) - { - return GetLibJvmJdkPaths () - .Distinct () - .Select (p => TryGetJdkInfo (p, logger, "`ls /usr/lib/jvm/*`")) - .Where (jdk => jdk != null) - .Select (jdk => jdk!) - .OrderByDescending (jdk => jdk, JdkInfoVersionComparer.Default); - } - - static IEnumerable GetLibJvmJdkPaths () - { - if (!OS.IsLinux) { - yield break; - } - - var jvm = "/usr/lib/jvm"; - if (!Directory.Exists (jvm)) - yield break; - - foreach (var jdk in Directory.EnumerateDirectories (jvm)) { - var release = Path.Combine (jdk, "release"); - if (File.Exists (release)) - yield return jdk; - } - } - // Last-ditch fallback! static IEnumerable GetPathEnvironmentJdks (Action logger) { From d17a04511c4cde9ba00235036b06bac795ace453 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Mon, 27 Apr 2026 09:09:37 -0500 Subject: [PATCH 270/308] [JdkInfo] Fix `StringBuilder` concurrency crash in `JdkInfo.GetJavaProperties()` (#337) ProcessUtils.Exec hooks both OutputDataReceived and ErrorDataReceived to the same handler, which fire on separate background threads. The handler accesses shared mutable state (StringBuilder, Dictionary, etc.) without synchronization, causing ArgumentException crashes. ## Reduce lock scope to StringBuilder and fix indentation Narrow the lock to just the output.AppendLine() call since the StringBuilder is the only shared state causing the crash. The property parsing state (props, curKey, foundPS) is only meaningfully modified by stderr data. Lock on the StringBuilder instance directly instead of a separate gate object. Co-authored-by: Jonathan Peppers --- .../src/Xamarin.Android.Tools.AndroidSdk/JdkInfo.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/JdkInfo.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/JdkInfo.cs index c4429661808..f103880483c 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/JdkInfo.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/JdkInfo.cs @@ -279,7 +279,9 @@ static Dictionary> GetJavaProperties (Action Date: Mon, 27 Apr 2026 09:12:16 -0500 Subject: [PATCH 271/308] [BaseTasks] Fix NRE in `DeleteFile` catch block (#342) Use pattern matching (`if (log is TaskLoggingHelper helper)`) instead of an unconditional `as` cast + dereference. When `log` is null or not a TaskLoggingHelper, the original deletion exception is now silently caught instead of being masked by a NullReferenceException. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Microsoft.Android.Build.BaseTasks/Files.cs | 5 +++-- .../FilesTests.cs | 16 ++++++++++++++++ 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Files.cs b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Files.cs index 77af2f2ef06..f6256549126 100644 --- a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Files.cs +++ b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Files.cs @@ -613,8 +613,9 @@ public static void DeleteFile (string filename, object log) try { File.Delete (filename); } catch (Exception ex) { - var helper = log as TaskLoggingHelper; - helper.LogErrorFromException (ex); + if (log is TaskLoggingHelper helper) { + helper.LogErrorFromException (ex); + } } } diff --git a/external/xamarin-android-tools/tests/Microsoft.Android.Build.BaseTasks-Tests/FilesTests.cs b/external/xamarin-android-tools/tests/Microsoft.Android.Build.BaseTasks-Tests/FilesTests.cs index b1e50e1f1a7..b01c1ded6c0 100644 --- a/external/xamarin-android-tools/tests/Microsoft.Android.Build.BaseTasks-Tests/FilesTests.cs +++ b/external/xamarin-android-tools/tests/Microsoft.Android.Build.BaseTasks-Tests/FilesTests.cs @@ -726,5 +726,21 @@ public void ToHashString () var expected = BitConverter.ToString (bytes).Replace ("-", string.Empty); Assert.AreEqual (expected, Files.ToHexString (bytes)); } + + [Test] + public void DeleteFile_NullLog_DoesNotThrow () + { + var path = Path.Combine (tempDir, "directory-instead-of-file"); + Directory.CreateDirectory (path); + Assert.DoesNotThrow (() => Files.DeleteFile (path, null)); + } + + [Test] + public void DeleteFile_NonTaskLoggingHelperLog_DoesNotThrow () + { + var path = Path.Combine (tempDir, "directory-instead-of-file-nontasklog"); + Directory.CreateDirectory (path); + Assert.DoesNotThrow (() => Files.DeleteFile (path, "not a TaskLoggingHelper")); + } } } From 82dce355face55a821659ff31ef30a319fb0a172 Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Tue, 28 Apr 2026 02:44:58 -0500 Subject: [PATCH 272/308] [copilot] Add `/review` GitHub agentic workflow (#346) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Port the android-tools-reviewer from a Copilot CLI skill to a GitHub Agentic Workflow triggered by the `/review` slash command on pull requests. The workflow performs automated code review using the existing review rules and posts findings as inline review comments and a review summary via safe-outputs. Changes: - Create `.github/workflows/android-tools-reviewer.md` with slash_command trigger, read-only permissions, claude-opus-4.6 model, network allowlist, min-integrity: none for external contributors, roles restriction to admin/maintainer/write, and safe-outputs for PR review comments and review submission - Create `.github/agents/agentic-workflows.agent.md` dispatcher - Create `.github/aw/actions-lock.json` for pinned action SHAs - Compile `.github/workflows/android-tools-reviewer.lock.yml` - Update SKILL.md to remove submit_review.cs script workflow and add CI status checking, Copilot-authored PR handling, and direct review posting via safe-outputs - Delete `.github/skills/android-tools-reviewer/scripts/submit_review.cs` (no longer needed — the workflow uses safe-outputs instead of gh api) - Update `copilot-setup-steps.yml` to install gh-aw CLI extension and bump actions/checkout to v6 - Update `.gitattributes` to mark lock files as linguist-generated - Remove stale `submit_review.cs` reference from copilot-instructions.md ## Match full agentic-workflows.agent.md from dotnet/android Add missing sections: Problems This Solves, How to Use, Key Features, Important Notes, and detailed Load when / Use cases per prompt. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- external/xamarin-android-tools/.gitattributes | 4 +- .../.github/agents/agentic-workflows.agent.md | 178 +++ .../.github/aw/actions-lock.json | 9 + .../.github/copilot-instructions.md | 8 - .../skills/android-tools-reviewer/SKILL.md | 53 +- .../scripts/submit_review.cs | 153 -- .../workflows/android-tools-reviewer.lock.yml | 1312 +++++++++++++++++ .../workflows/android-tools-reviewer.md | 59 + .../.github/workflows/copilot-setup-steps.yml | 6 +- 9 files changed, 1587 insertions(+), 195 deletions(-) create mode 100644 external/xamarin-android-tools/.github/agents/agentic-workflows.agent.md create mode 100644 external/xamarin-android-tools/.github/aw/actions-lock.json delete mode 100644 external/xamarin-android-tools/.github/skills/android-tools-reviewer/scripts/submit_review.cs create mode 100644 external/xamarin-android-tools/.github/workflows/android-tools-reviewer.lock.yml create mode 100644 external/xamarin-android-tools/.github/workflows/android-tools-reviewer.md diff --git a/external/xamarin-android-tools/.gitattributes b/external/xamarin-android-tools/.gitattributes index 22983d6f305..3ed0d2963d7 100644 --- a/external/xamarin-android-tools/.gitattributes +++ b/external/xamarin-android-tools/.gitattributes @@ -1,3 +1,5 @@ *.Designer.cs eol=crlf *.resx text -*.xlf text \ No newline at end of file +*.xlf text + +.github/workflows/*.lock.yml linguist-generated=true merge=ours \ No newline at end of file diff --git a/external/xamarin-android-tools/.github/agents/agentic-workflows.agent.md b/external/xamarin-android-tools/.github/agents/agentic-workflows.agent.md new file mode 100644 index 00000000000..e2e2b2b58be --- /dev/null +++ b/external/xamarin-android-tools/.github/agents/agentic-workflows.agent.md @@ -0,0 +1,178 @@ +--- +description: GitHub Agentic Workflows (gh-aw) - Create, debug, and upgrade AI-powered workflows with intelligent prompt routing +disable-model-invocation: true +--- + +# GitHub Agentic Workflows Agent + +This agent helps you work with **GitHub Agentic Workflows (gh-aw)**, a CLI extension for creating AI-powered workflows in natural language using markdown files. + +## What This Agent Does + +This is a **dispatcher agent** that routes your request to the appropriate specialized prompt based on your task: + +- **Creating new workflows**: Routes to `create` prompt +- **Updating existing workflows**: Routes to `update` prompt +- **Debugging workflows**: Routes to `debug` prompt +- **Upgrading workflows**: Routes to `upgrade-agentic-workflows` prompt +- **Creating report-generating workflows**: Routes to `report` prompt — consult this whenever the workflow posts status updates, audits, analyses, or any structured output as issues, discussions, or comments +- **Creating shared components**: Routes to `create-shared-agentic-workflow` prompt +- **Fixing Dependabot PRs**: Routes to `dependabot` prompt — use this when Dependabot opens PRs that modify generated manifest files (`.github/workflows/package.json`, `.github/workflows/requirements.txt`, `.github/workflows/go.mod`). Never merge those PRs directly; instead update the source `.md` files and rerun `gh aw compile --dependabot` to bundle all fixes +- **Analyzing test coverage**: Routes to `test-coverage` prompt — consult this whenever the workflow reads, analyzes, or reports on test coverage data from PRs or CI runs + +Workflows may optionally include: + +- **Project tracking / monitoring** (GitHub Projects updates, status reporting) +- **Orchestration / coordination** (one workflow assigning agents or dispatching and coordinating other workflows) + +## Files This Applies To + +- Workflow files: `.github/workflows/*.md` and `.github/workflows/**/*.md` +- Workflow lock files: `.github/workflows/*.lock.yml` +- Shared components: `.github/workflows/shared/*.md` +- Configuration: https://github.com/github/gh-aw/blob/v0.68.3/.github/aw/github-agentic-workflows.md + +## Problems This Solves + +- **Workflow Creation**: Design secure, validated agentic workflows with proper triggers, tools, and permissions +- **Workflow Debugging**: Analyze logs, identify missing tools, investigate failures, and fix configuration issues +- **Version Upgrades**: Migrate workflows to new gh-aw versions, apply codemods, fix breaking changes +- **Component Design**: Create reusable shared workflow components that wrap MCP servers + +## How to Use + +When you interact with this agent, it will: + +1. **Understand your intent** - Determine what kind of task you're trying to accomplish +2. **Route to the right prompt** - Load the specialized prompt file for your task +3. **Execute the task** - Follow the detailed instructions in the loaded prompt + +## Available Prompts + +### Create New Workflow +**Load when**: User wants to create a new workflow from scratch, add automation, or design a workflow that doesn't exist yet + +**Prompt file**: https://github.com/github/gh-aw/blob/v0.68.3/.github/aw/create-agentic-workflow.md + +**Use cases**: +- "Create a workflow that triages issues" +- "I need a workflow to label pull requests" +- "Design a weekly research automation" + +### Update Existing Workflow +**Load when**: User wants to modify, improve, or refactor an existing workflow + +**Prompt file**: https://github.com/github/gh-aw/blob/v0.68.3/.github/aw/update-agentic-workflow.md + +**Use cases**: +- "Add web-fetch tool to the issue-classifier workflow" +- "Update the PR reviewer to use discussions instead of issues" +- "Improve the prompt for the weekly-research workflow" + +### Debug Workflow +**Load when**: User needs to investigate, audit, debug, or understand a workflow, troubleshoot issues, analyze logs, or fix errors + +**Prompt file**: https://github.com/github/gh-aw/blob/v0.68.3/.github/aw/debug-agentic-workflow.md + +**Use cases**: +- "Why is this workflow failing?" +- "Analyze the logs for workflow X" +- "Investigate missing tool calls in run #12345" + +### Upgrade Agentic Workflows +**Load when**: User wants to upgrade workflows to a new gh-aw version or fix deprecations + +**Prompt file**: https://github.com/github/gh-aw/blob/v0.68.3/.github/aw/upgrade-agentic-workflows.md + +**Use cases**: +- "Upgrade all workflows to the latest version" +- "Fix deprecated fields in workflows" +- "Apply breaking changes from the new release" + +### Create a Report-Generating Workflow +**Load when**: The workflow being created or updated produces reports — recurring status updates, audit summaries, analyses, or any structured output posted as a GitHub issue, discussion, or comment + +**Prompt file**: https://github.com/github/gh-aw/blob/v0.68.3/.github/aw/report.md + +**Use cases**: +- "Create a weekly CI health report" +- "Post a daily security audit to Discussions" +- "Add a status update comment to open PRs" + +### Create Shared Agentic Workflow +**Load when**: User wants to create a reusable workflow component or wrap an MCP server + +**Prompt file**: https://github.com/github/gh-aw/blob/v0.68.3/.github/aw/create-shared-agentic-workflow.md + +**Use cases**: +- "Create a shared component for Notion integration" +- "Wrap the Slack MCP server as a reusable component" +- "Design a shared workflow for database queries" + +### Fix Dependabot PRs +**Load when**: User needs to close or fix open Dependabot PRs that update dependencies in generated manifest files (`.github/workflows/package.json`, `.github/workflows/requirements.txt`, `.github/workflows/go.mod`) + +**Prompt file**: https://github.com/github/gh-aw/blob/v0.68.3/.github/aw/dependabot.md + +**Use cases**: +- "Fix the open Dependabot PRs for npm dependencies" +- "Bundle and close the Dependabot PRs for workflow dependencies" +- "Update @playwright/test to fix the Dependabot PR" + +### Analyze Test Coverage +**Load when**: The workflow reads, analyzes, or reports test coverage — whether triggered by a PR, a schedule, or a slash command. Always consult this prompt before designing the coverage data strategy. + +**Prompt file**: https://github.com/github/gh-aw/blob/v0.68.3/.github/aw/test-coverage.md + +**Use cases**: +- "Create a workflow that comments coverage on PRs" +- "Analyze coverage trends over time" +- "Add a coverage gate that blocks PRs below a threshold" + +## Instructions + +When a user interacts with you: + +1. **Identify the task type** from the user's request +2. **Load the appropriate prompt** from the GitHub repository URLs listed above +3. **Follow the loaded prompt's instructions** exactly +4. **If uncertain**, ask clarifying questions to determine the right prompt + +## Quick Reference + +```bash +# Initialize repository for agentic workflows +gh aw init + +# Generate the lock file for a workflow +gh aw compile [workflow-name] + +# Debug workflow runs +gh aw logs [workflow-name] +gh aw audit + +# Upgrade workflows +gh aw fix --write +gh aw compile --validate +``` + +## Key Features of gh-aw + +- **Natural Language Workflows**: Write workflows in markdown with YAML frontmatter +- **AI Engine Support**: Copilot, Claude, Codex, or custom engines +- **MCP Server Integration**: Connect to Model Context Protocol servers for tools +- **Safe Outputs**: Structured communication between AI and GitHub API +- **Strict Mode**: Security-first validation and sandboxing +- **Shared Components**: Reusable workflow building blocks +- **Repo Memory**: Persistent git-backed storage for agents +- **Sandboxed Execution**: All workflows run in the Agent Workflow Firewall (AWF) sandbox, enabling full `bash` and `edit` tools by default + +## Important Notes + +- Always reference the instructions file at https://github.com/github/gh-aw/blob/v0.68.3/.github/aw/github-agentic-workflows.md for complete documentation +- Use the MCP tool `agentic-workflows` when running in GitHub Copilot Cloud +- Workflows must be compiled to `.lock.yml` files before running in GitHub Actions +- **Bash tools are enabled by default** - Don't restrict bash commands unnecessarily since workflows are sandboxed by the AWF +- Follow security best practices: minimal permissions, explicit network access, no template injection +- **Network configuration**: Use ecosystem identifiers (`node`, `python`, `go`, etc.) or explicit FQDNs in `network.allowed`. Bare shorthands like `npm` or `pypi` are **not** valid. See https://github.com/github/gh-aw/blob/v0.68.3/.github/aw/network.md for the full list of valid ecosystem identifiers and domain patterns. +- **Single-file output**: When creating a workflow, produce exactly **one** workflow `.md` file. Do not create separate documentation files (architecture docs, runbooks, usage guides, etc.). If documentation is needed, add a brief `## Usage` section inside the workflow file itself. diff --git a/external/xamarin-android-tools/.github/aw/actions-lock.json b/external/xamarin-android-tools/.github/aw/actions-lock.json new file mode 100644 index 00000000000..97077a3bc29 --- /dev/null +++ b/external/xamarin-android-tools/.github/aw/actions-lock.json @@ -0,0 +1,9 @@ +{ + "entries": { + "github/gh-aw-actions/setup@v0.68.3": { + "repo": "github/gh-aw-actions/setup", + "version": "v0.68.3", + "sha": "ba90f2186d7ad780ec640f364005fa24e797b360" + } + } +} diff --git a/external/xamarin-android-tools/.github/copilot-instructions.md b/external/xamarin-android-tools/.github/copilot-instructions.md index 004834875e3..e04bf19c069 100644 --- a/external/xamarin-android-tools/.github/copilot-instructions.md +++ b/external/xamarin-android-tools/.github/copilot-instructions.md @@ -24,14 +24,6 @@ dotnet test tests/Microsoft.Android.Build.BaseTasks-Tests/Microsoft.Android.Buil Output: `bin\$(Configuration)\` (redistributables), `bin\Test$(Configuration)\` (tests). `$(DotNetTargetFrameworkVersion)` = `10.0` in `Directory.Build.props`. Versioning: `nuget.version` has `major.minor`; patch = git commit count since file changed. -## Running Scripts - -This repo uses `dotnet run file.cs` (.NET 10+ feature) to execute standalone C# scripts with top-level statements — no `.csproj` needed. Example: - -```sh -dotnet run .github/skills/android-tools-reviewer/scripts/submit_review.cs -- arg1 arg2 -``` - ## Android Environment Variables Per the [official Android docs](https://developer.android.com/tools/variables#envar): diff --git a/external/xamarin-android-tools/.github/skills/android-tools-reviewer/SKILL.md b/external/xamarin-android-tools/.github/skills/android-tools-reviewer/SKILL.md index 3541491b26a..8feeaa4d948 100644 --- a/external/xamarin-android-tools/.github/skills/android-tools-reviewer/SKILL.md +++ b/external/xamarin-android-tools/.github/skills/android-tools-reviewer/SKILL.md @@ -23,9 +23,9 @@ Flag severity clearly in every comment: ## Workflow -### 1. Parse the PR URL +### 1. Identify the PR -Extract `owner`, `repo`, `pr_number` from the URL. +If triggered from an agentic workflow (slash command on a PR), use the PR from the event context. Otherwise, extract `owner`, `repo`, `pr_number` from a URL or reference provided by the user. Formats: `https://github.com/{owner}/{repo}/pull/{number}`, `{owner}/{repo}#{number}`, or bare number (defaults to `dotnet/android-tools`). ### 2. Gather context (before reading PR description) @@ -47,11 +47,22 @@ gh pr view {number} --repo {owner}/{repo} --json title,body Now read the PR description and linked issues. Treat them as claims to verify, not facts to accept. Where your independent reading disagrees with the PR description, investigate further. If the PR claims a performance improvement, require evidence. If it claims a bug fix, verify the bug exists and the fix addresses root cause — not symptoms. -### 4. Load review rules +### 4. Check CI status + +``` +gh pr checks {number} --repo {owner}/{repo} +``` + +Review the CI results. **Never post ✅ LGTM if any required CI check is failing or if the code doesn't build.** If CI is failing: +- Investigate the failure. +- If the failure is caused by the PR's code changes, flag it as ❌ error. +- If the failure is a known infrastructure issue or pre-existing flake unrelated to the PR, note it in the summary but still use ⚠️ Needs Changes — the PR isn't mergeable until CI is green. + +### 5. Load review rules Read `references/review-rules.md` from this skill's directory. -### 5. Analyze the diff +### 6. Analyze the diff For each changed file, check against the review rules. Record issues as: @@ -67,38 +78,16 @@ Constraints: - **Don't flag what CI catches.** Skip compiler errors, formatting the linter will catch, etc. - **Avoid false positives.** Verify the concern actually applies given the full context. If unsure, phrase it as a question rather than a firm claim. -### 6. Build the review JSON +### 7. Post the review -Write a temp JSON file: +Post your findings directly: -```json -{ - "event": "COMMENT", - "body": "## 🤖 AI Review Summary\n\n**Verdict**: ✅ LGTM | ⚠️ Needs Changes | ❌ Reject\n\nFound **N issues**: ...\n\n- ❌ **Category**: description (`file:line`)\n- ⚠️ **Category**: description (`file:line`)\n\n👍 Positive callouts.\n\n---\n_Review generated by android-tools-reviewer from [review guidelines](../../../docs/CODE_REVIEW_POSTMORTEM.md) by @jonathanpeppers._", - "comments": [ - { - "path": "src/Example.cs", - "line": 42, - "side": "RIGHT", - "body": "🤖 ❌ **Error handling** — Every `catch` should capture the `Exception` and log it.\n\n_Rule: No empty catch blocks (Postmortem `#11`)_" - } - ] -} -``` - -If no issues found, submit with empty `comments` and a positive summary. - -### 7. Submit as a single batch - -```powershell -dotnet run {skill-dir}/scripts/submit_review.cs -- {owner} {repo} {number} {path-to-json} -``` +- **Inline comments** on specific lines of the diff with the severity, category, and explanation. +- **Review summary** with the overall verdict (✅ LGTM, ⚠️ Needs Changes, or ❌ Reject), issue counts by severity, and positive callouts. -> **Note:** `dotnet run file.cs` is a .NET 10+ feature that runs standalone C# files -> with top-level statements directly — no `.csproj` needed. Use `--` to separate -> `dotnet run` flags from script arguments. +If no issues found **and CI is green**, submit with at most one or two 💡 suggestions and a positive summary. -The script validates structure (required fields, 🤖 prefix, positive line numbers) then calls `gh api`. Delete the temp file after success. +**Copilot-authored PRs:** If the PR author is `Copilot` (the GitHub Copilot coding agent) and the verdict is ⚠️ Needs Changes or ❌ Reject, prefix the review summary with `@copilot ` so the comment automatically triggers Copilot to address the feedback. Do NOT add the prefix for ✅ LGTM verdicts. ## Comment format diff --git a/external/xamarin-android-tools/.github/skills/android-tools-reviewer/scripts/submit_review.cs b/external/xamarin-android-tools/.github/skills/android-tools-reviewer/scripts/submit_review.cs deleted file mode 100644 index 0cac123b5c8..00000000000 --- a/external/xamarin-android-tools/.github/skills/android-tools-reviewer/scripts/submit_review.cs +++ /dev/null @@ -1,153 +0,0 @@ -// Usage: dotnet run submit_review.cs -// -// Validates the review JSON structure, then submits it as a batched PR review -// via `gh api`. Requires the `gh` CLI to be installed and authenticated. - -using System; -using System.Diagnostics; -using System.IO; -using System.Text.Json; - -if (args.Length != 4) -{ - Console.Error.WriteLine ($"Usage: dotnet run submit_review.cs "); - return 1; -} - -var owner = args [0]; -var repo = args [1]; -var prNumber = args [2]; -var jsonPath = args [3]; - -if (!File.Exists (jsonPath)) { - Console.Error.WriteLine ($"❌ File not found: {jsonPath}"); - return 1; -} - -var json = File.ReadAllText (jsonPath); -JsonDocument doc; -try { - doc = JsonDocument.Parse (json); -} catch (JsonException ex) { - Console.Error.WriteLine ($"❌ Invalid JSON: {ex.Message}"); - return 1; -} - -using (doc) { - -var root = doc.RootElement; - -// Validate structure -var errors = new System.Collections.Generic.List (); - -if (!root.TryGetProperty ("event", out var eventProp) || eventProp.ValueKind != JsonValueKind.String) { - errors.Add ("Missing or invalid 'event' field — must be COMMENT, APPROVE, or REQUEST_CHANGES"); -} else { - var ev = eventProp.GetString () ?? ""; - if (ev != "COMMENT" && ev != "APPROVE" && ev != "REQUEST_CHANGES") - errors.Add ($"Invalid event '{ev}' — must be COMMENT, APPROVE, or REQUEST_CHANGES"); -} - -if (!root.TryGetProperty ("body", out var bodyProp) || bodyProp.ValueKind != JsonValueKind.String || string.IsNullOrWhiteSpace (bodyProp.GetString ())) { - errors.Add ("Missing or empty 'body' field (the review summary)"); -} - -if (root.TryGetProperty ("comments", out var commentsProp)) { - if (commentsProp.ValueKind != JsonValueKind.Array) { - errors.Add ("Invalid 'comments' field — must be an array"); - } else { - int i = 0; - foreach (var c in commentsProp.EnumerateArray ()) { - var prefix = $"comments[{i}]"; - - if (!c.TryGetProperty ("path", out var pathProp) || pathProp.ValueKind != JsonValueKind.String || string.IsNullOrEmpty (pathProp.GetString ())) - errors.Add ($"{prefix}: missing 'path'"); - - if (!c.TryGetProperty ("line", out var lineProp) || lineProp.ValueKind != JsonValueKind.Number || lineProp.GetInt32 () < 1) - errors.Add ($"{prefix}: 'line' must be a positive integer"); - - if (!c.TryGetProperty ("body", out var cbody) || cbody.ValueKind != JsonValueKind.String) { - errors.Add ($"{prefix}: missing or empty 'body'"); - } else { - var commentBody = cbody.GetString () ?? ""; - if (string.IsNullOrWhiteSpace (commentBody)) - errors.Add ($"{prefix}: missing or empty 'body'"); - else if (!commentBody.StartsWith ("🤖")) - errors.Add ($"{prefix}: body must start with 🤖 prefix"); - } - - if (c.TryGetProperty ("side", out var sideProp) && sideProp.ValueKind == JsonValueKind.String) { - var side = sideProp.GetString () ?? ""; - if (side != "LEFT" && side != "RIGHT") - errors.Add ($"{prefix}: 'side' must be LEFT or RIGHT, got '{side}'"); - } - - i++; - } - } -} - -if (errors.Count > 0) { - Console.Error.WriteLine ("❌ Review JSON validation failed:"); - foreach (var e in errors) - Console.Error.WriteLine ($" • {e}"); - return 1; -} - -var commentCount = root.TryGetProperty ("comments", out var cp) && cp.ValueKind == JsonValueKind.Array ? cp.GetArrayLength () : 0; -Console.WriteLine ($"✅ Review validated: {commentCount} comment(s)"); -Console.WriteLine ($"📤 Submitting review to {owner}/{repo}#{prNumber}..."); - -// Submit via gh api -var psi = new ProcessStartInfo { - FileName = "gh", - UseShellExecute = false, - RedirectStandardOutput = true, - RedirectStandardError = true, -}; -psi.ArgumentList.Add ("api"); -psi.ArgumentList.Add ($"repos/{owner}/{repo}/pulls/{prNumber}/reviews"); -psi.ArgumentList.Add ("--method"); -psi.ArgumentList.Add ("POST"); -psi.ArgumentList.Add ("--input"); -psi.ArgumentList.Add (jsonPath); - -using var process = Process.Start (psi); -if (process is null) { - Console.Error.WriteLine ("❌ Failed to start 'gh' — is it installed and on PATH?"); - return 1; -} -var stdoutTask = process.StandardOutput.ReadToEndAsync (); -var stderrTask = process.StandardError.ReadToEndAsync (); -process.WaitForExit (); -var stdout = stdoutTask.Result; -var stderr = stderrTask.Result; - -if (process.ExitCode != 0) { - Console.Error.WriteLine ($"❌ gh api failed (exit code {process.ExitCode}):"); - if (!string.IsNullOrEmpty (stderr)) - Console.Error.WriteLine (stderr); - if (!string.IsNullOrEmpty (stdout)) { - try { - using var errDoc = JsonDocument.Parse (stdout); - if (errDoc.RootElement.TryGetProperty ("message", out var msg)) - Console.Error.WriteLine ($" GitHub says: {msg.GetString ()}"); - } catch (JsonException) { - Console.Error.WriteLine (stdout); - } - } - return 1; -} - -Console.WriteLine ("✅ Review posted."); -try { - using var resp = JsonDocument.Parse (stdout); - if (resp.RootElement.TryGetProperty ("html_url", out var url)) - Console.WriteLine ($" {url.GetString ()}"); -} catch (JsonException) { - Console.WriteLine ("Note: API response was not JSON; review was posted but URL is unavailable."); -} - -} // using (doc) - -return 0; diff --git a/external/xamarin-android-tools/.github/workflows/android-tools-reviewer.lock.yml b/external/xamarin-android-tools/.github/workflows/android-tools-reviewer.lock.yml new file mode 100644 index 00000000000..ed52e7a9764 --- /dev/null +++ b/external/xamarin-android-tools/.github/workflows/android-tools-reviewer.lock.yml @@ -0,0 +1,1312 @@ +# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"10f45ab5acbfd8f4152d6d465324cc535ddca73e66b888311f9198f9d09b2193","compiler_version":"v0.68.3","strict":true,"agent_id":"copilot","agent_model":"claude-opus-4.6"} +# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"373c709c69115d41ff229c7e5df9f8788daa9553","version":"v9"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"ba90f2186d7ad780ec640f364005fa24e797b360","version":"v0.68.3"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.20"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.20"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.20"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.2.19"},{"image":"ghcr.io/github/github-mcp-server:v0.32.0"},{"image":"node:lts-alpine"}]} +# ___ _ _ +# / _ \ | | (_) +# | |_| | __ _ ___ _ __ | |_ _ ___ +# | _ |/ _` |/ _ \ '_ \| __| |/ __| +# | | | | (_| | __/ | | | |_| | (__ +# \_| |_/\__, |\___|_| |_|\__|_|\___| +# __/ | +# _ _ |___/ +# | | | | / _| | +# | | | | ___ _ __ _ __| |_| | _____ ____ +# | |/\| |/ _ \ '__| |/ /| _| |/ _ \ \ /\ / / ___| +# \ /\ / (_) | | | | ( | | | | (_) \ V V /\__ \ +# \/ \/ \___/|_| |_|\_\|_| |_|\___/ \_/\_/ |___/ +# +# This file was automatically generated by gh-aw (v0.68.3). DO NOT EDIT. +# +# To update this file, edit the corresponding .md file and run: +# gh aw compile +# Not all edits will cause changes to this file. +# +# For more information: https://github.github.com/gh-aw/introduction/overview/ +# +# +# Secrets used: +# - COPILOT_GITHUB_TOKEN +# - GH_AW_GITHUB_MCP_SERVER_TOKEN +# - GH_AW_GITHUB_TOKEN +# - GITHUB_TOKEN +# +# Custom actions used: +# - actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 +# - actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 +# - actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 +# - actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 +# - github/gh-aw-actions/setup@ba90f2186d7ad780ec640f364005fa24e797b360 # v0.68.3 +# +# Container images used: +# - ghcr.io/github/gh-aw-firewall/agent:0.25.20 +# - ghcr.io/github/gh-aw-firewall/api-proxy:0.25.20 +# - ghcr.io/github/gh-aw-firewall/squid:0.25.20 +# - ghcr.io/github/gh-aw-mcpg:v0.2.19 +# - ghcr.io/github/github-mcp-server:v0.32.0 +# - node:lts-alpine + +name: "Android Tools PR Reviewer" +"on": + issue_comment: + types: + - created + - edited + # roles: # Roles processed as role check in pre-activation job + # - admin # Roles processed as role check in pre-activation job + # - maintainer # Roles processed as role check in pre-activation job + # - write # Roles processed as role check in pre-activation job + +permissions: {} + +concurrency: + group: "gh-aw-${{ github.workflow }}-${{ github.event.issue.number || github.event.pull_request.number || github.run_id }}" + +run-name: "Android Tools PR Reviewer" + +jobs: + activation: + needs: pre_activation + if: "needs.pre_activation.outputs.activated == 'true' && (github.event_name == 'issue_comment' && (startsWith(github.event.comment.body, '/review ') || startsWith(github.event.comment.body, '/review\n') || github.event.comment.body == '/review') && github.event.issue.pull_request != null || !(github.event_name == 'issue_comment'))" + runs-on: ubuntu-slim + permissions: + actions: read + contents: read + discussions: write + issues: write + pull-requests: write + outputs: + body: ${{ steps.sanitized.outputs.body }} + comment_id: ${{ steps.add-comment.outputs.comment-id }} + comment_repo: ${{ steps.add-comment.outputs.comment-repo }} + comment_url: ${{ steps.add-comment.outputs.comment-url }} + lockdown_check_failed: ${{ steps.generate_aw_info.outputs.lockdown_check_failed == 'true' }} + model: ${{ steps.generate_aw_info.outputs.model }} + secret_verification_result: ${{ steps.validate-secret.outputs.verification_result }} + setup-trace-id: ${{ steps.setup.outputs.trace-id }} + slash_command: ${{ needs.pre_activation.outputs.matched_command }} + stale_lock_file_failed: ${{ steps.check-lock-file.outputs.stale_lock_file_failed == 'true' }} + text: ${{ steps.sanitized.outputs.text }} + title: ${{ steps.sanitized.outputs.title }} + steps: + - name: Setup Scripts + id: setup + uses: github/gh-aw-actions/setup@ba90f2186d7ad780ec640f364005fa24e797b360 # v0.68.3 + with: + destination: ${{ runner.temp }}/gh-aw/actions + job-name: ${{ github.job }} + trace-id: ${{ needs.pre_activation.outputs.setup-trace-id }} + - name: Generate agentic run info + id: generate_aw_info + env: + GH_AW_INFO_ENGINE_ID: "copilot" + GH_AW_INFO_ENGINE_NAME: "GitHub Copilot CLI" + GH_AW_INFO_MODEL: "claude-opus-4.6" + GH_AW_INFO_VERSION: "1.0.21" + GH_AW_INFO_AGENT_VERSION: "1.0.21" + GH_AW_INFO_CLI_VERSION: "v0.68.3" + GH_AW_INFO_WORKFLOW_NAME: "Android Tools PR Reviewer" + GH_AW_INFO_EXPERIMENTAL: "false" + GH_AW_INFO_SUPPORTS_TOOLS_ALLOWLIST: "true" + GH_AW_INFO_STAGED: "false" + GH_AW_INFO_ALLOWED_DOMAINS: '["defaults","dotnet","github","aka.ms","microsoft.com"]' + GH_AW_INFO_FIREWALL_ENABLED: "true" + GH_AW_INFO_AWF_VERSION: "v0.25.20" + GH_AW_INFO_AWMG_VERSION: "" + GH_AW_INFO_FIREWALL_TYPE: "squid" + GH_AW_COMPILED_STRICT: "true" + uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/generate_aw_info.cjs'); + await main(core, context); + - name: Add eyes reaction for immediate feedback + id: react + if: github.event_name == 'issues' || github.event_name == 'issue_comment' || github.event_name == 'pull_request_review_comment' || github.event_name == 'discussion' || github.event_name == 'discussion_comment' || github.event_name == 'pull_request' && github.event.pull_request.head.repo.id == github.repository_id + uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + env: + GH_AW_REACTION: "eyes" + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/add_reaction.cjs'); + await main(); + - name: Validate COPILOT_GITHUB_TOKEN secret + id: validate-secret + run: bash "${RUNNER_TEMP}/gh-aw/actions/validate_multi_secret.sh" COPILOT_GITHUB_TOKEN 'GitHub Copilot CLI' https://github.github.com/gh-aw/reference/engines/#github-copilot-default + env: + COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + - name: Checkout .github and .agents folders + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + sparse-checkout: | + .github + .agents + sparse-checkout-cone-mode: true + fetch-depth: 1 + - name: Check workflow lock file + id: check-lock-file + uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + env: + GH_AW_WORKFLOW_FILE: "android-tools-reviewer.lock.yml" + GH_AW_CONTEXT_WORKFLOW_REF: "${{ github.workflow_ref }}" + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/check_workflow_timestamp_api.cjs'); + await main(); + - name: Check compile-agentic version + uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + env: + GH_AW_COMPILED_VERSION: "v0.68.3" + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/check_version_updates.cjs'); + await main(); + - name: Compute current body text + id: sanitized + uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/compute_text.cjs'); + await main(); + - name: Add comment with workflow run link + id: add-comment + if: github.event_name == 'issues' || github.event_name == 'issue_comment' || github.event_name == 'pull_request_review_comment' || github.event_name == 'discussion' || github.event_name == 'discussion_comment' || github.event_name == 'pull_request' && github.event.pull_request.head.repo.id == github.repository_id + uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + env: + GH_AW_WORKFLOW_NAME: "Android Tools PR Reviewer" + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/add_workflow_run_comment.cjs'); + await main(); + - name: Create prompt with built-in context + env: + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + GH_AW_SAFE_OUTPUTS: ${{ runner.temp }}/gh-aw/safeoutputs/outputs.jsonl + GH_AW_GITHUB_ACTOR: ${{ github.actor }} + GH_AW_GITHUB_EVENT_COMMENT_ID: ${{ github.event.comment.id }} + GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: ${{ github.event.discussion.number }} + GH_AW_GITHUB_EVENT_ISSUE_NUMBER: ${{ github.event.issue.number }} + GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number }} + GH_AW_GITHUB_REPOSITORY: ${{ github.repository }} + GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} + GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }} + GH_AW_IS_PR_COMMENT: ${{ github.event.issue.pull_request && 'true' || '' }} + # poutine:ignore untrusted_checkout_exec + run: | + bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh" + { + cat << 'GH_AW_PROMPT_994477edffd80981_EOF' + + GH_AW_PROMPT_994477edffd80981_EOF + cat "${RUNNER_TEMP}/gh-aw/prompts/xpia.md" + cat "${RUNNER_TEMP}/gh-aw/prompts/temp_folder_prompt.md" + cat "${RUNNER_TEMP}/gh-aw/prompts/markdown.md" + cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_prompt.md" + cat << 'GH_AW_PROMPT_994477edffd80981_EOF' + + Tools: create_pull_request_review_comment(max:50), submit_pull_request_review, missing_tool, missing_data, noop + + + The following GitHub context information is available for this workflow: + {{#if __GH_AW_GITHUB_ACTOR__ }} + - **actor**: __GH_AW_GITHUB_ACTOR__ + {{/if}} + {{#if __GH_AW_GITHUB_REPOSITORY__ }} + - **repository**: __GH_AW_GITHUB_REPOSITORY__ + {{/if}} + {{#if __GH_AW_GITHUB_WORKSPACE__ }} + - **workspace**: __GH_AW_GITHUB_WORKSPACE__ + {{/if}} + {{#if __GH_AW_GITHUB_EVENT_ISSUE_NUMBER__ }} + - **issue-number**: #__GH_AW_GITHUB_EVENT_ISSUE_NUMBER__ + {{/if}} + {{#if __GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER__ }} + - **discussion-number**: #__GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER__ + {{/if}} + {{#if __GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER__ }} + - **pull-request-number**: #__GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER__ + {{/if}} + {{#if __GH_AW_GITHUB_EVENT_COMMENT_ID__ }} + - **comment-id**: __GH_AW_GITHUB_EVENT_COMMENT_ID__ + {{/if}} + {{#if __GH_AW_GITHUB_RUN_ID__ }} + - **workflow-run-id**: __GH_AW_GITHUB_RUN_ID__ + {{/if}} + + + GH_AW_PROMPT_994477edffd80981_EOF + cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md" + if [ "$GITHUB_EVENT_NAME" = "issue_comment" ] && [ -n "$GH_AW_IS_PR_COMMENT" ] || [ "$GITHUB_EVENT_NAME" = "pull_request_review_comment" ] || [ "$GITHUB_EVENT_NAME" = "pull_request_review" ]; then + cat "${RUNNER_TEMP}/gh-aw/prompts/pr_context_prompt.md" + fi + cat << 'GH_AW_PROMPT_994477edffd80981_EOF' + + {{#runtime-import .github/workflows/android-tools-reviewer.md}} + GH_AW_PROMPT_994477edffd80981_EOF + } > "$GH_AW_PROMPT" + - name: Interpolate variables and render templates + uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + env: + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/interpolate_prompt.cjs'); + await main(); + - name: Substitute placeholders + uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + env: + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + GH_AW_GITHUB_ACTOR: ${{ github.actor }} + GH_AW_GITHUB_EVENT_COMMENT_ID: ${{ github.event.comment.id }} + GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: ${{ github.event.discussion.number }} + GH_AW_GITHUB_EVENT_ISSUE_NUMBER: ${{ github.event.issue.number }} + GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number }} + GH_AW_GITHUB_REPOSITORY: ${{ github.repository }} + GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} + GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }} + GH_AW_IS_PR_COMMENT: ${{ github.event.issue.pull_request && 'true' || '' }} + GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ACTIVATED: ${{ needs.pre_activation.outputs.activated }} + GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_MATCHED_COMMAND: ${{ needs.pre_activation.outputs.matched_command }} + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + + const substitutePlaceholders = require('${{ runner.temp }}/gh-aw/actions/substitute_placeholders.cjs'); + + // Call the substitution function + return await substitutePlaceholders({ + file: process.env.GH_AW_PROMPT, + substitutions: { + GH_AW_GITHUB_ACTOR: process.env.GH_AW_GITHUB_ACTOR, + GH_AW_GITHUB_EVENT_COMMENT_ID: process.env.GH_AW_GITHUB_EVENT_COMMENT_ID, + GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: process.env.GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER, + GH_AW_GITHUB_EVENT_ISSUE_NUMBER: process.env.GH_AW_GITHUB_EVENT_ISSUE_NUMBER, + GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: process.env.GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER, + GH_AW_GITHUB_REPOSITORY: process.env.GH_AW_GITHUB_REPOSITORY, + GH_AW_GITHUB_RUN_ID: process.env.GH_AW_GITHUB_RUN_ID, + GH_AW_GITHUB_WORKSPACE: process.env.GH_AW_GITHUB_WORKSPACE, + GH_AW_IS_PR_COMMENT: process.env.GH_AW_IS_PR_COMMENT, + GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ACTIVATED: process.env.GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ACTIVATED, + GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_MATCHED_COMMAND: process.env.GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_MATCHED_COMMAND + } + }); + - name: Validate prompt placeholders + env: + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + # poutine:ignore untrusted_checkout_exec + run: bash "${RUNNER_TEMP}/gh-aw/actions/validate_prompt_placeholders.sh" + - name: Print prompt + env: + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + # poutine:ignore untrusted_checkout_exec + run: bash "${RUNNER_TEMP}/gh-aw/actions/print_prompt_summary.sh" + - name: Upload activation artifact + if: success() + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: activation + path: | + /tmp/gh-aw/aw_info.json + /tmp/gh-aw/aw-prompts/prompt.txt + /tmp/gh-aw/github_rate_limits.jsonl + if-no-files-found: ignore + retention-days: 1 + + agent: + needs: activation + runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: read + env: + DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} + GH_AW_ASSETS_ALLOWED_EXTS: "" + GH_AW_ASSETS_BRANCH: "" + GH_AW_ASSETS_MAX_SIZE_KB: 0 + GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs + GH_AW_WORKFLOW_ID_SANITIZED: androidtoolsreviewer + outputs: + agentic_engine_timeout: ${{ steps.detect-copilot-errors.outputs.agentic_engine_timeout || 'false' }} + checkout_pr_success: ${{ steps.checkout-pr.outputs.checkout_pr_success || 'true' }} + effective_tokens: ${{ steps.parse-mcp-gateway.outputs.effective_tokens }} + has_patch: ${{ steps.collect_output.outputs.has_patch }} + inference_access_error: ${{ steps.detect-copilot-errors.outputs.inference_access_error || 'false' }} + mcp_policy_error: ${{ steps.detect-copilot-errors.outputs.mcp_policy_error || 'false' }} + model: ${{ needs.activation.outputs.model }} + model_not_supported_error: ${{ steps.detect-copilot-errors.outputs.model_not_supported_error || 'false' }} + output: ${{ steps.collect_output.outputs.output }} + output_types: ${{ steps.collect_output.outputs.output_types }} + setup-trace-id: ${{ steps.setup.outputs.trace-id }} + steps: + - name: Setup Scripts + id: setup + uses: github/gh-aw-actions/setup@ba90f2186d7ad780ec640f364005fa24e797b360 # v0.68.3 + with: + destination: ${{ runner.temp }}/gh-aw/actions + job-name: ${{ github.job }} + trace-id: ${{ needs.activation.outputs.setup-trace-id }} + - name: Set runtime paths + id: set-runtime-paths + run: | + { + echo "GH_AW_SAFE_OUTPUTS=${RUNNER_TEMP}/gh-aw/safeoutputs/outputs.jsonl" + echo "GH_AW_SAFE_OUTPUTS_CONFIG_PATH=${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" + echo "GH_AW_SAFE_OUTPUTS_TOOLS_PATH=${RUNNER_TEMP}/gh-aw/safeoutputs/tools.json" + } >> "$GITHUB_OUTPUT" + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + - name: Create gh-aw temp directory + run: bash "${RUNNER_TEMP}/gh-aw/actions/create_gh_aw_tmp_dir.sh" + - name: Configure gh CLI for GitHub Enterprise + run: bash "${RUNNER_TEMP}/gh-aw/actions/configure_gh_for_ghe.sh" + env: + GH_TOKEN: ${{ github.token }} + - name: Configure Git credentials + env: + REPO_NAME: ${{ github.repository }} + SERVER_URL: ${{ github.server_url }} + GITHUB_TOKEN: ${{ github.token }} + run: | + git config --global user.email "github-actions[bot]@users.noreply.github.com" + git config --global user.name "github-actions[bot]" + git config --global am.keepcr true + # Re-authenticate git with GitHub token + SERVER_URL_STRIPPED="${SERVER_URL#https://}" + git remote set-url origin "https://x-access-token:${GITHUB_TOKEN}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" + echo "Git configured with standard GitHub Actions identity" + - name: Checkout PR branch + id: checkout-pr + if: | + github.event.pull_request || github.event.issue.pull_request + uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + env: + GH_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + with: + github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/checkout_pr_branch.cjs'); + await main(); + - name: Install GitHub Copilot CLI + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.21 + env: + GH_HOST: github.com + - name: Install AWF binary + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.20 + - name: Parse integrity filter lists + id: parse-guard-vars + env: + GH_AW_BLOCKED_USERS_VAR: ${{ vars.GH_AW_GITHUB_BLOCKED_USERS || '' }} + GH_AW_TRUSTED_USERS_VAR: ${{ vars.GH_AW_GITHUB_TRUSTED_USERS || '' }} + GH_AW_APPROVAL_LABELS_VAR: ${{ vars.GH_AW_GITHUB_APPROVAL_LABELS || '' }} + run: bash "${RUNNER_TEMP}/gh-aw/actions/parse_guard_list.sh" + - name: Download container images + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.25.20 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.20 ghcr.io/github/gh-aw-firewall/squid:0.25.20 ghcr.io/github/gh-aw-mcpg:v0.2.19 ghcr.io/github/github-mcp-server:v0.32.0 node:lts-alpine + - name: Write Safe Outputs Config + run: | + mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" + mkdir -p /tmp/gh-aw/safeoutputs + mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs + cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_603f5e61db821820_EOF' + {"create_pull_request_review_comment":{"max":50,"side":"RIGHT"},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"report_incomplete":{},"submit_pull_request_review":{"allowed_events":["COMMENT","REQUEST_CHANGES"],"max":1}} + GH_AW_SAFE_OUTPUTS_CONFIG_603f5e61db821820_EOF + - name: Write Safe Outputs Tools + env: + GH_AW_TOOLS_META_JSON: | + { + "description_suffixes": { + "create_pull_request_review_comment": " CONSTRAINTS: Maximum 50 review comment(s) can be created. Comments will be on the RIGHT side of the diff.", + "submit_pull_request_review": " CONSTRAINTS: Maximum 1 review(s) can be submitted." + }, + "repo_params": {}, + "dynamic_tools": [] + } + GH_AW_VALIDATION_JSON: | + { + "create_pull_request_review_comment": { + "defaultMax": 1, + "fields": { + "body": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 65000 + }, + "line": { + "required": true, + "positiveInteger": true + }, + "path": { + "required": true, + "type": "string" + }, + "pull_request_number": { + "optionalPositiveInteger": true + }, + "repo": { + "type": "string", + "maxLength": 256 + }, + "side": { + "type": "string", + "enum": [ + "LEFT", + "RIGHT" + ] + }, + "start_line": { + "optionalPositiveInteger": true + } + }, + "customValidation": "startLineLessOrEqualLine" + }, + "missing_data": { + "defaultMax": 20, + "fields": { + "alternatives": { + "type": "string", + "sanitize": true, + "maxLength": 256 + }, + "context": { + "type": "string", + "sanitize": true, + "maxLength": 256 + }, + "data_type": { + "type": "string", + "sanitize": true, + "maxLength": 128 + }, + "reason": { + "type": "string", + "sanitize": true, + "maxLength": 256 + } + } + }, + "missing_tool": { + "defaultMax": 20, + "fields": { + "alternatives": { + "type": "string", + "sanitize": true, + "maxLength": 512 + }, + "reason": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 256 + }, + "tool": { + "type": "string", + "sanitize": true, + "maxLength": 128 + } + } + }, + "noop": { + "defaultMax": 1, + "fields": { + "message": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 65000 + } + } + }, + "report_incomplete": { + "defaultMax": 5, + "fields": { + "details": { + "type": "string", + "sanitize": true, + "maxLength": 65000 + }, + "reason": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 1024 + } + } + }, + "submit_pull_request_review": { + "defaultMax": 1, + "fields": { + "body": { + "type": "string", + "sanitize": true, + "maxLength": 65000 + }, + "event": { + "type": "string", + "enum": [ + "APPROVE", + "REQUEST_CHANGES", + "COMMENT" + ] + } + } + } + } + uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/generate_safe_outputs_tools.cjs'); + await main(); + - name: Generate Safe Outputs MCP Server Config + id: safe-outputs-config + run: | + # Generate a secure random API key (360 bits of entropy, 40+ chars) + # Mask immediately to prevent timing vulnerabilities + API_KEY=$(openssl rand -base64 45 | tr -d '/+=') + echo "::add-mask::${API_KEY}" + + PORT=3001 + + # Set outputs for next steps + { + echo "safe_outputs_api_key=${API_KEY}" + echo "safe_outputs_port=${PORT}" + } >> "$GITHUB_OUTPUT" + + echo "Safe Outputs MCP server will run on port ${PORT}" + + - name: Start Safe Outputs MCP HTTP Server + id: safe-outputs-start + env: + DEBUG: '*' + GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} + GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-config.outputs.safe_outputs_port }} + GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-config.outputs.safe_outputs_api_key }} + GH_AW_SAFE_OUTPUTS_TOOLS_PATH: ${{ runner.temp }}/gh-aw/safeoutputs/tools.json + GH_AW_SAFE_OUTPUTS_CONFIG_PATH: ${{ runner.temp }}/gh-aw/safeoutputs/config.json + GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs + run: | + # Environment variables are set above to prevent template injection + export DEBUG + export GH_AW_SAFE_OUTPUTS + export GH_AW_SAFE_OUTPUTS_PORT + export GH_AW_SAFE_OUTPUTS_API_KEY + export GH_AW_SAFE_OUTPUTS_TOOLS_PATH + export GH_AW_SAFE_OUTPUTS_CONFIG_PATH + export GH_AW_MCP_LOG_DIR + + bash "${RUNNER_TEMP}/gh-aw/actions/start_safe_outputs_server.sh" + + - name: Start MCP Gateway + id: start-mcp-gateway + env: + GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} + GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-start.outputs.api_key }} + GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-start.outputs.port }} + GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + run: | + set -eo pipefail + mkdir -p /tmp/gh-aw/mcp-config + + # Export gateway environment variables for MCP config and gateway script + export MCP_GATEWAY_PORT="80" + export MCP_GATEWAY_DOMAIN="host.docker.internal" + MCP_GATEWAY_API_KEY=$(openssl rand -base64 45 | tr -d '/+=') + echo "::add-mask::${MCP_GATEWAY_API_KEY}" + export MCP_GATEWAY_API_KEY + export MCP_GATEWAY_PAYLOAD_DIR="/tmp/gh-aw/mcp-payloads" + mkdir -p "${MCP_GATEWAY_PAYLOAD_DIR}" + export MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD="524288" + export DEBUG="*" + + export GH_AW_ENGINE="copilot" + export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host -v /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/github/gh-aw-mcpg:v0.2.19' + + mkdir -p /home/runner/.copilot + cat << GH_AW_MCP_CONFIG_c8e52fbe22218e9c_EOF | bash "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.sh" + { + "mcpServers": { + "github": { + "type": "stdio", + "container": "ghcr.io/github/github-mcp-server:v0.32.0", + "env": { + "GITHUB_HOST": "\${GITHUB_SERVER_URL}", + "GITHUB_PERSONAL_ACCESS_TOKEN": "\${GITHUB_MCP_SERVER_TOKEN}", + "GITHUB_READ_ONLY": "1", + "GITHUB_TOOLSETS": "pull_requests,repos" + }, + "guard-policies": { + "allow-only": { + "approval-labels": ${{ steps.parse-guard-vars.outputs.approval_labels }}, + "blocked-users": ${{ steps.parse-guard-vars.outputs.blocked_users }}, + "min-integrity": "none", + "repos": "all", + "trusted-users": ${{ steps.parse-guard-vars.outputs.trusted_users }} + } + } + }, + "safeoutputs": { + "type": "http", + "url": "http://host.docker.internal:$GH_AW_SAFE_OUTPUTS_PORT", + "headers": { + "Authorization": "\${GH_AW_SAFE_OUTPUTS_API_KEY}" + }, + "guard-policies": { + "write-sink": { + "accept": [ + "*" + ] + } + } + } + }, + "gateway": { + "port": $MCP_GATEWAY_PORT, + "domain": "${MCP_GATEWAY_DOMAIN}", + "apiKey": "${MCP_GATEWAY_API_KEY}", + "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" + } + } + GH_AW_MCP_CONFIG_c8e52fbe22218e9c_EOF + - name: Download activation artifact + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: activation + path: /tmp/gh-aw + - name: Clean git credentials + continue-on-error: true + run: bash "${RUNNER_TEMP}/gh-aw/actions/clean_git_credentials.sh" + - name: Execute GitHub Copilot CLI + id: agentic_execution + # Copilot CLI tool arguments (sorted): + timeout-minutes: 20 + run: | + set -o pipefail + touch /tmp/gh-aw/agent-step-summary.md + (umask 177 && touch /tmp/gh-aw/agent-stdio.log) + # shellcheck disable=SC1003 + sudo -E awf --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --env-all --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --allow-domains '*.githubusercontent.com,*.vsblob.vsassets.io,aka.ms,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.nuget.org,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,azuresearch-usnc.nuget.org,azuresearch-ussc.nuget.org,builds.dotnet.microsoft.com,ci.dot.net,codeload.github.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,dc.services.visualstudio.com,dist.nuget.org,docs.github.com,dot.net,dotnet.microsoft.com,dotnetcli.blob.core.windows.net,github-cloud.githubusercontent.com,github-cloud.s3.amazonaws.com,github.blog,github.com,github.githubassets.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,lfs.github.com,microsoft.com,nuget.org,nuget.pkg.github.com,nugetregistryv2prod.blob.core.windows.net,objects.githubusercontent.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,oneocsp.microsoft.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,pkgs.dev.azure.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com,www.microsoft.com' --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --image-tag 0.25.20 --skip-pull --enable-api-proxy \ + -- /bin/bash -c 'node ${RUNNER_TEMP}/gh-aw/actions/copilot_driver.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log + env: + COPILOT_AGENT_RUNNER_TYPE: STANDALONE + COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + COPILOT_MODEL: claude-opus-4.6 + GH_AW_MCP_CONFIG: /home/runner/.copilot/mcp-config.json + GH_AW_PHASE: agent + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} + GH_AW_VERSION: v0.68.3 + GITHUB_API_URL: ${{ github.api_url }} + GITHUB_AW: true + GITHUB_HEAD_REF: ${{ github.head_ref }} + GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + GITHUB_REF_NAME: ${{ github.ref_name }} + GITHUB_SERVER_URL: ${{ github.server_url }} + GITHUB_STEP_SUMMARY: /tmp/gh-aw/agent-step-summary.md + GITHUB_WORKSPACE: ${{ github.workspace }} + GIT_AUTHOR_EMAIL: github-actions[bot]@users.noreply.github.com + GIT_AUTHOR_NAME: github-actions[bot] + GIT_COMMITTER_EMAIL: github-actions[bot]@users.noreply.github.com + GIT_COMMITTER_NAME: github-actions[bot] + XDG_CONFIG_HOME: /home/runner + - name: Detect Copilot errors + id: detect-copilot-errors + if: always() + continue-on-error: true + run: node "${RUNNER_TEMP}/gh-aw/actions/detect_copilot_errors.cjs" + - name: Configure Git credentials + env: + REPO_NAME: ${{ github.repository }} + SERVER_URL: ${{ github.server_url }} + GITHUB_TOKEN: ${{ github.token }} + run: | + git config --global user.email "github-actions[bot]@users.noreply.github.com" + git config --global user.name "github-actions[bot]" + git config --global am.keepcr true + # Re-authenticate git with GitHub token + SERVER_URL_STRIPPED="${SERVER_URL#https://}" + git remote set-url origin "https://x-access-token:${GITHUB_TOKEN}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" + echo "Git configured with standard GitHub Actions identity" + - name: Copy Copilot session state files to logs + if: always() + continue-on-error: true + run: bash "${RUNNER_TEMP}/gh-aw/actions/copy_copilot_session_state.sh" + - name: Stop MCP Gateway + if: always() + continue-on-error: true + env: + MCP_GATEWAY_PORT: ${{ steps.start-mcp-gateway.outputs.gateway-port }} + MCP_GATEWAY_API_KEY: ${{ steps.start-mcp-gateway.outputs.gateway-api-key }} + GATEWAY_PID: ${{ steps.start-mcp-gateway.outputs.gateway-pid }} + run: | + bash "${RUNNER_TEMP}/gh-aw/actions/stop_mcp_gateway.sh" "$GATEWAY_PID" + - name: Redact secrets in logs + if: always() + uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/redact_secrets.cjs'); + await main(); + env: + GH_AW_SECRET_NAMES: 'COPILOT_GITHUB_TOKEN,GH_AW_GITHUB_MCP_SERVER_TOKEN,GH_AW_GITHUB_TOKEN,GITHUB_TOKEN' + SECRET_COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + SECRET_GH_AW_GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }} + SECRET_GH_AW_GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }} + SECRET_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Append agent step summary + if: always() + run: bash "${RUNNER_TEMP}/gh-aw/actions/append_agent_step_summary.sh" + - name: Copy Safe Outputs + if: always() + env: + GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} + run: | + mkdir -p /tmp/gh-aw + cp "$GH_AW_SAFE_OUTPUTS" /tmp/gh-aw/safeoutputs.jsonl 2>/dev/null || true + - name: Ingest agent output + id: collect_output + if: always() + uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + env: + GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} + GH_AW_ALLOWED_DOMAINS: "*.githubusercontent.com,*.vsblob.vsassets.io,aka.ms,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.nuget.org,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,azuresearch-usnc.nuget.org,azuresearch-ussc.nuget.org,builds.dotnet.microsoft.com,ci.dot.net,codeload.github.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,dc.services.visualstudio.com,dist.nuget.org,docs.github.com,dot.net,dotnet.microsoft.com,dotnetcli.blob.core.windows.net,github-cloud.githubusercontent.com,github-cloud.s3.amazonaws.com,github.blog,github.com,github.githubassets.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,lfs.github.com,microsoft.com,nuget.org,nuget.pkg.github.com,nugetregistryv2prod.blob.core.windows.net,objects.githubusercontent.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,oneocsp.microsoft.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,pkgs.dev.azure.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com,www.microsoft.com" + GITHUB_SERVER_URL: ${{ github.server_url }} + GITHUB_API_URL: ${{ github.api_url }} + GH_AW_COMMAND: review + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/collect_ndjson_output.cjs'); + await main(); + - name: Parse agent logs for step summary + if: always() + uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + env: + GH_AW_AGENT_OUTPUT: /tmp/gh-aw/sandbox/agent/logs/ + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_copilot_log.cjs'); + await main(); + - name: Parse MCP Gateway logs for step summary + if: always() + id: parse-mcp-gateway + uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_mcp_gateway_log.cjs'); + await main(); + - name: Print firewall logs + if: always() + continue-on-error: true + env: + AWF_LOGS_DIR: /tmp/gh-aw/sandbox/firewall/logs + run: | + # Fix permissions on firewall logs so they can be uploaded as artifacts + # AWF runs with sudo, creating files owned by root + sudo chmod -R a+r /tmp/gh-aw/sandbox/firewall/logs 2>/dev/null || true + # Only run awf logs summary if awf command exists (it may not be installed if workflow failed before install step) + if command -v awf &> /dev/null; then + awf logs summary | tee -a "$GITHUB_STEP_SUMMARY" + else + echo 'AWF binary not installed, skipping firewall log summary' + fi + - name: Parse token usage for step summary + if: always() + continue-on-error: true + uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_token_usage.cjs'); + await main(); + - name: Write agent output placeholder if missing + if: always() + run: | + if [ ! -f /tmp/gh-aw/agent_output.json ]; then + echo '{"items":[]}' > /tmp/gh-aw/agent_output.json + fi + - name: Upload agent artifacts + if: always() + continue-on-error: true + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: agent + path: | + /tmp/gh-aw/aw-prompts/prompt.txt + /tmp/gh-aw/sandbox/agent/logs/ + /tmp/gh-aw/redacted-urls.log + /tmp/gh-aw/mcp-logs/ + /tmp/gh-aw/proxy-logs/ + !/tmp/gh-aw/proxy-logs/proxy-tls/ + /tmp/gh-aw/agent_usage.json + /tmp/gh-aw/agent-stdio.log + /tmp/gh-aw/agent/ + /tmp/gh-aw/github_rate_limits.jsonl + /tmp/gh-aw/safeoutputs.jsonl + /tmp/gh-aw/agent_output.json + /tmp/gh-aw/aw-*.patch + /tmp/gh-aw/aw-*.bundle + /tmp/gh-aw/sandbox/firewall/logs/ + /tmp/gh-aw/sandbox/firewall/audit/ + if-no-files-found: ignore + + conclusion: + needs: + - activation + - agent + - detection + - safe_outputs + if: > + always() && (needs.agent.result != 'skipped' || needs.activation.outputs.lockdown_check_failed == 'true' || + needs.activation.outputs.stale_lock_file_failed == 'true') + runs-on: ubuntu-slim + permissions: + contents: read + pull-requests: write + concurrency: + group: "gh-aw-conclusion-android-tools-reviewer" + cancel-in-progress: false + outputs: + incomplete_count: ${{ steps.report_incomplete.outputs.incomplete_count }} + noop_message: ${{ steps.noop.outputs.noop_message }} + tools_reported: ${{ steps.missing_tool.outputs.tools_reported }} + total_count: ${{ steps.missing_tool.outputs.total_count }} + steps: + - name: Setup Scripts + id: setup + uses: github/gh-aw-actions/setup@ba90f2186d7ad780ec640f364005fa24e797b360 # v0.68.3 + with: + destination: ${{ runner.temp }}/gh-aw/actions + job-name: ${{ github.job }} + trace-id: ${{ needs.activation.outputs.setup-trace-id }} + - name: Download agent output artifact + id: download-agent-output + continue-on-error: true + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: agent + path: /tmp/gh-aw/ + - name: Setup agent output environment variable + id: setup-agent-output-env + if: steps.download-agent-output.outcome == 'success' + run: | + mkdir -p /tmp/gh-aw/ + find "/tmp/gh-aw/" -type f -print + echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT" + - name: Process no-op messages + id: noop + uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + env: + GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} + GH_AW_NOOP_MAX: "1" + GH_AW_WORKFLOW_NAME: "Android Tools PR Reviewer" + GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }} + GH_AW_NOOP_REPORT_AS_ISSUE: "true" + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_noop_message.cjs'); + await main(); + - name: Log detection run + id: detection_runs + uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + env: + GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} + GH_AW_WORKFLOW_NAME: "Android Tools PR Reviewer" + GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + GH_AW_DETECTION_CONCLUSION: ${{ needs.detection.outputs.detection_conclusion }} + GH_AW_DETECTION_REASON: ${{ needs.detection.outputs.detection_reason }} + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_detection_runs.cjs'); + await main(); + - name: Record missing tool + id: missing_tool + uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + env: + GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} + GH_AW_MISSING_TOOL_CREATE_ISSUE: "true" + GH_AW_WORKFLOW_NAME: "Android Tools PR Reviewer" + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/missing_tool.cjs'); + await main(); + - name: Record incomplete + id: report_incomplete + uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + env: + GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} + GH_AW_REPORT_INCOMPLETE_CREATE_ISSUE: "true" + GH_AW_WORKFLOW_NAME: "Android Tools PR Reviewer" + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/report_incomplete_handler.cjs'); + await main(); + - name: Handle agent failure + id: handle_agent_failure + if: always() + uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + env: + GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} + GH_AW_WORKFLOW_NAME: "Android Tools PR Reviewer" + GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }} + GH_AW_WORKFLOW_ID: "android-tools-reviewer" + GH_AW_ENGINE_ID: "copilot" + GH_AW_SECRET_VERIFICATION_RESULT: ${{ needs.activation.outputs.secret_verification_result }} + GH_AW_CHECKOUT_PR_SUCCESS: ${{ needs.agent.outputs.checkout_pr_success }} + GH_AW_INFERENCE_ACCESS_ERROR: ${{ needs.agent.outputs.inference_access_error }} + GH_AW_MCP_POLICY_ERROR: ${{ needs.agent.outputs.mcp_policy_error }} + GH_AW_AGENTIC_ENGINE_TIMEOUT: ${{ needs.agent.outputs.agentic_engine_timeout }} + GH_AW_MODEL_NOT_SUPPORTED_ERROR: ${{ needs.agent.outputs.model_not_supported_error }} + GH_AW_LOCKDOWN_CHECK_FAILED: ${{ needs.activation.outputs.lockdown_check_failed }} + GH_AW_STALE_LOCK_FILE_FAILED: ${{ needs.activation.outputs.stale_lock_file_failed }} + GH_AW_GROUP_REPORTS: "false" + GH_AW_FAILURE_REPORT_AS_ISSUE: "true" + GH_AW_TIMEOUT_MINUTES: "20" + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_agent_failure.cjs'); + await main(); + - name: Update reaction comment with completion status + id: conclusion + uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + env: + GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} + GH_AW_COMMENT_ID: ${{ needs.activation.outputs.comment_id }} + GH_AW_COMMENT_REPO: ${{ needs.activation.outputs.comment_repo }} + GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + GH_AW_WORKFLOW_NAME: "Android Tools PR Reviewer" + GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }} + GH_AW_DETECTION_CONCLUSION: ${{ needs.detection.outputs.detection_conclusion }} + GH_AW_DETECTION_REASON: ${{ needs.detection.outputs.detection_reason }} + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/notify_comment_error.cjs'); + await main(); + + detection: + needs: + - activation + - agent + if: > + always() && needs.agent.result != 'skipped' && (needs.agent.outputs.output_types != '' || needs.agent.outputs.has_patch == 'true') + runs-on: ubuntu-latest + permissions: + contents: read + outputs: + detection_conclusion: ${{ steps.detection_conclusion.outputs.conclusion }} + detection_reason: ${{ steps.detection_conclusion.outputs.reason }} + detection_success: ${{ steps.detection_conclusion.outputs.success }} + steps: + - name: Setup Scripts + id: setup + uses: github/gh-aw-actions/setup@ba90f2186d7ad780ec640f364005fa24e797b360 # v0.68.3 + with: + destination: ${{ runner.temp }}/gh-aw/actions + job-name: ${{ github.job }} + trace-id: ${{ needs.activation.outputs.setup-trace-id }} + - name: Download agent output artifact + id: download-agent-output + continue-on-error: true + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: agent + path: /tmp/gh-aw/ + - name: Setup agent output environment variable + id: setup-agent-output-env + if: steps.download-agent-output.outcome == 'success' + run: | + mkdir -p /tmp/gh-aw/ + find "/tmp/gh-aw/" -type f -print + echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT" + - name: Checkout repository for patch context + if: needs.agent.outputs.has_patch == 'true' + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + # --- Threat Detection --- + - name: Clean stale firewall files from agent artifact + run: | + rm -rf /tmp/gh-aw/sandbox/firewall/logs + rm -rf /tmp/gh-aw/sandbox/firewall/audit + - name: Download container images + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.25.20 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.20 ghcr.io/github/gh-aw-firewall/squid:0.25.20 + - name: Check if detection needed + id: detection_guard + if: always() + env: + OUTPUT_TYPES: ${{ needs.agent.outputs.output_types }} + HAS_PATCH: ${{ needs.agent.outputs.has_patch }} + run: | + if [[ -n "$OUTPUT_TYPES" || "$HAS_PATCH" == "true" ]]; then + echo "run_detection=true" >> "$GITHUB_OUTPUT" + echo "Detection will run: output_types=$OUTPUT_TYPES, has_patch=$HAS_PATCH" + else + echo "run_detection=false" >> "$GITHUB_OUTPUT" + echo "Detection skipped: no agent outputs or patches to analyze" + fi + - name: Clear MCP configuration for detection + if: always() && steps.detection_guard.outputs.run_detection == 'true' + run: | + rm -f /tmp/gh-aw/mcp-config/mcp-servers.json + rm -f /home/runner/.copilot/mcp-config.json + rm -f "$GITHUB_WORKSPACE/.gemini/settings.json" + - name: Prepare threat detection files + if: always() && steps.detection_guard.outputs.run_detection == 'true' + run: | + mkdir -p /tmp/gh-aw/threat-detection/aw-prompts + cp /tmp/gh-aw/aw-prompts/prompt.txt /tmp/gh-aw/threat-detection/aw-prompts/prompt.txt 2>/dev/null || true + cp /tmp/gh-aw/agent_output.json /tmp/gh-aw/threat-detection/agent_output.json 2>/dev/null || true + for f in /tmp/gh-aw/aw-*.patch; do + [ -f "$f" ] && cp "$f" /tmp/gh-aw/threat-detection/ 2>/dev/null || true + done + for f in /tmp/gh-aw/aw-*.bundle; do + [ -f "$f" ] && cp "$f" /tmp/gh-aw/threat-detection/ 2>/dev/null || true + done + echo "Prepared threat detection files:" + ls -la /tmp/gh-aw/threat-detection/ 2>/dev/null || true + - name: Setup threat detection + if: always() && steps.detection_guard.outputs.run_detection == 'true' + uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + env: + WORKFLOW_NAME: "Android Tools PR Reviewer" + WORKFLOW_DESCRIPTION: "No description provided" + HAS_PATCH: ${{ needs.agent.outputs.has_patch }} + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/setup_threat_detection.cjs'); + await main(); + - name: Ensure threat-detection directory and log + if: always() && steps.detection_guard.outputs.run_detection == 'true' + run: | + mkdir -p /tmp/gh-aw/threat-detection + touch /tmp/gh-aw/threat-detection/detection.log + - name: Install GitHub Copilot CLI + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.21 + env: + GH_HOST: github.com + - name: Install AWF binary + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.20 + - name: Execute GitHub Copilot CLI + if: always() && steps.detection_guard.outputs.run_detection == 'true' + id: detection_agentic_execution + # Copilot CLI tool arguments (sorted): + timeout-minutes: 20 + run: | + set -o pipefail + touch /tmp/gh-aw/agent-step-summary.md + (umask 177 && touch /tmp/gh-aw/threat-detection/detection.log) + # shellcheck disable=SC1003 + sudo -E awf --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --env-all --exclude-env COPILOT_GITHUB_TOKEN --allow-domains api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,github.com,host.docker.internal,telemetry.enterprise.githubcopilot.com --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --image-tag 0.25.20 --skip-pull --enable-api-proxy \ + -- /bin/bash -c 'node ${RUNNER_TEMP}/gh-aw/actions/copilot_driver.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --add-dir "${GITHUB_WORKSPACE}" --prompt "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log + env: + COPILOT_AGENT_RUNNER_TYPE: STANDALONE + COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + COPILOT_MODEL: claude-opus-4.6 + GH_AW_PHASE: detection + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + GH_AW_VERSION: v0.68.3 + GITHUB_API_URL: ${{ github.api_url }} + GITHUB_AW: true + GITHUB_HEAD_REF: ${{ github.head_ref }} + GITHUB_REF_NAME: ${{ github.ref_name }} + GITHUB_SERVER_URL: ${{ github.server_url }} + GITHUB_STEP_SUMMARY: /tmp/gh-aw/agent-step-summary.md + GITHUB_WORKSPACE: ${{ github.workspace }} + GIT_AUTHOR_EMAIL: github-actions[bot]@users.noreply.github.com + GIT_AUTHOR_NAME: github-actions[bot] + GIT_COMMITTER_EMAIL: github-actions[bot]@users.noreply.github.com + GIT_COMMITTER_NAME: github-actions[bot] + XDG_CONFIG_HOME: /home/runner + - name: Upload threat detection log + if: always() && steps.detection_guard.outputs.run_detection == 'true' + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: detection + path: /tmp/gh-aw/threat-detection/detection.log + if-no-files-found: ignore + - name: Parse and conclude threat detection + id: detection_conclusion + if: always() + uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + env: + RUN_DETECTION: ${{ steps.detection_guard.outputs.run_detection }} + GH_AW_DETECTION_CONTINUE_ON_ERROR: "true" + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_threat_detection_results.cjs'); + await main(); + + pre_activation: + if: "github.event_name == 'issue_comment' && (startsWith(github.event.comment.body, '/review ') || startsWith(github.event.comment.body, '/review\n') || github.event.comment.body == '/review') && github.event.issue.pull_request != null || !(github.event_name == 'issue_comment')" + runs-on: ubuntu-slim + outputs: + activated: ${{ steps.check_membership.outputs.is_team_member == 'true' && steps.check_command_position.outputs.command_position_ok == 'true' }} + matched_command: ${{ steps.check_command_position.outputs.matched_command }} + setup-trace-id: ${{ steps.setup.outputs.trace-id }} + steps: + - name: Setup Scripts + id: setup + uses: github/gh-aw-actions/setup@ba90f2186d7ad780ec640f364005fa24e797b360 # v0.68.3 + with: + destination: ${{ runner.temp }}/gh-aw/actions + job-name: ${{ github.job }} + - name: Check team membership for command workflow + id: check_membership + uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + env: + GH_AW_REQUIRED_ROLES: "admin,maintainer,write" + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/check_membership.cjs'); + await main(); + - name: Check command position + id: check_command_position + uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + env: + GH_AW_COMMANDS: "[\"review\"]" + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/check_command_position.cjs'); + await main(); + + safe_outputs: + needs: + - activation + - agent + - detection + if: (!cancelled()) && needs.agent.result != 'skipped' && needs.detection.result == 'success' + runs-on: ubuntu-slim + permissions: + contents: read + pull-requests: write + timeout-minutes: 15 + env: + GH_AW_CALLER_WORKFLOW_ID: "${{ github.repository }}/android-tools-reviewer" + GH_AW_DETECTION_CONCLUSION: ${{ needs.detection.outputs.detection_conclusion }} + GH_AW_DETECTION_REASON: ${{ needs.detection.outputs.detection_reason }} + GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens }} + GH_AW_ENGINE_ID: "copilot" + GH_AW_ENGINE_MODEL: "claude-opus-4.6" + GH_AW_WORKFLOW_ID: "android-tools-reviewer" + GH_AW_WORKFLOW_NAME: "Android Tools PR Reviewer" + outputs: + code_push_failure_count: ${{ steps.process_safe_outputs.outputs.code_push_failure_count }} + code_push_failure_errors: ${{ steps.process_safe_outputs.outputs.code_push_failure_errors }} + create_discussion_error_count: ${{ steps.process_safe_outputs.outputs.create_discussion_error_count }} + create_discussion_errors: ${{ steps.process_safe_outputs.outputs.create_discussion_errors }} + process_safe_outputs_processed_count: ${{ steps.process_safe_outputs.outputs.processed_count }} + process_safe_outputs_temporary_id_map: ${{ steps.process_safe_outputs.outputs.temporary_id_map }} + steps: + - name: Setup Scripts + id: setup + uses: github/gh-aw-actions/setup@ba90f2186d7ad780ec640f364005fa24e797b360 # v0.68.3 + with: + destination: ${{ runner.temp }}/gh-aw/actions + job-name: ${{ github.job }} + trace-id: ${{ needs.activation.outputs.setup-trace-id }} + - name: Download agent output artifact + id: download-agent-output + continue-on-error: true + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: agent + path: /tmp/gh-aw/ + - name: Setup agent output environment variable + id: setup-agent-output-env + if: steps.download-agent-output.outcome == 'success' + run: | + mkdir -p /tmp/gh-aw/ + find "/tmp/gh-aw/" -type f -print + echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT" + - name: Configure GH_HOST for enterprise compatibility + id: ghes-host-config + shell: bash + run: | + # Derive GH_HOST from GITHUB_SERVER_URL so the gh CLI targets the correct + # GitHub instance (GHES/GHEC). On github.com this is a harmless no-op. + GH_HOST="${GITHUB_SERVER_URL#https://}" + GH_HOST="${GH_HOST#http://}" + echo "GH_HOST=${GH_HOST}" >> "$GITHUB_ENV" + - name: Process Safe Outputs + id: process_safe_outputs + uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + env: + GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} + GH_AW_ALLOWED_DOMAINS: "*.githubusercontent.com,*.vsblob.vsassets.io,aka.ms,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.nuget.org,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,azuresearch-usnc.nuget.org,azuresearch-ussc.nuget.org,builds.dotnet.microsoft.com,ci.dot.net,codeload.github.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,dc.services.visualstudio.com,dist.nuget.org,docs.github.com,dot.net,dotnet.microsoft.com,dotnetcli.blob.core.windows.net,github-cloud.githubusercontent.com,github-cloud.s3.amazonaws.com,github.blog,github.com,github.githubassets.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,lfs.github.com,microsoft.com,nuget.org,nuget.pkg.github.com,nugetregistryv2prod.blob.core.windows.net,objects.githubusercontent.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,oneocsp.microsoft.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,pkgs.dev.azure.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com,www.microsoft.com" + GITHUB_SERVER_URL: ${{ github.server_url }} + GITHUB_API_URL: ${{ github.api_url }} + GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"create_pull_request_review_comment\":{\"max\":50,\"side\":\"RIGHT\"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"},\"report_incomplete\":{},\"submit_pull_request_review\":{\"allowed_events\":[\"COMMENT\",\"REQUEST_CHANGES\"],\"max\":1}}" + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/safe_output_handler_manager.cjs'); + await main(); + - name: Upload Safe Outputs Items + if: always() + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: safe-outputs-items + path: | + /tmp/gh-aw/safe-output-items.jsonl + /tmp/gh-aw/temporary-id-map.json + if-no-files-found: ignore + diff --git a/external/xamarin-android-tools/.github/workflows/android-tools-reviewer.md b/external/xamarin-android-tools/.github/workflows/android-tools-reviewer.md new file mode 100644 index 00000000000..377a0d32950 --- /dev/null +++ b/external/xamarin-android-tools/.github/workflows/android-tools-reviewer.md @@ -0,0 +1,59 @@ +--- +on: + slash_command: + name: review + events: [pull_request_comment] + roles: [admin, maintainer, write] +permissions: + contents: read + pull-requests: read +engine: + id: copilot + model: claude-opus-4.6 +network: + allowed: + - defaults + - dotnet + - github + - "aka.ms" + - "microsoft.com" +tools: + github: + toolsets: [pull_requests, repos] + # Allow reading PR content from external/first-time contributors. + # The /review command is gated to maintainers, so only trusted users can trigger it. + min-integrity: none +safe-outputs: + create-pull-request-review-comment: + max: 50 + submit-pull-request-review: + max: 1 + allowed-events: [COMMENT, REQUEST_CHANGES] +--- + +# Android Tools PR Reviewer + +A maintainer commented `/review` on this pull request. Perform a thorough code review following the dotnet/android-tools review guidelines. + +## Instructions + +1. Read the review rules from `.github/skills/android-tools-reviewer/references/review-rules.md` — these contain the detailed patterns and anti-patterns to check for. +2. Read the review methodology from `.github/skills/android-tools-reviewer/SKILL.md` — this defines the review workflow, mindset, severity levels, and comment format. +3. Follow the skill's workflow to analyze the pull request: + - Gather context: read the diff and changed files + - For each changed file, read the **full source file** to understand surrounding context + - Form an independent assessment before reading the PR description + - Read the PR title and description — treat claims as things to verify + - Check CI status + - Analyze the diff against the review rules +4. Post your findings as inline review comments and a review summary. + +## Constraints + +- Only comment on added/modified lines visible in the diff. +- One issue per inline comment. +- If the same issue appears many times, flag it once listing all affected files. +- Don't flag what CI catches (compiler errors, linter issues). +- Avoid false positives — verify concerns given the full file context. +- **Never submit an APPROVE event.** Use COMMENT for clean PRs and REQUEST_CHANGES when issues are found. +- Prioritize: bugs > safety > performance > missing tests > duplication > consistency > documentation. diff --git a/external/xamarin-android-tools/.github/workflows/copilot-setup-steps.yml b/external/xamarin-android-tools/.github/workflows/copilot-setup-steps.yml index a843671128d..f304f3592aa 100644 --- a/external/xamarin-android-tools/.github/workflows/copilot-setup-steps.yml +++ b/external/xamarin-android-tools/.github/workflows/copilot-setup-steps.yml @@ -13,7 +13,11 @@ jobs: copilot-setup-steps: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 + - name: Install gh-aw extension + uses: github/gh-aw-actions/setup-cli@ba90f2186d7ad780ec640f364005fa24e797b360 # v0.68.3 + with: + version: v0.68.3 - uses: actions/setup-dotnet@v4 with: dotnet-version: "10.0.x" From dc991d0aae2a91addf2c19b70ac81cf52579a3ef Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Tue, 28 Apr 2026 02:49:39 -0500 Subject: [PATCH 273/308] [Tools.AndroidSdk] Remove unused `key_name` variables (#345) Delete two dead-code string.Format assignments that were never read: - OS.CheckRegistryKeyForExecutable (OS.cs) - OracleJdkLocations.GetOracleJdkPaths (OracleJdkLocations.cs) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Xamarin.Android.Tools.AndroidSdk/Jdks/OracleJdkLocations.cs | 1 - .../src/Xamarin.Android.Tools.AndroidSdk/OS.cs | 2 -- 2 files changed, 3 deletions(-) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Jdks/OracleJdkLocations.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Jdks/OracleJdkLocations.cs index 0e2a3b01eb3..a49f2ec7ee3 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Jdks/OracleJdkLocations.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Jdks/OracleJdkLocations.cs @@ -28,7 +28,6 @@ static IEnumerable GetOracleJdkPaths () string subkey = @"SOFTWARE\JavaSoft\Java Development Kit"; foreach (var wow64 in new[] { RegistryEx.Wow64.Key32, RegistryEx.Wow64.Key64 }) { - string key_name = string.Format (@"{0}\{1}\{2}", "HKLM", subkey, "CurrentVersion"); var currentVersion = RegistryEx.GetValueString (RegistryEx.LocalMachine, subkey, "CurrentVersion", wow64); if (!string.IsNullOrEmpty (currentVersion)) { diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/OS.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/OS.cs index 1d139613421..66a0b8fe6c0 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/OS.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/OS.cs @@ -179,8 +179,6 @@ IntPtr lpftLastWriteTime internal static bool CheckRegistryKeyForExecutable (UIntPtr key, string subkey, string valueName, RegistryEx.Wow64 wow64, string subdir, string exe) { try { - string key_name = string.Format (@"{0}\{1}\{2}", key == RegistryEx.CurrentUser ? "HKCU" : "HKLM", subkey, valueName); - var path = AndroidSdkBase.NullIfEmpty (RegistryEx.GetValueString (key, subkey, valueName, wow64)); if (path == null) { From 9899436cb2d3d822251d2a520f7187139afda412 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Tue, 28 Apr 2026 09:50:12 +0200 Subject: [PATCH 274/308] [BaseTasks] Enable nullable reference types (#344) ## Enable nullable reference types in Microsoft.Android.Build.BaseTasks - Add enable to the csproj - Add nullable to the csproj - Fix all nullable warnings across source files: - AndroidRidAbiHelper.cs: nullable return types for GetNativeLibraryAbi - AndroidRunToolTask.cs: nullable BaseDirectory and androidErrorRegex - AsyncTask.cs: nullable code/file params in Log methods, IDictionary - Files.cs: nullable HashZip returns, out params, null-safe DeleteFile, CopyIfZipChanged - LinePreservedXmlWriter.cs: nullable nav field and IXmlLineInfo locals - MSBuildExtensions.cs: nullable return types for generic TaskObject methods - Add CopyIfZipChanged regression tests Co-authored-by: Jonathan Peppers --- .../AndroidRidAbiHelper.cs | 4 +- .../AndroidRunToolTask.cs | 4 +- .../AsyncTask.cs | 18 +++--- .../Files.cs | 40 +++++++------ .../LinePreservedXmlWriter.cs | 8 +-- .../MSBuildExtensions.cs | 4 +- .../Microsoft.Android.Build.BaseTasks.csproj | 2 + .../FilesTests.cs | 58 +++++++++++++++++++ 8 files changed, 100 insertions(+), 38 deletions(-) diff --git a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/AndroidRidAbiHelper.cs b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/AndroidRidAbiHelper.cs index 01aef16b703..009181afffd 100644 --- a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/AndroidRidAbiHelper.cs +++ b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/AndroidRidAbiHelper.cs @@ -16,7 +16,7 @@ public static class AndroidRidAbiHelper "x86_64", }; - public static string GetNativeLibraryAbi (string lib) + public static string? GetNativeLibraryAbi (string lib) { if (string.IsNullOrEmpty (lib)) return null; @@ -47,7 +47,7 @@ public static string GetNativeLibraryAbi (string lib) return null; } - public static string GetNativeLibraryAbi (ITaskItem lib) + public static string? GetNativeLibraryAbi (ITaskItem lib) { // If Abi is explicitly specified, simply return it. var lib_abi = lib.GetMetadata ("Abi"); diff --git a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/AndroidRunToolTask.cs b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/AndroidRunToolTask.cs index db8bf8b592d..64fd1ac5ffc 100644 --- a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/AndroidRunToolTask.cs +++ b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/AndroidRunToolTask.cs @@ -27,7 +27,7 @@ protected virtual Regex ErrorRegex { } /* This gets pre-pended to any filenames that we get from error strings */ - protected string BaseDirectory { get; set; } + protected string? BaseDirectory { get; set; } // Aapt errors looks like this: // res\layout\main.axml:7: error: No resource identifier found for attribute 'id2' in package 'android' (TaskId:22) @@ -37,7 +37,7 @@ protected virtual Regex ErrorRegex { // Warnings can be like this // aapt2 W 09-17 18:15:27 98796 12879433 ApkAssets.cpp:138] resources.arsc in APK 'android.jar' is compressed. // Look for them and convert them to MSBuild compatible errors. - static Regex androidErrorRegex; + static Regex? androidErrorRegex; public static Regex AndroidErrorRegex { get { if (androidErrorRegex == null) diff --git a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/AsyncTask.cs b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/AsyncTask.cs index 7a8a6072c6c..5ff29a9275c 100644 --- a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/AsyncTask.cs +++ b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/AsyncTask.cs @@ -105,7 +105,7 @@ public void LogDebugTaskItems (string message, ITaskItem [] items) LogDebugMessage (" {0}", item.ItemSpec); } - public void LogTelemetry (string eventName, IDictionary properties) + public void LogTelemetry (string eventName, IDictionary properties) { if (uiThreadId == Thread.CurrentThread.ManagedThreadId) { #pragma warning disable 618 @@ -157,16 +157,16 @@ public void LogError (string message) public void LogError (string message, params object [] messageArgs) => LogCodedError (code: null, message: string.Format (message, messageArgs)); - public void LogCodedError (string code, string message) + public void LogCodedError (string? code, string message) => LogCodedError (code: code, message: message, file: null, lineNumber: 0); - public void LogCodedError (string code, string message, params object [] messageArgs) + public void LogCodedError (string? code, string message, params object [] messageArgs) => LogCodedError (code: code, message: string.Format (message, messageArgs), file: null, lineNumber: 0); - public void LogCodedError (string code, string file, int lineNumber, string message, params object [] messageArgs) + public void LogCodedError (string? code, string? file, int lineNumber, string message, params object [] messageArgs) => LogCodedError (code: code, message: string.Format (message, messageArgs), file: file, lineNumber: lineNumber); - public void LogCodedError (string code, string message, string file, int lineNumber) + public void LogCodedError (string? code, string message, string? file, int lineNumber) { if (uiThreadId == Thread.CurrentThread.ManagedThreadId) { #pragma warning disable 618 @@ -206,16 +206,16 @@ public void LogWarning (string message) public void LogWarning (string message, params object [] messageArgs) => LogCodedWarning (code: null, message: string.Format (message, messageArgs)); - public void LogCodedWarning (string code, string message) + public void LogCodedWarning (string? code, string message) => LogCodedWarning (code: code, message: message, file: null, lineNumber: 0); - public void LogCodedWarning (string code, string message, params object [] messageArgs) + public void LogCodedWarning (string? code, string message, params object [] messageArgs) => LogCodedWarning (code: code, message: string.Format (message, messageArgs), file: null, lineNumber: 0); - public void LogCodedWarning (string code, string file, int lineNumber, string message, params object [] messageArgs) + public void LogCodedWarning (string? code, string? file, int lineNumber, string message, params object [] messageArgs) => LogCodedWarning (code: code, message: string.Format (message, messageArgs), file: file, lineNumber: lineNumber); - public void LogCodedWarning (string code, string message, string file, int lineNumber) + public void LogCodedWarning (string? code, string message, string? file, int lineNumber) { if (uiThreadId == Thread.CurrentThread.ManagedThreadId) { #pragma warning disable 618 diff --git a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Files.cs b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Files.cs index f6256549126..de4194273d2 100644 --- a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Files.cs +++ b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Files.cs @@ -281,9 +281,10 @@ public static bool CopyIfStreamChangedOnce (Stream stream, string destination) public static bool CopyIfZipChanged (Stream source, string destination) { - string hash; - if (HasZipChanged (source, destination, out hash)) { - Directory.CreateDirectory (Path.GetDirectoryName (destination)); + if (HasZipChanged (source, destination, out _)) { + var directory = Path.GetDirectoryName (destination); + if (!string.IsNullOrEmpty (directory)) + Directory.CreateDirectory (directory); source.Position = 0; using (var f = File.Create (destination)) { source.CopyTo (f); @@ -298,9 +299,10 @@ public static bool CopyIfZipChanged (Stream source, string destination) public static bool CopyIfZipChanged (string source, string destination) { - string hash; - if (HasZipChanged (source, destination, out hash)) { - Directory.CreateDirectory (Path.GetDirectoryName (destination)); + if (HasZipChanged (source, destination, out _)) { + var directory = Path.GetDirectoryName (destination); + if (!string.IsNullOrEmpty (directory)) + Directory.CreateDirectory (directory); File.Copy (source, destination, true); File.SetLastWriteTimeUtc (destination, DateTime.UtcNow); @@ -310,16 +312,16 @@ public static bool CopyIfZipChanged (string source, string destination) return false; } - public static bool HasZipChanged (Stream source, string destination, out string hash) + public static bool HasZipChanged (Stream source, string destination, out string? hash) { hash = null; - string src_hash = hash = HashZip (source); + string? src_hash = hash = HashZip (source); if (!File.Exists (destination)) return true; - string dst_hash = HashZip (destination); + string? dst_hash = HashZip (destination); if (src_hash == null || dst_hash == null) return true; @@ -327,18 +329,18 @@ public static bool HasZipChanged (Stream source, string destination, out string return src_hash != dst_hash; } - public static bool HasZipChanged (string source, string destination, out string hash) + public static bool HasZipChanged (string source, string destination, out string? hash) { hash = null; if (!File.Exists (source)) return true; - string src_hash = hash = HashZip (source); + string? src_hash = hash = HashZip (source); if (!File.Exists (destination)) return true; - string dst_hash = HashZip (destination); + string? dst_hash = HashZip (destination); if (src_hash == null || dst_hash == null) return true; @@ -397,7 +399,7 @@ public static bool HasBytesChanged (byte [] bytes, string destination) return false; } - static string HashZip (Stream stream) + static string? HashZip (Stream stream) { string hashes = String.Empty; @@ -413,7 +415,7 @@ static string HashZip (Stream stream) return hashes; } - static string HashZip (string filename) + static string? HashZip (string filename) { string hashes = String.Empty; @@ -446,14 +448,14 @@ public static bool ZipAny (string filename, Func filter) } [Obsolete ("Use the overload that accepts a TaskLoggingHelper parameter.")] - public static bool ExtractAll (ZipArchive zip, string destination, Action progressCallback, - Func modifyCallback, Func deleteCallback, Func skipCallback) + public static bool ExtractAll (ZipArchive zip, string destination, Action? progressCallback, + Func? modifyCallback, Func? deleteCallback, Func? skipCallback) { return ExtractAll (zip, destination, progressCallback, modifyCallback, deleteCallback, skipCallback, log: null); } - public static bool ExtractAll (ZipArchive zip, string destination, Action progressCallback = null, Func modifyCallback = null, - Func deleteCallback = null, Func skipCallback = null, TaskLoggingHelper log = null) + public static bool ExtractAll (ZipArchive zip, string destination, Action? progressCallback = null, Func? modifyCallback = null, + Func? deleteCallback = null, Func? skipCallback = null, TaskLoggingHelper? log = null) { int i = 0; int total = (int)zip.EntryCount; @@ -643,7 +645,7 @@ public static void CleanBOM (string filePath) if (string.IsNullOrEmpty (filePath) || !File.Exists (filePath)) return; - string temp = null; + string? temp = null; try { using (var input = File.OpenRead (filePath)) { // Check if the file actually has a BOM diff --git a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/LinePreservedXmlWriter.cs b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/LinePreservedXmlWriter.cs index a2dd0015a91..d2cd82e1d39 100644 --- a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/LinePreservedXmlWriter.cs +++ b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/LinePreservedXmlWriter.cs @@ -33,13 +33,13 @@ internal LinePreservedXmlWriter (LinePreservedTextWriter w) this.tw = w; } - XPathNavigator nav; + XPathNavigator? nav; public override void WriteNode (XPathNavigator navigator, bool defattr) { - XPathNavigator bak = nav; + XPathNavigator? bak = nav; this.nav = navigator; - IXmlLineInfo li = navigator as IXmlLineInfo; + IXmlLineInfo? li = navigator as IXmlLineInfo; if (li != null) tw.ProceedTo (li.LineNumber, li.LinePosition); base.WriteNode (navigator, defattr); @@ -60,7 +60,7 @@ public override void WriteStartElement (string prefix, string localName, string base.WriteStartElement (prefix, localName, namespaceUri); } - void Proceed (IXmlLineInfo li) + void Proceed (IXmlLineInfo? li) { if (li == null || !li.HasLineInfo ()) return; diff --git a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildExtensions.cs b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildExtensions.cs index 323c874e35b..dc1f55a17bd 100644 --- a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildExtensions.cs +++ b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildExtensions.cs @@ -302,7 +302,7 @@ public static object GetRegisteredTaskObjectAssemblyLocal (this IBuildEngine4 en /// Or you can use the `ProjectSpecificTaskObjectKey` method of the `AndroidTask` to generate /// a project specific key if needed. /// - public static T GetRegisteredTaskObjectAssemblyLocal (this IBuildEngine4 engine, object key, RegisteredTaskObjectLifetime lifetime) + public static T? GetRegisteredTaskObjectAssemblyLocal (this IBuildEngine4 engine, object key, RegisteredTaskObjectLifetime lifetime) where T : class => engine.GetRegisteredTaskObject ((AssemblyLocation, key), lifetime) as T; @@ -324,7 +324,7 @@ public static object UnregisterTaskObjectAssemblyLocal (this IBuildEngine4 engin /// Or you can use the `ProjectSpecificTaskObjectKey` method of the `AndroidTask` to generate /// a project specific key if needed. /// - public static T UnregisterTaskObjectAssemblyLocal (this IBuildEngine4 engine, object key, RegisteredTaskObjectLifetime lifetime) + public static T? UnregisterTaskObjectAssemblyLocal (this IBuildEngine4 engine, object key, RegisteredTaskObjectLifetime lifetime) where T : class => engine.UnregisterTaskObject ((AssemblyLocation, key), lifetime) as T; } diff --git a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Microsoft.Android.Build.BaseTasks.csproj b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Microsoft.Android.Build.BaseTasks.csproj index 6e5cfe6fef3..6a23bfb7999 100644 --- a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Microsoft.Android.Build.BaseTasks.csproj +++ b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Microsoft.Android.Build.BaseTasks.csproj @@ -10,6 +10,8 @@ ..\..\product.snk $(VendorPrefix)Microsoft.Android.Build.BaseTasks$(VendorSuffix) latest + enable + nullable diff --git a/external/xamarin-android-tools/tests/Microsoft.Android.Build.BaseTasks-Tests/FilesTests.cs b/external/xamarin-android-tools/tests/Microsoft.Android.Build.BaseTasks-Tests/FilesTests.cs index b01c1ded6c0..df7171dd2c4 100644 --- a/external/xamarin-android-tools/tests/Microsoft.Android.Build.BaseTasks-Tests/FilesTests.cs +++ b/external/xamarin-android-tools/tests/Microsoft.Android.Build.BaseTasks-Tests/FilesTests.cs @@ -727,6 +727,64 @@ public void ToHashString () Assert.AreEqual (expected, Files.ToHexString (bytes)); } + [Test] + public void CopyIfZipChanged_Stream () + { + Directory.CreateDirectory (tempDir); + var destination = Path.Combine (tempDir, "dest.zip"); + + using (var zip = ZipArchive.Create (stream)) { + zip.AddEntry ("a.txt", "a", encoding); + } + stream.Position = 0; + + Assert.IsTrue (Files.CopyIfZipChanged (stream, destination), "Should copy on new file."); + FileAssert.Exists (destination); + Assert.IsFalse (Files.CopyIfZipChanged (stream, destination), "Should *not* copy when unchanged."); + } + + [Test] + public void CopyIfZipChanged_String () + { + Directory.CreateDirectory (tempDir); + var source = Path.Combine (tempDir, "source.zip"); + var destination = Path.Combine (tempDir, "dest.zip"); + + using (var zip = ZipArchive.Create (stream)) { + zip.AddEntry ("a.txt", "a", encoding); + } + stream.Position = 0; + using (var f = File.Create (source)) { + stream.CopyTo (f); + } + + Assert.IsTrue (Files.CopyIfZipChanged (source, destination), "Should copy on new file."); + FileAssert.Exists (destination); + Assert.IsFalse (Files.CopyIfZipChanged (source, destination), "Should *not* copy when unchanged."); + } + + [Test] + public void CopyIfZipChanged_BareFileName () + { + // Regression test: Path.GetDirectoryName("file.zip") returns "", + // which previously caused Directory.CreateDirectory("") to throw. + var cwd = Directory.GetCurrentDirectory (); + try { + Directory.CreateDirectory (tempDir); + Directory.SetCurrentDirectory (tempDir); + + using (var zip = ZipArchive.Create (stream)) { + zip.AddEntry ("a.txt", "a", encoding); + } + stream.Position = 0; + + Assert.IsTrue (Files.CopyIfZipChanged (stream, "bare.zip"), "Should copy bare filename."); + FileAssert.Exists (Path.Combine (tempDir, "bare.zip")); + } finally { + Directory.SetCurrentDirectory (cwd); + } + } + [Test] public void DeleteFile_NullLog_DoesNotThrow () { From d426de9bb2bcdc976e85c3ef4b0f6718941a3443 Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Thu, 30 Apr 2026 09:45:52 -0500 Subject: [PATCH 275/308] [copilot] Fix reviewer not posting body-only reviews (#352) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The gh-aw safe_outputs framework requires at least one create_pull_request_review_comment before submit_pull_request_review will work. When the reviewer gives a clean LGTM with no inline comments, the review silently fails with "No review context set." Update the workflow prompt and SKILL.md to require at least one inline comment on every review, even for clean PRs. ### Example - PR that triggered the bug: https://github.com/dotnet/android-tools/pull/325 - Broken workflow run: https://github.com/dotnet/android-tools/actions/runs/25168175699 - See the `safe_outputs` → `Process Safe Outputs` step: `"Submitting PR review (body-only, no inline comments)"` → `"No review context set - cannot submit review"` - Completion comment (with no actual review posted): https://github.com/dotnet/android-tools/pull/325#issuecomment-4352875794 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../.github/skills/android-tools-reviewer/SKILL.md | 2 +- .../.github/workflows/android-tools-reviewer.md | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/external/xamarin-android-tools/.github/skills/android-tools-reviewer/SKILL.md b/external/xamarin-android-tools/.github/skills/android-tools-reviewer/SKILL.md index 8feeaa4d948..7ded5441118 100644 --- a/external/xamarin-android-tools/.github/skills/android-tools-reviewer/SKILL.md +++ b/external/xamarin-android-tools/.github/skills/android-tools-reviewer/SKILL.md @@ -85,7 +85,7 @@ Post your findings directly: - **Inline comments** on specific lines of the diff with the severity, category, and explanation. - **Review summary** with the overall verdict (✅ LGTM, ⚠️ Needs Changes, or ❌ Reject), issue counts by severity, and positive callouts. -If no issues found **and CI is green**, submit with at most one or two 💡 suggestions and a positive summary. +If no issues found **and CI is green**, submit with one or two 💡 suggestions on key implementation lines and a positive summary. **Always post at least one inline comment** — the review submission framework requires it. **Copilot-authored PRs:** If the PR author is `Copilot` (the GitHub Copilot coding agent) and the verdict is ⚠️ Needs Changes or ❌ Reject, prefix the review summary with `@copilot ` so the comment automatically triggers Copilot to address the feedback. Do NOT add the prefix for ✅ LGTM verdicts. diff --git a/external/xamarin-android-tools/.github/workflows/android-tools-reviewer.md b/external/xamarin-android-tools/.github/workflows/android-tools-reviewer.md index 377a0d32950..d55aac0c9f0 100644 --- a/external/xamarin-android-tools/.github/workflows/android-tools-reviewer.md +++ b/external/xamarin-android-tools/.github/workflows/android-tools-reviewer.md @@ -50,6 +50,7 @@ A maintainer commented `/review` on this pull request. Perform a thorough code r ## Constraints +- **Always post at least one inline review comment**, even for clean PRs — add a brief 💡 observation on a key implementation line. The review framework requires at least one inline comment to submit the review. - Only comment on added/modified lines visible in the diff. - One issue per inline comment. - If the same issue appears many times, flag it once listing all affected files. From b2bd80dda1b036cbf172e204610f742bbf15155c Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Mon, 4 May 2026 09:19:02 -0500 Subject: [PATCH 276/308] [copilot] Split reviewer skill rules into conditional files (#355) Split the monolithic review-rules.md into focused files that can be conditionally loaded based on which files changed in a PR: Always loaded: - repo-conventions.md: repo-specific patterns (ProcessUtils, FileUtil, etc.) - ai-pitfalls.md: common AI code generation mistakes - security-rules.md: archive, path, and command safety Conditionally loaded: - csharp-rules.md: general C# guidance (when .cs files change) - msbuild-rules.md: MSBuild task guidance (when .targets/.props change) - testing-rules.md: test review guidance (when test files change) Updated skill.md step 5 to describe the conditional loading logic. ### Restore lost rule detail from split - Add back "No HttpClient injection (YAGNI)" rule to csharp-rules.md - Restore netstandard2.0 fallback example in ThrowIf rule - Restore "recommend creating one" guidance in null-object pattern - Restore IsNullOrEmpty/null-coalescing guidance and logical negation examples in null-forgiving operator rule ### Add 14 new rules from dotnet/android and align duplicates New rules added to csharp-rules.md from dotnet/android PR #11266: Async section (3): - Lock ordering - Avoid double-checked locking (prefer Lazy) - Singleton initialization completeness Error Handling (4): - Log messages must have context - Differentiate similar error messages - Assert boundary invariants - Initialize output parameters in all paths Performance (5): - HashSet.Add() already handles duplicates - Don't wrap a value in an interpolated string - Split() with count parameter - Cache repeated accessor calls - Extract throw helpers ([DoesNotReturn]) Code Organization (2): - sealed classes skip full Dispose - Use interfaces over concrete types Also: - Fixed ThrowIf rule to drop specific version number - Added "concurrent calls" to testing edge cases Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../skills/android-tools-reviewer/SKILL.md | 12 +- .../references/ai-pitfalls.md | 23 ++ .../references/csharp-rules.md | 106 +++++++++ .../references/msbuild-rules.md | 31 +++ .../references/repo-conventions.md | 58 +++++ .../references/review-rules.md | 208 ------------------ .../references/security-rules.md | 21 ++ .../references/testing-rules.md | 12 + .../workflows/android-tools-reviewer.md | 7 +- 9 files changed, 265 insertions(+), 213 deletions(-) create mode 100644 external/xamarin-android-tools/.github/skills/android-tools-reviewer/references/ai-pitfalls.md create mode 100644 external/xamarin-android-tools/.github/skills/android-tools-reviewer/references/csharp-rules.md create mode 100644 external/xamarin-android-tools/.github/skills/android-tools-reviewer/references/msbuild-rules.md create mode 100644 external/xamarin-android-tools/.github/skills/android-tools-reviewer/references/repo-conventions.md delete mode 100644 external/xamarin-android-tools/.github/skills/android-tools-reviewer/references/review-rules.md create mode 100644 external/xamarin-android-tools/.github/skills/android-tools-reviewer/references/security-rules.md create mode 100644 external/xamarin-android-tools/.github/skills/android-tools-reviewer/references/testing-rules.md diff --git a/external/xamarin-android-tools/.github/skills/android-tools-reviewer/SKILL.md b/external/xamarin-android-tools/.github/skills/android-tools-reviewer/SKILL.md index 7ded5441118..12d06251572 100644 --- a/external/xamarin-android-tools/.github/skills/android-tools-reviewer/SKILL.md +++ b/external/xamarin-android-tools/.github/skills/android-tools-reviewer/SKILL.md @@ -60,7 +60,17 @@ Review the CI results. **Never post ✅ LGTM if any required CI check is failing ### 5. Load review rules -Read `references/review-rules.md` from this skill's directory. +Based on the changed files from step 2, load the appropriate rule files from `references/`: + +**Always load:** +- `references/repo-conventions.md` — repo-specific patterns and conventions +- `references/ai-pitfalls.md` — common AI code generation mistakes +- `references/security-rules.md` — security review checklist + +**Conditionally load:** +- `references/csharp-rules.md` — if any `.cs` files changed +- `references/msbuild-rules.md` — if any `.targets`, `.props`, or `.projitems` files changed, or if changed `.cs` files are under `src/Microsoft.Android.Build.BaseTasks/` +- `references/testing-rules.md` — if any files under `tests/` changed or files with `Test` in the path changed ### 6. Analyze the diff diff --git a/external/xamarin-android-tools/.github/skills/android-tools-reviewer/references/ai-pitfalls.md b/external/xamarin-android-tools/.github/skills/android-tools-reviewer/references/ai-pitfalls.md new file mode 100644 index 00000000000..cfa109e854e --- /dev/null +++ b/external/xamarin-android-tools/.github/skills/android-tools-reviewer/references/ai-pitfalls.md @@ -0,0 +1,23 @@ +# AI Code Generation Pitfalls + +Patterns that AI-generated code consistently gets wrong. Always loaded during reviews. + +--- + +| Pattern | What to watch for | +|---------|------------------| +| **Reinventing the wheel** | AI creates new infrastructure instead of using existing utilities. ALWAYS check if a similar utility exists before accepting new wrapper code. This is the most expensive AI pattern — hundreds of lines of plausible code that duplicates what's already there. | +| **Over-engineering** | HttpClient injection "for testability", elevation auto-detection, speculative helper classes, unused overloads. If no caller needs it today, remove it. | +| **Swallowed errors** | AI catch blocks love to eat exceptions silently. Check EVERY catch block. Also check that exit codes are checked consistently. | +| **Ignoring target framework** | AI generates code for the newest .NET. Check every API call against the oldest supported target framework. | +| **Sloppy structure** | Multiple types in one file, block-scoped namespaces, #region directives, classes where records would do. New helpers marked `public` when `internal` suffices. | +| **Confidently wrong domain facts** | AI once claimed `ANDROID_SDK_ROOT` was the recommended variable (it's deprecated). Always verify domain-specific claims against official docs. | +| **Over-mocking** | Not everything needs to be mocked. Integration tests with `Assert.Ignore` on failure are fine and catch real API changes that mocks never will. | +| **Docs describe intent not reality** | AI doc comments often describe what the code *should* do, not what it *actually* does. Review doc comments against the implementation. | +| **Unused parameters** | AI adds `CancellationToken` parameters but never observes them, or accepts `additionalArgs` as a string and interpolates it into a command. Unused CancellationToken is a broken contract; string args are injection risks. | +| **Null-forgiving operator (`!`)** | The postfix `!` null-forgiving operator (e.g., `foo!.Bar`) is banned in most codebases. If the value can be null, add a proper null check. If it can't be null, make the parameter/variable non-nullable. AI frequently sprinkles `!` to silence the compiler — this turns compile-time warnings into runtime `NullReferenceException`s. Use `IsNullOrEmpty()` extension methods or null-coalescing instead. Note: this rule is about the postfix `!` operator, not the logical negation `!` (e.g., `if (!someBool)` or `if (!string.IsNullOrEmpty (s))`). | +| **`Debug.WriteLine` for logging** | AI catch blocks often log with `System.Diagnostics.Debug.WriteLine()` or `Console.WriteLine()` — neither integrates with most codebase logger patterns. | +| **`git commit --amend`** | AI uses `--amend` on commits that are already pushed or belong to another author. Always create new commits — the maintainer will squash as needed. | +| **Commit messages omit non-obvious choices** | Behavioral decisions and known limitations belong in the commit message, not just the code. | +| **Typos in user-visible strings** | Users copy-paste error messages into bug reports. Get them right. | +| **Filler words in docs** | "So" at the start of a sentence adds nothing. Be direct. | diff --git a/external/xamarin-android-tools/.github/skills/android-tools-reviewer/references/csharp-rules.md b/external/xamarin-android-tools/.github/skills/android-tools-reviewer/references/csharp-rules.md new file mode 100644 index 00000000000..c826d9bae45 --- /dev/null +++ b/external/xamarin-android-tools/.github/skills/android-tools-reviewer/references/csharp-rules.md @@ -0,0 +1,106 @@ +# C# Review Rules + +General C# and .NET guidance for code reviews. Applicable to any .NET repository. + +--- + +## 1. Target Framework Compatibility + +When a library multi-targets (e.g., `netstandard2.0` + a modern TFM), every API call must work on all targets. + +| Check | What to look for | +|-------|-----------------| +| **netstandard2.0 API surface** | Methods/overloads that only exist on net5+. Common traps: `HttpContent.ReadAsStringAsync(CancellationToken)`, `ProcessStartInfo.ArgumentList`, `Environment.IsPrivilegedProcess`, `ArrayPool` (needs `System.Buffers` package on ns2.0). When unsure, check MS Learn docs. | +| **C# language features** | `init` accessors, `required` keyword, file-scoped types, raw string literals — may need polyfills or `#if` guards. | +| **Conditional compilation** | New API usage should be behind `#if NET5_0_OR_GREATER` (or similar) with a fallback for netstandard2.0. | + +--- + +## 2. Async & Cancellation Patterns + +| Check | What to look for | +|-------|-----------------| +| **CancellationToken propagation** | Every `async` method that accepts a `CancellationToken` must pass it to ALL downstream async calls (`GetAsync`, `ReadAsStreamAsync`, `SendAsync`, etc.). A token that's accepted but never used is a broken contract. | +| **OperationCanceledException** | Catch-all blocks (`catch (Exception)`) must NOT swallow `OperationCanceledException`. Either catch it explicitly first and rethrow, or use a type filter. | +| **GetStringAsync** | On netstandard2.0, `GetStringAsync(url)` doesn't accept a `CancellationToken`. Use `GetAsync(url, ct)` + `ReadAsStringAsync()` instead. | +| **Thread safety of shared state** | If a new field or property can be accessed from multiple threads (e.g., static caches, event handlers), verify thread-safe access: `ConcurrentDictionary`, `Interlocked`, or explicit locks. A `Dictionary` read concurrently with a write is undefined behavior. | +| **Lock ordering** | If code acquires multiple locks, the order must be consistent everywhere. Document the ordering. Inconsistent ordering → deadlock. | +| **Avoid double-checked locking** | The double-checked locking (DCL) pattern is error-prone and [discouraged by Microsoft](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/volatile). Prefer `Lazy` or `LazyInitializer.EnsureInitialized()` — they handle thread-safe initialization correctly. If DCL is truly necessary (e.g., for performance in a hot path with evidence), verify all initialization completes before the field is assigned and no thread can observe a partially-initialized instance. | +| **Singleton initialization completeness** | When a singleton is initialized behind a lock, ensure ALL setup steps (not just construction) complete before publishing the instance. If `Initialize()` does `instance = new Foo(); instance.Setup();`, another thread can see `instance != null` and use it before `Setup()` runs. Either do all setup in the constructor, or don't publish the reference until all setup is done. | + +--- + +## 3. Resource Management + +| Check | What to look for | +|-------|-----------------| +| **HttpClient must be static** | `HttpClient` instances should be `static readonly` fields, not per-instance. Creating/disposing `HttpClient` leads to socket exhaustion via `TIME_WAIT` accumulation. See [Microsoft guidelines](https://learn.microsoft.com/dotnet/fundamentals/networking/http/httpclient-guidelines). | +| **No HttpClient injection (YAGNI)** | Don't add `HttpClient` constructor parameters "for testability" unless a caller actually needs it today. The AI tends to over-engineer this. | +| **ArrayPool for large buffers** | Buffers ≥ 1KB (especially 80KB+ download buffers) should use `ArrayPool.Shared.Rent()` with `try/finally` return. Large allocations go to the LOH and are expensive to GC. | +| **IDisposable** | Classes that own unmanaged resources or expensive managed resources must implement `IDisposable` with a dispose guard (`ThrowIfDisposed`). | + +--- + +## 4. Error Handling + +| Check | What to look for | +|-------|-----------------| +| **No empty catch blocks** | Every `catch` must capture the `Exception` and log it (or rethrow). No silent swallowing. Even "expected" exceptions should be logged for diagnostics. | +| **Validate parameters** | Enum parameters and string-typed "mode" values must be validated — throw `ArgumentException` or `NotSupportedException` for unexpected values. Don't silently accept garbage. | +| **Fail fast on critical ops** | If an operation like `chmod` or checksum verification fails, throw immediately. Silently continuing leads to confusing downstream failures. | +| **Mandatory verification** | Checksum/hash verification must NOT be optional. If the checksum can't be fetched, the operation must fail — not proceed unverified. | +| **Include actionable details in exceptions** | Use `nameof` for parameter names. Include the unsupported value or unexpected type. Never throw empty exceptions. | +| **Log messages must have context** | A bare `"Operation failed"` could be anything. Include *what* you were doing and relevant identifiers so the message is actionable without a debugger. | +| **Differentiate similar error messages** | Two messages saying `"X failed"` for different operations are impossible to debug. Make each unique so log output is unambiguous. | +| **Assert boundary invariants** | If a name=value pair array must have even length, assert `(length % 2) == 0` before indexing `[i+1]`. Validate structural assumptions at the boundary, not deep inside the logic. | +| **Initialize output parameters in all paths** | Methods with `out` parameters must initialize them in all error paths, not just the success path. | +| **Use `ThrowIf` helpers where available** | Prefer `ArgumentOutOfRangeException.ThrowIfNegative`, `ArgumentNullException.ThrowIfNull`, etc. over manual if-then-throw patterns where these helpers are available. In `netstandard2.0` projects where these helpers are unavailable, use explicit checks such as `if (x is null) throw new ArgumentNullException (nameof (x));`. | +| **Challenge exception swallowing** | When a PR adds `catch { continue; }` or `catch { return null; }`, question whether the exception is truly expected or masking a deeper problem. The default should be to let unexpected exceptions propagate. | + +--- + +## 5. Performance + +| Check | What to look for | +|-------|-----------------| +| **XmlReader over LINQ XML** | For forward-only XML parsing (manifests, config files), use `XmlReader` — it's streaming and allocation-free. `XElement`/`XDocument` builds a full DOM tree. | +| **p/invoke over process spawn** | For single syscalls like `chmod`, use `[DllImport("libc")]` instead of spawning a child process. Process creation is orders of magnitude more expensive. | +| **Avoid intermediate collections** | Don't create two lists and `AddRange()` one to the other. Build a single list, or use LINQ to chain. | +| **Cache reusable arrays** | Char arrays for `string.Split()` (like whitespace chars) should be `static readonly` fields, not allocated on each call. | +| **Pre-allocate collections when size is known** | Use `new List(capacity)` or `new Dictionary(count)` when the size is known or estimable. Repeated resizing is O(n) allocation waste. | +| **Avoid closures in hot paths** | Lambdas that capture local variables allocate a closure object on every call. In loops or frequently-called methods, extract the lambda to a static method or cache the delegate. | +| **Place cheap checks before expensive ones** | In validation chains, test simple conditions (null checks, boolean flags) before allocating strings or doing I/O. Short-circuit with `&&`/`||`. | +| **Watch for O(n²)** | Nested loops over the same or related collections, repeated `.Contains()` on a `List`, or LINQ `.Where()` inside a loop are O(n²). Switch to `HashSet` or `Dictionary` for lookups. | +| **`HashSet.Add()` already handles duplicates** | Calling `.Contains()` before `.Add()` does the hash lookup twice. Just call `.Add()` — it returns `false` if the item already exists. | +| **Don't wrap a value in an interpolated string** | `$"{someString}"` creates an unnecessary `string.Format` call when `someString` is already a string. Just use `someString` directly. | +| **`Split()` with count parameter** | `line.Split(new char[]{'='}, 2)` prevents values containing `=` from being split incorrectly. Follow existing patterns that limit the split count. | +| **Cache repeated accessor calls** | If `foo.Bar.Baz` is used multiple times in a block, assign it to a local. This avoids repeated property evaluation and makes intent clearer. | +| **Extract throw helpers** | Code like `if (x) throw new SomeException(...)` in a frequently-called method prevents inlining. Extract into a `[DoesNotReturn]` helper so the JIT can inline the happy path. | + +--- + +## 6. Code Organization + +| Check | What to look for | +|-------|-----------------| +| **File-scoped namespaces** | New files should use `namespace Foo;` (not `namespace Foo { ... }`). Don't reformat existing files. | +| **Use `record` for data types** | Immutable data-carrier types (progress, version info, license info) should be `record` types. They get value equality, `ToString()`, and deconstruction for free. | +| **Remove unused code** | Dead methods, speculative helpers, and code "for later" should be removed. Ship only what's needed. | +| **No commented-out code** | Commented-out code should be deleted — Git has history. | +| **Reduce indentation with early returns** | Invert conditions with `continue` or early `return` to reduce nesting. `foreach (var x in items ?? Array.Empty())` eliminates null-check nesting. | +| **Don't initialize fields to default values** | `bool flag = false;` and `int count = 0;` are noise. The CLR zero-initializes all fields. Only assign when the initial value is non-default. | +| **`sealed` classes skip full Dispose** | A `sealed` class doesn't need `Dispose(bool)` + `GC.SuppressFinalize`. Just implement `IDisposable.Dispose()` directly. The full pattern is only for unsealed base classes. | +| **Use interfaces over concrete types** | Fields and parameters should prefer interfaces (`IMetadataResolver`) over concrete classes. When the implementation changes, you swap the implementation — not every call site. | + +--- + +## 7. API Design + +| Check | What to look for | +|-------|-----------------| +| **Return `IReadOnlyList` not `List`** | Public methods should return `IReadOnlyList` (or `IReadOnlyCollection`) instead of mutable `List`. Prevents callers from mutating internal state. | +| **New helpers default to `internal`** | New utility methods should be `internal` unless a confirmed external consumer needs them. Use `InternalsVisibleTo` for test access. | +| **Structured args, not string interpolation** | Additional arguments to processes should be `IEnumerable`, not a single `string` that gets interpolated. | +| **Honor `CancellationToken`** | If a method accepts a `CancellationToken`, it MUST observe it — register a callback to kill processes, check `IsCancellationRequested` in loops, pass it to downstream async calls. Don't just accept it for API completeness. | +| **Add overloads to reduce caller ceremony** | If every caller performs the same conversion before calling a method, the method should have an overload that accepts the unconverted type directly. | +| **Prefer C# pattern matching** | Use `is`, `switch` expressions, and property patterns instead of `if`/`else` type-check chains. Pattern matching is more concise, avoids casts, and enables exhaustiveness checks. | diff --git a/external/xamarin-android-tools/.github/skills/android-tools-reviewer/references/msbuild-rules.md b/external/xamarin-android-tools/.github/skills/android-tools-reviewer/references/msbuild-rules.md new file mode 100644 index 00000000000..617e80e42ab --- /dev/null +++ b/external/xamarin-android-tools/.github/skills/android-tools-reviewer/references/msbuild-rules.md @@ -0,0 +1,31 @@ +# MSBuild Review Rules + +Guidance for MSBuild tasks, targets, and props files. Applicable to any .NET repository with custom MSBuild infrastructure. + +--- + +## 1. Task Logging + +| Check | What to look for | +|-------|-----------------| +| **Don't use `Debug.WriteLine` or `Console.WriteLine`** | MSBuild tasks must use the task's logging facilities (e.g., `Log.LogMessage`, `Log.LogWarning`, `Log.LogError`). `Debug.WriteLine()` only reaches attached debuggers (invisible in CI). `Console.WriteLine()` bypasses MSBuild's logging pipeline entirely. | +| **Use appropriate log levels** | Use `MessageImportance.Low` for verbose diagnostics, `MessageImportance.Normal` for progress, and `MessageImportance.High` for important status. Don't spam high-importance messages. | + +--- + +## 2. Process Management in Tasks + +| Check | What to look for | +|-------|-----------------| +| **Don't redirect stdout/stderr without draining** | Background processes with `RedirectStandardOutput = true` must have async readers draining the output. Otherwise the OS pipe buffer fills and the child process deadlocks. For fire-and-forget processes, set `Redirect* = false`. | +| **Check exit codes consistently** | If one task operation checks the process exit code, ALL similar operations must too. Inconsistent error checking creates a false sense of safety. | +| **Include stdout in error diagnostics** | When a task captures stdout, pass it to error reporting so failure messages include all output, not just stderr. | + +--- + +## 3. Downstream Coordination + +| Check | What to look for | +|-------|-----------------| +| **Port, don't rewrite** | If a downstream consumer already has working logic for the same task, port it rather than writing new code. The existing code has real-world edge cases already handled. | +| **Draft downstream PR before merging** | Shared library changes should be accompanied by a draft PR in the consuming repo that proves the API actually works. Merge the library first, update the submodule pointer, then merge the consumer. | diff --git a/external/xamarin-android-tools/.github/skills/android-tools-reviewer/references/repo-conventions.md b/external/xamarin-android-tools/.github/skills/android-tools-reviewer/references/repo-conventions.md new file mode 100644 index 00000000000..9f5926190e5 --- /dev/null +++ b/external/xamarin-android-tools/.github/skills/android-tools-reviewer/references/repo-conventions.md @@ -0,0 +1,58 @@ +# Repo Conventions + + + +Patterns, naming, and style rules specific to this repository (`dotnet/android-tools`). Always loaded during reviews. + +--- + +## 1. Required Utilities + +| Check | What to look for | +|-------|-----------------| +| **Use `ProcessUtils`** | All process creation must go through `ProcessUtils.CreateProcessStartInfo()` and `ProcessUtils.StartProcess()`. No direct `new ProcessStartInfo()` or `Process.Start()`. Pass arguments as separate strings — `ProcessUtils` uses `ArgumentList` on net5+ and falls back to `Arguments` on netstandard2.0. | +| **Use `FileUtil`** | File extraction, downloads, checksum verification, and path operations belong in `FileUtil`. Don't duplicate file helpers in domain classes. | +| **Use `Action` logger** | Diagnostic output must use the existing `Action? logger` delegate pattern — never `System.Diagnostics.Debug.WriteLine()` or `Console.WriteLine()`. Methods that might be called from MSBuild tasks must accept a `logger` parameter and invoke it with the appropriate `TraceLevel`. See `AndroidSdkInfo.DefaultConsoleLogger` for the canonical implementation. | + +--- + +## 2. Null-Object Pattern + +| Check | What to look for | +|-------|-----------------| +| **Assign null-object sentinels early** | Methods accepting nullable dependencies (`IProgress?`, `ILogger?`, `Action?`) should assign a null-object sentinel early (e.g., `progress ??= NullProgress.Instance`) and then use the dependency without `?.` null checks throughout the method. Scattered `?.` calls are noise, invite missed spots, and signal a missing null-object type. If no null-object type exists yet, recommend creating one. | + +--- + +## 3. Naming & Constants + +| Check | What to look for | +|-------|-----------------| +| **Avoid ambiguous names** | Types that could collide with Android concepts (e.g., `ManifestComponent` vs `AndroidManifest.xml`) need disambiguating prefixes (e.g., `SdkManifestComponent`). | +| **No magic numbers** | Literal values like buffer sizes (`81920`), divisors (`1048576`), permission masks (`0x1ED` = 0755) should be named constants. | +| **Environment variable constants** | Use `EnvironmentVariableNames.AndroidHome` — not raw `"ANDROID_HOME"` strings. Typos in env var names produce silent, hard-to-debug failures. | +| **ANDROID_SDK_ROOT is deprecated** | Per [Android docs](https://developer.android.com/tools/variables#envar), use `ANDROID_HOME` everywhere. Do not introduce new references to `ANDROID_SDK_ROOT`. | + +--- + +## 4. File & Directory Patterns + +| Check | What to look for | +|-------|-----------------| +| **Version-based directories** | Install SDK/JDK to versioned paths (`cmdline-tools/19.0/`, not `cmdline-tools/latest/`). Versioned paths are self-documenting and allow side-by-side installs. | +| **Safe directory replacement** | Use move-with-rollback: rename existing → temp, move new in place, validate, delete temp only after validation succeeds. Never delete the backup before confirming the new install works. | +| **Cross-volume moves** | `Directory.Move` is really a rename — it fails across filesystems. Extract archives near the target path (same parent directory), or catch `IOException` and fall back to recursive copy + delete. | + +--- + +## 5. Code Style + +| Check | What to look for | +|-------|-----------------| +| **One type per file** | Each public class, struct, enum, or interface must be in its own `.cs` file named after the type. No multiple top-level types in a single file. | +| **No #region directives** | `#region` hides code and makes reviews harder. Remove them. This also applies to banner/section-separator comments (e.g., `// --- Device Tests ---`) — they signal the file should be split instead. | +| **Method names must reflect behavior** | If `CreateFoo()` sometimes returns an existing instance, rename it `GetOrCreateFoo()` or `GetFoo()`. | +| **Comments explain "why", not "what"** | `// increment i` adds nothing. `// skip the BOM — aapt2 chokes on it` explains intent. If a comment restates the code, delete it. | +| **Track TODOs as issues** | A `// TODO` hidden in code will be forgotten. File an issue and reference it in the comment. | +| **Remove stale comments** | If the code changed, update the comment. Comments that describe old behavior are misleading. | +| **Formatting** | Tabs (not spaces), K&R braces. See [Mono Coding Guidelines](http://www.mono-project.com/community/contributing/coding-guidelines/). Only format code you add or modify; never reformat existing lines. | diff --git a/external/xamarin-android-tools/.github/skills/android-tools-reviewer/references/review-rules.md b/external/xamarin-android-tools/.github/skills/android-tools-reviewer/references/review-rules.md deleted file mode 100644 index 711e29e5518..00000000000 --- a/external/xamarin-android-tools/.github/skills/android-tools-reviewer/references/review-rules.md +++ /dev/null @@ -1,208 +0,0 @@ -# Android Tools Review Rules - -Distilled from [CODE_REVIEW_POSTMORTEM.md](../../../../docs/CODE_REVIEW_POSTMORTEM.md) — 56 findings -from reviews by @jonathanpeppers on PRs #274, #275, #281–#284. - ---- - -## 1. Target Framework Compatibility - -This library multi-targets `netstandard2.0` + `net10.0` and runs inside Visual Studio on .NET -Framework. Every API call must work on both targets. - -| Check | What to look for | -|-------|-----------------| -| **netstandard2.0 API surface** | Methods/overloads that only exist on net5+. Common traps: `HttpContent.ReadAsStringAsync(CancellationToken)`, `ProcessStartInfo.ArgumentList`, `Environment.IsPrivilegedProcess`, `ArrayPool` (needs `System.Buffers` package on ns2.0). When unsure, check MS Learn docs. | -| **C# language features** | `init` accessors, `required` keyword, file-scoped types, raw string literals — may need polyfills or `#if` guards. | -| **Conditional compilation** | New API usage should be behind `#if NET5_0_OR_GREATER` (or similar) with a fallback for netstandard2.0. | - -**Postmortem refs:** #3, #4, #30 - ---- - -## 2. Async & Cancellation Patterns - -| Check | What to look for | -|-------|-----------------| -| **CancellationToken propagation** | Every `async` method that accepts a `CancellationToken` must pass it to ALL downstream async calls (`GetAsync`, `ReadAsStreamAsync`, `SendAsync`, etc.). A token that's accepted but never used is a broken contract. | -| **OperationCanceledException** | Catch-all blocks (`catch (Exception)`) must NOT swallow `OperationCanceledException`. Either catch it explicitly first and rethrow, or use a type filter. | -| **GetStringAsync** | On netstandard2.0, `GetStringAsync(url)` doesn't accept a `CancellationToken`. Use `GetAsync(url, ct)` + `ReadAsStringAsync()` instead. | -| **Thread safety of shared state** | If a new field or property can be accessed from multiple threads (e.g., static caches, event handlers), verify thread-safe access: `ConcurrentDictionary`, `Interlocked`, or explicit locks. A `Dictionary` read concurrently with a write is undefined behavior. | - -**Postmortem refs:** #5, #37 - ---- - -## 3. Resource Management - -| Check | What to look for | -|-------|-----------------| -| **HttpClient must be static** | `HttpClient` instances should be `static readonly` fields, not per-instance. Creating/disposing `HttpClient` leads to socket exhaustion via `TIME_WAIT` accumulation. See [Microsoft guidelines](https://learn.microsoft.com/dotnet/fundamentals/networking/http/httpclient-guidelines). | -| **No HttpClient injection (YAGNI)** | Don't add `HttpClient` constructor parameters "for testability" unless a caller actually needs it today. The AI tends to over-engineer this. | -| **ArrayPool for large buffers** | Buffers ≥ 1KB (especially 80KB+ download buffers) should use `ArrayPool.Shared.Rent()` with `try/finally` return. Large allocations go to the LOH and are expensive to GC. | -| **IDisposable** | Classes that own unmanaged resources or expensive managed resources must implement `IDisposable` with a dispose guard (`ThrowIfDisposed`). | - -**Postmortem refs:** #6, #7, #13 - ---- - -## 4. Error Handling - -| Check | What to look for | -|-------|-----------------| -| **No empty catch blocks** | Every `catch` must capture the `Exception` and log it (or rethrow). No silent swallowing. Even "expected" exceptions should be logged for diagnostics. | -| **Validate parameters** | Enum parameters and string-typed "mode" values must be validated — throw `ArgumentException` or `NotSupportedException` for unexpected values. Don't silently accept garbage. | -| **Fail fast on critical ops** | If an operation like `chmod` or checksum verification fails, throw immediately. Silently continuing leads to confusing downstream failures ("permission denied" when the real problem was chmod). | -| **Mandatory verification** | Checksum/hash verification must NOT be optional. If the checksum can't be fetched, the operation must fail — not proceed unverified. | -| **Include actionable details in exceptions** | Use `nameof` for parameter names. Include the unsupported value or unexpected type. Never throw empty exceptions. | -| **Use `ThrowIf` helpers where available** | In .NET 10+ projects, prefer `ArgumentOutOfRangeException.ThrowIfNegative`, `ArgumentNullException.ThrowIfNull`, etc. over manual if-then-throw patterns. In `netstandard2.0` projects where these helpers are unavailable, use explicit checks such as `if (x is null) throw new ArgumentNullException (nameof (x));`. | -| **Challenge exception swallowing** | When a PR adds `catch { continue; }` or `catch { return null; }`, question whether the exception is truly expected or masking a deeper problem. The default should be to let unexpected exceptions propagate. | - -**Postmortem refs:** #11, #20, #22, #35 - ---- - -## 5. Security - -| Check | What to look for | -|-------|-----------------| -| **Zip Slip protection** | Archive extraction must validate that every entry path, after `Path.GetFullPath()`, resolves under the destination directory. Never use `ZipFile.ExtractToDirectory()` for untrusted archives without entry-by-entry validation. | -| **Command injection** | Arguments passed to `Process.Start` or written to `.cmd`/`.sh` scripts must be sanitized. Use `ProcessUtils.CreateProcessStartInfo()` with separate argument strings — it uses `ArgumentList` on net5+ (no shell parsing). Never interpolate user/external input into command strings. | -| **Path traversal** | `StartsWith()` checks on paths must normalize with `Path.GetFullPath()` first. A path like `C:\Program Files\..\Users\evil` bypasses naive prefix checks. Also check for directory boundary issues (`C:\Program FilesX` matching `C:\Program Files`). | -| **Elevation** | Don't auto-elevate. Don't include `IsElevated()` helpers that silently re-launch elevated. The calling tool (VS, VS Code) should handle elevation prompts. The library should error if it lacks permissions. | - -**Postmortem refs:** #17, #34, #39 - ---- - -## 6. Code Organization - -| Check | What to look for | -|-------|-----------------| -| **One type per file** | Each public class, struct, enum, or interface must be in its own `.cs` file named after the type. No multiple top-level types in a single file. | -| **File-scoped namespaces** | New files should use `namespace Foo;` (not `namespace Foo { ... }`). Don't reformat existing files. | -| **No #region directives** | `#region` hides code and makes reviews harder. Remove them. This also applies to banner/section-separator comments (e.g., `// --- Device Tests ---`) — they serve the same purpose as `#region` and signal the file should be split instead. | -| **Use `record` for data types** | Immutable data-carrier types (progress, version info, license info) should be `record` types. They get value equality, `ToString()`, and deconstruction for free. | -| **Remove unused code** | Dead methods, speculative helpers, and code "for later" should be removed. Ship only what's needed. | -| **No commented-out code** | Commented-out code should be deleted — Git has history. | -| **Reduce indentation with early returns** | Invert conditions with `continue` or early `return` to reduce nesting. `foreach (var x in items ?? Array.Empty())` eliminates null-check nesting. | -| **Don't initialize fields to default values** | `bool flag = false;` and `int count = 0;` are noise. The CLR zero-initializes all fields. Only assign when the initial value is non-default. | - -**Postmortem refs:** #9, #12, #25, #28, #56 - ---- - -## 7. Naming & Constants - -| Check | What to look for | -|-------|-----------------| -| **Avoid ambiguous names** | Types that could collide with Android concepts (e.g., `ManifestComponent` vs `AndroidManifest.xml`) need disambiguating prefixes (e.g., `SdkManifestComponent`). | -| **No magic numbers** | Literal values like buffer sizes (`81920`), divisors (`1048576`), permission masks (`0x1ED` = 0755) should be named constants. | -| **Environment variable constants** | Use `EnvironmentVariableNames.AndroidHome` — not raw `"ANDROID_HOME"` strings. Typos in env var names produce silent, hard-to-debug failures. | -| **ANDROID_SDK_ROOT is deprecated** | Per [Android docs](https://developer.android.com/tools/variables#envar), use `ANDROID_HOME` everywhere. Do not introduce new references to `ANDROID_SDK_ROOT`. | - -**Postmortem refs:** #10, #14, #18, #19 - ---- - -## 8. Performance - -| Check | What to look for | -|-------|-----------------| -| **XmlReader over LINQ XML** | For forward-only XML parsing (manifests, config files), use `XmlReader` — it's streaming and allocation-free. `XElement`/`XDocument` builds a full DOM tree. | -| **p/invoke over process spawn** | For single syscalls like `chmod`, use `[DllImport("libc")]` instead of spawning a child process. Process creation is orders of magnitude more expensive. | -| **Avoid intermediate collections** | Don't create two lists and `AddRange()` one to the other. Build a single list, or use LINQ to chain. | -| **Cache reusable arrays** | Char arrays for `string.Split()` (like whitespace chars) should be `static readonly` fields, not allocated on each call. | -| **Pre-allocate collections when size is known** | Use `new List(capacity)` or `new Dictionary(count)` when the size is known or estimable. Repeated resizing is O(n) allocation waste. | -| **Avoid closures in hot paths** | Lambdas that capture local variables allocate a closure object on every call. In loops or frequently-called methods, extract the lambda to a static method or cache the delegate. | -| **Place cheap checks before expensive ones** | In validation chains, test simple conditions (null checks, boolean flags) before allocating strings or doing I/O. Short-circuit with `&&`/`||`. | -| **Watch for O(n²)** | Nested loops over the same or related collections, repeated `.Contains()` on a `List`, or LINQ `.Where()` inside a loop are O(n²). Switch to `HashSet` or `Dictionary` for lookups. | - -**Postmortem refs:** #8, #14, #21, #31 - ---- - -## 9. Patterns & Conventions - -| Check | What to look for | -|-------|-----------------| -| **Use `ProcessUtils`** | All process creation must go through `ProcessUtils.CreateProcessStartInfo()` and `ProcessUtils.StartProcess()`. No direct `new ProcessStartInfo()` or `Process.Start()`. | -| **Use `Action` logger, not `Debug.WriteLine`** | Diagnostic output must use the existing `Action? logger` delegate pattern — never `System.Diagnostics.Debug.WriteLine()` or `Console.WriteLine()`. `Debug.WriteLine()` only reaches attached debuggers (invisible in CI and production), and `Console.WriteLine()` bypasses MSBuild's logging pipeline. Methods that might be called from MSBuild tasks must accept a `logger` parameter and invoke it with the appropriate `TraceLevel`. See `AndroidSdkInfo.DefaultConsoleLogger` for the canonical implementation. | -| **Use `FileUtil`** | File extraction, downloads, checksum verification, and path operations belong in `FileUtil`. Don't duplicate file helpers in domain classes. | -| **Null-object pattern** | Methods accepting nullable dependencies (`IProgress?`, `ILogger?`, `Action?`) should assign a null-object sentinel early (e.g., `progress ??= NullProgress.Instance`, `logger ??= NullLogger.Instance`) and then use the dependency without `?.` null checks throughout the method. Scattered `logger?.Log(...)` or `progress?.Report(...)` calls are a code smell — they add noise, invite missed spots, and signal a missing null-object type. If no null-object type exists yet, recommend creating one. | -| **Version-based directories** | Install SDK/JDK to versioned paths (`cmdline-tools/19.0/`, not `cmdline-tools/latest/`). Versioned paths are self-documenting and allow side-by-side installs. | -| **Safe directory replacement** | Use move-with-rollback: rename existing → temp, move new in place, validate, delete temp only after validation succeeds. Never delete the backup before confirming the new install works. | -| **Cross-volume moves** | `Directory.Move` is really a rename — it fails across filesystems. Extract archives near the target path (same parent directory), or catch `IOException` and fall back to recursive copy + delete. | -| **Method names must reflect behavior** | If `CreateFoo()` sometimes returns an existing instance, rename it `GetOrCreateFoo()` or `GetFoo()`. | -| **Comments explain "why", not "what"** | `// increment i` adds nothing. `// skip the BOM — aapt2 chokes on it` explains intent. If a comment restates the code, delete it. | -| **Track TODOs as issues** | A `// TODO` hidden in code will be forgotten. File an issue and reference it in the comment. | -| **Remove stale comments** | If the code changed, update the comment. Comments that describe old behavior are misleading. | - -**Postmortem refs:** #15, #16, #23, #36, #38, [#282 comment](https://github.com/dotnet/android-tools/pull/282#discussion_r2925449030) - ---- - -## 10. YAGNI & AI-Specific Pitfalls - -These are patterns that AI-generated code consistently gets wrong: - -| Pattern | What to watch for | -|---------|------------------| -| **Reinventing the wheel** | AI creates new infrastructure (e.g., `AndroidToolRunner`) instead of using existing utilities (`ProcessUtils`). ALWAYS check if a similar utility exists before accepting new wrapper code. This is the most expensive AI pattern — hundreds of lines of plausible code that duplicates what's already there. | -| **Over-engineering** | HttpClient injection "for testability", elevation auto-detection, speculative helper classes, unused overloads. If no caller needs it today, remove it. | -| **Swallowed errors** | AI catch blocks love to eat exceptions silently. Check EVERY catch block. Also check that exit codes are checked consistently — if `ListDevicesAsync` checks exit codes, `StopEmulatorAsync` should too. | -| **Ignoring target framework** | AI generates code for the newest .NET. Check every API call against netstandard2.0. | -| **Sloppy structure** | Multiple types in one file, block-scoped namespaces, #region directives, classes where records would do. New helpers marked `public` when `internal` suffices. | -| **Confidently wrong domain facts** | AI once claimed `ANDROID_SDK_ROOT` was the recommended variable (it's deprecated). Always verify domain-specific claims against official docs. | -| **Over-mocking** | Not everything needs to be mocked. Network integration tests with `Assert.Ignore` on failure are fine and catch real API changes that mocks never will. | -| **Docs describe intent not reality** | AI doc comments often describe what the code *should* do, not what it *actually* does. Review doc comments against the implementation. | -| **Unused parameters** | AI adds `CancellationToken` parameters but never observes them, or accepts `additionalArgs` as a string and interpolates it into a command. Unused CancellationToken is a broken contract; string args are injection risks. | -| **Null-forgiving operator (`!`)** | The postfix `!` null-forgiving operator (e.g., `foo!.Bar`) is banned. If the value can be null, add a proper null check. If it can't be null, make the parameter/variable non-nullable. AI frequently sprinkles `!` to make the compiler happy — this turns compile-time warnings into runtime `NullReferenceException`s. Use `IsNullOrEmpty()` extension methods or null-coalescing instead. Note: this rule is about the postfix `!` operator, not the logical negation `!` (e.g., `if (!someBool)` or `if (!string.IsNullOrEmpty (s))`). | -| **`Debug.WriteLine` for logging** | AI catch blocks often log with `System.Diagnostics.Debug.WriteLine()` or `Console.WriteLine()` — neither integrates with the codebase logger pattern (`Action`). See rule in §9. | -| **`git commit --amend`** | AI uses `--amend` on commits that are already pushed or belong to another author. Always create new commits — the maintainer will squash as needed. | -| **Commit messages omit non-obvious choices** | Behavioral decisions and known limitations belong in the commit message, not just the code. | -| **Typos in user-visible strings** | Users copy-paste error messages into bug reports. Get them right. | -| **Filler words in docs** | "So" at the start of a sentence adds nothing. Be direct. | - -**Postmortem refs:** #7, #28, #29, #40, #41, #42, #49, #50, #51, #52, #54 - ---- - -## 11. API Design - -| Check | What to look for | -|-------|-----------------| -| **Return `IReadOnlyList` not `List`** | Public methods should return `IReadOnlyList` (or `IReadOnlyCollection`) instead of mutable `List`. Prevents callers from mutating internal state. | -| **New helpers default to `internal`** | New utility methods should be `internal` unless a confirmed external consumer (e.g., `dotnet/android`) needs them. Use `InternalsVisibleTo` for test access. | -| **Structured args, not string interpolation** | Additional arguments to processes should be `IEnumerable`, not a single `string` that gets interpolated. Use `ProcessUtils.CreateProcessStartInfo()` which handles `ArgumentList` safely. | -| **Honor `CancellationToken`** | If a method accepts a `CancellationToken`, it MUST observe it — register a callback to kill processes, check `IsCancellationRequested` in loops, pass it to downstream async calls. Don't just accept it for API completeness. | -| **Add overloads to reduce caller ceremony** | If every caller performs the same conversion before calling a method (e.g., `writer.ToString()` before `ThrowIfFailed()`), the method should have an overload that accepts the unconverted type directly. | -| **Prefer C# pattern matching** | Use `is`, `switch` expressions, and property patterns instead of `if`/`else` type-check chains. Pattern matching is more concise, avoids casts, and enables exhaustiveness checks. | - -**Postmortem refs:** #46, #47, #49, #50, #53, #55 - ---- - -## 12. Code Sharing & Downstream Coordination - -| Check | What to look for | -|-------|-----------------| -| **Port, don't rewrite** | If `dotnet/android` (or another downstream consumer) already has working logic for the same task, port it rather than writing new code. The existing code has real-world edge cases already handled. | -| **Draft downstream PR before merging** | Shared library changes should be accompanied by a draft PR in the consuming repo that proves the API actually works. Merge the library first, update the submodule pointer, then merge the consumer. | -| **Don't redirect stdout/stderr without draining** | Background processes with `RedirectStandardOutput = true` must have async readers draining the output. Otherwise the OS pipe buffer fills and the child process deadlocks. For fire-and-forget processes, set `Redirect* = false`. | -| **Check exit codes consistently** | If one operation (`ListDevicesAsync`) checks the process exit code, ALL similar operations (`StopEmulatorAsync`, `WaitForDeviceAsync`) must too. Inconsistent error checking creates a false sense of safety. | - -**Postmortem refs:** #42, #43, #44, #45, #48 - ---- - -## 13. Testing - -| Check | What to look for | -|-------|-----------------| -| **Bug fixes need regression tests** | Every PR that fixes a bug should include a test that fails without the fix and passes with it. If the PR description says "fixes #N" but adds no test, ask for one. | -| **Test assertions must be specific** | `Assert.IsNotNull(result)` or `Assert.IsTrue(success)` don't tell you what went wrong. Prefer `Assert.AreEqual(expected, actual)` or NUnit constraints (`Assert.That` with `Does.Contain`, `Is.EqualTo`, etc.) for richer failure messages. | -| **Deterministic test data** | Tests should not depend on system locale, timezone, or current date. Use explicit `CultureInfo.InvariantCulture` and hardcoded dates when testing formatting. | -| **Test edge cases** | Empty collections, null inputs, boundary values, and very large inputs should all be considered. If the PR only tests the happy path, suggest edge cases. | - -**Source:** dotnet/android`#10918` diff --git a/external/xamarin-android-tools/.github/skills/android-tools-reviewer/references/security-rules.md b/external/xamarin-android-tools/.github/skills/android-tools-reviewer/references/security-rules.md new file mode 100644 index 00000000000..fa882b8e101 --- /dev/null +++ b/external/xamarin-android-tools/.github/skills/android-tools-reviewer/references/security-rules.md @@ -0,0 +1,21 @@ +# Security Review Rules + +Security checklist for code reviews. Applicable to any repository handling file I/O, archives, or process execution. + +--- + +## 1. Archive & Path Safety + +| Check | What to look for | +|-------|-----------------| +| **Zip Slip protection** | Archive extraction must validate that every entry path, after `Path.GetFullPath()`, resolves under the destination directory. Never use `ZipFile.ExtractToDirectory()` for untrusted archives without entry-by-entry validation. | +| **Path traversal** | `StartsWith()` checks on paths must normalize with `Path.GetFullPath()` first. A path like `C:\Program Files\..\Users\evil` bypasses naive prefix checks. Also check for directory boundary issues (`C:\Program FilesX` matching `C:\Program Files`). | + +--- + +## 2. Process & Command Safety + +| Check | What to look for | +|-------|-----------------| +| **Command injection** | Arguments passed to external processes must be sanitized. Pass arguments as separate strings (not a single interpolated string) so they are never parsed by a shell. Never interpolate user/external input into command strings. | +| **Elevation** | Don't auto-elevate. Don't include `IsElevated()` helpers that silently re-launch elevated. The calling tool should handle elevation prompts. The library should error if it lacks permissions. | diff --git a/external/xamarin-android-tools/.github/skills/android-tools-reviewer/references/testing-rules.md b/external/xamarin-android-tools/.github/skills/android-tools-reviewer/references/testing-rules.md new file mode 100644 index 00000000000..fbddfa375af --- /dev/null +++ b/external/xamarin-android-tools/.github/skills/android-tools-reviewer/references/testing-rules.md @@ -0,0 +1,12 @@ +# Testing Review Rules + +Guidance for test code reviews. Loaded when test files change. + +--- + +| Check | What to look for | +|-------|-----------------| +| **Bug fixes need regression tests** | Every PR that fixes a bug should include a test that fails without the fix and passes with it. If the PR description says "fixes #N" but adds no test, ask for one. | +| **Test assertions must be specific** | `Assert.IsNotNull(result)` or `Assert.IsTrue(success)` don't tell you what went wrong. Prefer `Assert.AreEqual(expected, actual)` or NUnit constraints (`Assert.That` with `Does.Contain`, `Is.EqualTo`, etc.) for richer failure messages. | +| **Deterministic test data** | Tests should not depend on system locale, timezone, or current date. Use explicit `CultureInfo.InvariantCulture` and hardcoded dates when testing formatting. | +| **Test edge cases** | Empty collections, null inputs, boundary values, concurrent calls, and very large inputs should all be considered. If the PR only tests the happy path, suggest edge cases. | diff --git a/external/xamarin-android-tools/.github/workflows/android-tools-reviewer.md b/external/xamarin-android-tools/.github/workflows/android-tools-reviewer.md index d55aac0c9f0..b6e6545f32d 100644 --- a/external/xamarin-android-tools/.github/workflows/android-tools-reviewer.md +++ b/external/xamarin-android-tools/.github/workflows/android-tools-reviewer.md @@ -37,16 +37,15 @@ A maintainer commented `/review` on this pull request. Perform a thorough code r ## Instructions -1. Read the review rules from `.github/skills/android-tools-reviewer/references/review-rules.md` — these contain the detailed patterns and anti-patterns to check for. -2. Read the review methodology from `.github/skills/android-tools-reviewer/SKILL.md` — this defines the review workflow, mindset, severity levels, and comment format. -3. Follow the skill's workflow to analyze the pull request: +1. Read the review methodology from `.github/skills/android-tools-reviewer/SKILL.md` — this defines the review workflow (including which rule files to load), mindset, severity levels, and comment format. +2. Follow the skill's workflow to analyze the pull request: - Gather context: read the diff and changed files - For each changed file, read the **full source file** to understand surrounding context - Form an independent assessment before reading the PR description - Read the PR title and description — treat claims as things to verify - Check CI status - Analyze the diff against the review rules -4. Post your findings as inline review comments and a review summary. +3. Post your findings as inline review comments and a review summary. ## Constraints From b9c16f69751c8953f7fe563d6168a6c06bdcf59b Mon Sep 17 00:00:00 2001 From: Rui Marinho Date: Mon, 4 May 2026 18:10:07 +0100 Subject: [PATCH 277/308] [copilot] Add performance and allocation review learnings (#354) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds performance and allocation review learnings from PR #327 to the reviewer skill and Copilot instructions. ## Changes **`.github/copilot-instructions.md`** - Add **Reuse buffers** guidance (`ArrayPool.Shared` + reusable `readonly byte[]` fields) - Add **Document thread-safety** guidance (`This class is not thread-safe.`) **`.github/skills/android-tools-reviewer/SKILL.md`** - Add "Prefer existing repo patterns" workflow step — grep for `ArrayPool`, `ObjectPool`, `MemoryStreamPool`, `ProcessUtils` before suggesting new infrastructure **`.github/skills/android-tools-reviewer/references/csharp-rules.md`** - Add **Reuse hot-path buffers** and **Reuse loop helpers** to the Performance section - Add **Document thread-safety invariants** to the Code Organization section ## Removed from original PR (per review feedback) - UTF-8 string literal guidance — `ReadOnlySpan` works on netstandard2.0 via `System.Memory` - `Encoding.ASCII.GetBytes()` recommendation — wrong for non-ASCII content - "No string intermediates in protocols" — overly prescriptive - "No stackalloc in async I/O" — already a compiler error, not a review concern Also rebased onto main to resolve merge conflicts from PR #355 (which split `review-rules.md` into per-category files). Co-authored-by: Jonathan Peppers Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- external/xamarin-android-tools/.github/copilot-instructions.md | 2 ++ .../.github/skills/android-tools-reviewer/SKILL.md | 2 ++ .../skills/android-tools-reviewer/references/csharp-rules.md | 3 +++ 3 files changed, 7 insertions(+) diff --git a/external/xamarin-android-tools/.github/copilot-instructions.md b/external/xamarin-android-tools/.github/copilot-instructions.md index e04bf19c069..68460df4ace 100644 --- a/external/xamarin-android-tools/.github/copilot-instructions.md +++ b/external/xamarin-android-tools/.github/copilot-instructions.md @@ -49,6 +49,7 @@ When setting environment variables for SDK tools (e.g. `sdkmanager`, `avdmanager - **Use `FileUtil`**: file operations like extraction, downloads, checksum verification, and path checks belong in `FileUtil.cs`. Don't duplicate file helpers in domain classes. - **Concise XML docs**: omit `` tags for self-explanatory methods. Only add doc comments when the behavior is non-obvious. Avoid restating the method name. - **`netstandard2.0` awareness**: many modern .NET APIs are unavailable or have fewer overloads on `netstandard2.0`. When unsure about API availability, search mslearn to check documentation for the target framework. +- **Reuse buffers**: use `ArrayPool.Shared.Rent()`/`Return()` with `try/finally` for temporary buffers (see `DownloadUtils.cs`). For fixed-size repeated reads, prefer a reusable `readonly byte[]` field. - **Format your code**: always match the existing file indentation (tabs, not spaces — see `.editorconfig`). Only format code you add or modify; never reformat existing lines. - **No whitespace-only diffs**: before committing, run `git diff --stat` and verify only files with intentional code changes appear. If a file shows as fully rewritten (all lines removed and re-added) you have introduced line-ending or trailing-whitespace changes — revert that file with `git checkout -- ` and re-apply only your code change. Never commit whitespace-only or line-ending-only changes. - **File-scoped namespaces**: all new files should use file-scoped namespaces (`namespace Foo;` instead of `namespace Foo { ... }`). @@ -56,6 +57,7 @@ When setting environment variables for SDK tools (e.g. `sdkmanager`, `avdmanager - [Mono Coding Guidelines](http://www.mono-project.com/community/contributing/coding-guidelines/): tabs, K&R braces, `PascalCase` public members. - **No null-forgiving operator (`!`)**: do not use the null-forgiving operator after null checks. Instead, use C# property patterns (e.g. `if (value is { Length: > 0 } v)`) which give the compiler proper non-null flow analysis on all target frameworks including `netstandard2.0`. - **Prefer switch expressions**: use C# switch expressions over switch statements for simple value mappings (e.g. `return state switch { "x" => A, _ => B };`). Use switch statements only when the body has side effects or complex logic. +- **Document thread-safety**: when a class reuses mutable state (buffers, caches) assuming single-caller semantics, add `This class is not thread-safe.`. - Nullable enabled in `AndroidSdk`. `NullableAttributes.cs` excluded on `net10.0+`. - Strong-named via `product.snk`. In the AndroidSdk project, tests use `InternalsVisibleTo` with full public key (`Properties/AssemblyInfo.cs`). - Assembly names support `$(VendorPrefix)`/`$(VendorSuffix)` for branding forks. diff --git a/external/xamarin-android-tools/.github/skills/android-tools-reviewer/SKILL.md b/external/xamarin-android-tools/.github/skills/android-tools-reviewer/SKILL.md index 12d06251572..618083ee8ae 100644 --- a/external/xamarin-android-tools/.github/skills/android-tools-reviewer/SKILL.md +++ b/external/xamarin-android-tools/.github/skills/android-tools-reviewer/SKILL.md @@ -37,6 +37,8 @@ gh pr view {number} --repo {owner}/{repo} --json files For each changed file, read the **full source file** (not just the diff) to understand surrounding invariants, call patterns, and data flow. If the change modifies a public/internal API or utility, search for callers. Check whether sibling types need the same fix. +**Prefer existing repo patterns.** Before suggesting new infrastructure, grep for established patterns (`ArrayPool`, `ObjectPool`, `MemoryStreamPool`, `ProcessUtils`) and follow those. + **Form an independent assessment** of what the change does and what problems it has *before* reading the PR description. ### 3. Incorporate PR narrative and reconcile diff --git a/external/xamarin-android-tools/.github/skills/android-tools-reviewer/references/csharp-rules.md b/external/xamarin-android-tools/.github/skills/android-tools-reviewer/references/csharp-rules.md index c826d9bae45..c659dcd09f1 100644 --- a/external/xamarin-android-tools/.github/skills/android-tools-reviewer/references/csharp-rules.md +++ b/external/xamarin-android-tools/.github/skills/android-tools-reviewer/references/csharp-rules.md @@ -63,6 +63,8 @@ When a library multi-targets (e.g., `netstandard2.0` + a modern TFM), every API | Check | What to look for | |-------|-----------------| +| **Reuse hot-path buffers** | Pool repeated `byte[]` allocations with `ArrayPool.Shared` (see `DownloadUtils.cs`). For fixed-size repeated reads, use a `readonly byte[]` instance field. Document non-thread-safety in ``. | +| **Reuse loop helpers** | If a loop creates resettable helpers (e.g., `TcpClient`), reuse one instance via `Reconnect()`/`Reset()` instead of `new` per iteration. | | **XmlReader over LINQ XML** | For forward-only XML parsing (manifests, config files), use `XmlReader` — it's streaming and allocation-free. `XElement`/`XDocument` builds a full DOM tree. | | **p/invoke over process spawn** | For single syscalls like `chmod`, use `[DllImport("libc")]` instead of spawning a child process. Process creation is orders of magnitude more expensive. | | **Avoid intermediate collections** | Don't create two lists and `AddRange()` one to the other. Build a single list, or use LINQ to chain. | @@ -85,6 +87,7 @@ When a library multi-targets (e.g., `netstandard2.0` + a modern TFM), every API |-------|-----------------| | **File-scoped namespaces** | New files should use `namespace Foo;` (not `namespace Foo { ... }`). Don't reformat existing files. | | **Use `record` for data types** | Immutable data-carrier types (progress, version info, license info) should be `record` types. They get value equality, `ToString()`, and deconstruction for free. | +| **Document thread-safety invariants** | When a class reuses buffers, caches, or mutable state assuming single-caller semantics, add `This class is not thread-safe.` to the type. Callers need to know if external synchronization is required. | | **Remove unused code** | Dead methods, speculative helpers, and code "for later" should be removed. Ship only what's needed. | | **No commented-out code** | Commented-out code should be deleted — Git has history. | | **Reduce indentation with early returns** | Invert conditions with `continue` or early `return` to reduce nesting. `foreach (var x in items ?? Array.Empty())` eliminates null-check nesting. | From a98e0dd12f0ed9f6b0812de3ebfcdd7c758775a9 Mon Sep 17 00:00:00 2001 From: Rui Marinho Date: Mon, 4 May 2026 18:25:38 +0100 Subject: [PATCH 278/308] Add `AvdManagerRunner.ListDeviceProfilesAsync()` for device enumeration (#325) Add ListDeviceProfilesAsync() and ParseDeviceListOutput() to AvdManagerRunner. Runs 'avdmanager list device' and parses the output into AvdDeviceProfile records (Id). Includes AvdDeviceProfile record model, 6 unit tests, and PublicAPI entries for both net10.0 and netstandard2.0. Closes #321 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Models/AvdDeviceProfile.cs | 9 +++ .../PublicAPI/net10.0/PublicAPI.Unshipped.txt | 5 ++ .../netstandard2.0/PublicAPI.Unshipped.txt | 5 ++ .../Runners/AvdManagerRunner.cs | 29 ++++++++++ .../AvdManagerRunnerTests.cs | 57 +++++++++++++++++++ 5 files changed, 105 insertions(+) create mode 100644 external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Models/AvdDeviceProfile.cs diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Models/AvdDeviceProfile.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Models/AvdDeviceProfile.cs new file mode 100644 index 00000000000..1ea65cb4050 --- /dev/null +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Models/AvdDeviceProfile.cs @@ -0,0 +1,9 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Xamarin.Android.Tools; + +/// +/// Represents a hardware device profile (e.g., "pixel_7", "Nexus 5X") from avdmanager list device --compact. +/// +public record AvdDeviceProfile (string Id); diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/PublicAPI/net10.0/PublicAPI.Unshipped.txt b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/PublicAPI/net10.0/PublicAPI.Unshipped.txt index 61fc0e0bdd2..dd3c96f16cf 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/PublicAPI/net10.0/PublicAPI.Unshipped.txt +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/PublicAPI/net10.0/PublicAPI.Unshipped.txt @@ -199,3 +199,8 @@ virtual Xamarin.Android.Tools.AdbRunner.ListReversePortsAsync(string! serial, Sy virtual Xamarin.Android.Tools.AdbRunner.RemoveAllReversePortsAsync(string! serial, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task! virtual Xamarin.Android.Tools.AdbRunner.RemoveReversePortAsync(string! serial, Xamarin.Android.Tools.AdbPortSpec! remote, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task! virtual Xamarin.Android.Tools.AdbRunner.ReversePortAsync(string! serial, Xamarin.Android.Tools.AdbPortSpec! remote, Xamarin.Android.Tools.AdbPortSpec! local, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task! +Xamarin.Android.Tools.AvdManagerRunner.ListDeviceProfilesAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task!>! +Xamarin.Android.Tools.AvdDeviceProfile +Xamarin.Android.Tools.AvdDeviceProfile.AvdDeviceProfile(string! Id) -> void +Xamarin.Android.Tools.AvdDeviceProfile.Id.get -> string! +Xamarin.Android.Tools.AvdDeviceProfile.Id.init -> void diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt index 61fc0e0bdd2..dd3c96f16cf 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt @@ -199,3 +199,8 @@ virtual Xamarin.Android.Tools.AdbRunner.ListReversePortsAsync(string! serial, Sy virtual Xamarin.Android.Tools.AdbRunner.RemoveAllReversePortsAsync(string! serial, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task! virtual Xamarin.Android.Tools.AdbRunner.RemoveReversePortAsync(string! serial, Xamarin.Android.Tools.AdbPortSpec! remote, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task! virtual Xamarin.Android.Tools.AdbRunner.ReversePortAsync(string! serial, Xamarin.Android.Tools.AdbPortSpec! remote, Xamarin.Android.Tools.AdbPortSpec! local, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task! +Xamarin.Android.Tools.AvdManagerRunner.ListDeviceProfilesAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task!>! +Xamarin.Android.Tools.AvdDeviceProfile +Xamarin.Android.Tools.AvdDeviceProfile.AvdDeviceProfile(string! Id) -> void +Xamarin.Android.Tools.AvdDeviceProfile.Id.get -> string! +Xamarin.Android.Tools.AvdDeviceProfile.Id.init -> void diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Runners/AvdManagerRunner.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Runners/AvdManagerRunner.cs index b233ae38b28..954f1379158 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Runners/AvdManagerRunner.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Runners/AvdManagerRunner.cs @@ -122,6 +122,35 @@ public async Task DeleteAvdAsync (string name, CancellationToken cancellationTok ProcessUtils.ThrowIfFailed (exitCode, $"avdmanager delete avd --name {name}", stderr); } + /// + /// Lists available device profiles (hardware definitions) using avdmanager list device --compact. + /// + public async Task> ListDeviceProfilesAsync (CancellationToken cancellationToken = default) + { + using var stdout = new StringWriter (); + using var stderr = new StringWriter (); + var psi = ProcessUtils.CreateProcessStartInfo (avdManagerPath, "list", "device", "--compact"); + logger.Invoke (TraceLevel.Verbose, "Running: avdmanager list device --compact"); + var exitCode = await ProcessUtils.StartProcess (psi, stdout, stderr, cancellationToken, environmentVariables).ConfigureAwait (false); + + ProcessUtils.ThrowIfFailed (exitCode, "avdmanager list device --compact", stderr, stdout); + + return ParseCompactDeviceListOutput (stdout.ToString ()); + } + + internal static IReadOnlyList ParseCompactDeviceListOutput (string output) + { + var profiles = new List (); + + foreach (var line in output.Split ('\n')) { + var trimmed = line.Trim (); + if (trimmed.Length > 0) + profiles.Add (new AvdDeviceProfile (trimmed)); + } + + return profiles; + } + internal static IReadOnlyList ParseAvdListOutput (string output) { var avds = new List (); diff --git a/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/AvdManagerRunnerTests.cs b/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/AvdManagerRunnerTests.cs index 6ae30de8292..187beedb430 100644 --- a/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/AvdManagerRunnerTests.cs +++ b/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/AvdManagerRunnerTests.cs @@ -293,4 +293,61 @@ public void FindCmdlineTool_PrefersStableOverPreRelease () Directory.Delete (tempDir, true); } } + + [Test] + public void ParseCompactDeviceListOutput_MultipleProfiles () + { + var output = + "automotive_1024p_landscape\n" + + "pixel_7\n" + + "Nexus 5X\n" + + "Galaxy Nexus\n"; + + var profiles = AvdManagerRunner.ParseCompactDeviceListOutput (output); + + Assert.AreEqual (4, profiles.Count); + Assert.AreEqual ("automotive_1024p_landscape", profiles [0].Id); + Assert.AreEqual ("pixel_7", profiles [1].Id); + Assert.AreEqual ("Nexus 5X", profiles [2].Id); + Assert.AreEqual ("Galaxy Nexus", profiles [3].Id); + } + + [Test] + public void ParseCompactDeviceListOutput_EmptyOutput () + { + var profiles = AvdManagerRunner.ParseCompactDeviceListOutput (""); + Assert.AreEqual (0, profiles.Count); + } + + [Test] + public void ParseCompactDeviceListOutput_WindowsNewlines () + { + var output = + "pixel_fold\r\n" + + "pixel_9_pro\r\n"; + + var profiles = AvdManagerRunner.ParseCompactDeviceListOutput (output); + + Assert.AreEqual (2, profiles.Count); + Assert.AreEqual ("pixel_fold", profiles [0].Id); + Assert.AreEqual ("pixel_9_pro", profiles [1].Id); + } + + [Test] + public void ParseCompactDeviceListOutput_SkipsBlankLines () + { + var output = "\n\npixel_7\n\n"; + + var profiles = AvdManagerRunner.ParseCompactDeviceListOutput (output); + + Assert.AreEqual (1, profiles.Count); + Assert.AreEqual ("pixel_7", profiles [0].Id); + } + + [Test] + public void ParseCompactDeviceListOutput_ReturnsIReadOnlyList () + { + var profiles = AvdManagerRunner.ParseCompactDeviceListOutput (""); + Assert.IsInstanceOf> (profiles); + } } From 2593a5267dbfa0750fc09fd18130b66b710ab0fc Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Wed, 6 May 2026 10:24:19 -0500 Subject: [PATCH 279/308] Add API 37 (CinnamonBun) to KnownVersions (#357) Add AndroidVersion entry for API level 37.0 (framework v17.0, codename CinnamonBun) so that GetIdFromApiLevel(37), GetFrameworkVersionFromApiLevel(37), etc. resolve correctly as a fallback when AndroidApiInfo.xml is not loaded. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../src/Xamarin.Android.Tools.AndroidSdk/AndroidVersions.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidVersions.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidVersions.cs index ef371570719..734aa1cf656 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidVersions.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/AndroidVersions.cs @@ -214,6 +214,9 @@ static bool MatchesId (AndroidVersion version, string id) new AndroidVersion (new Version ("36.1"), "16.0") { AlternateIds = new[]{ "Canary" }, }, + new AndroidVersion (new Version ("37.0"), "17.0") { + AlternateIds = new[]{ "CinnamonBun", "Cinnamon Bun" }, + }, }; } } From d2cd2075b3e7af21e084e8d05e89aa8d9b4107a2 Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Wed, 6 May 2026 10:27:53 -0500 Subject: [PATCH 280/308] [copilot] Restore workflow_dispatch trigger for Copilot coding agent (#356) The copilot-setup-steps.yml has always used `on: pull_request` with a paths filter, meaning the Copilot coding agent never had proper setup steps. Restore the `on: workflow_dispatch` trigger so the agent gets the right build environment when assigned to issues. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../.github/workflows/copilot-setup-steps.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/external/xamarin-android-tools/.github/workflows/copilot-setup-steps.yml b/external/xamarin-android-tools/.github/workflows/copilot-setup-steps.yml index f304f3592aa..6e90f640273 100644 --- a/external/xamarin-android-tools/.github/workflows/copilot-setup-steps.yml +++ b/external/xamarin-android-tools/.github/workflows/copilot-setup-steps.yml @@ -1,6 +1,7 @@ name: Copilot Setup Steps CI on: + workflow_dispatch: pull_request: paths: - '.github/workflows/copilot-setup-steps.yml' From 7296b649d623677c7d5ba5ca2478a25e2a7fc3de Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Thu, 7 May 2026 09:34:18 -0500 Subject: [PATCH 281/308] Handle InvalidOperationException when emulator process has no associated OS process - Check Process.Start() return value in LaunchEmulator - Guard HasExited/ExitCode access with try-catch for InvalidOperationException - Use `using (emulatorProcess)` block for automatic disposal instead of manual Dispose() calls scattered across every exit path - Remove Dispose() from TryKillProcess since the using block handles it - Add InvalidEmulatorBinary_ReturnsLaunchFailed test: script that exits with code 1 asserts LaunchFailed result with diagnostic message Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Runners/EmulatorRunner.cs | 89 ++++++++++--------- .../EmulatorRunnerTests.cs | 32 +++++++ 2 files changed, 81 insertions(+), 40 deletions(-) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Runners/EmulatorRunner.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Runners/EmulatorRunner.cs index 4a4bd6e8baf..772d40feea7 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Runners/EmulatorRunner.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Runners/EmulatorRunner.cs @@ -85,7 +85,10 @@ public Process LaunchEmulator (string avdName, bool coldBoot = false, List BootEmulatorAsync ( // code 0 immediately. The real emulator continues as a separate process and // will eventually appear in 'adb devices'. We only treat non-zero exit codes // as immediate failures; exit code 0 means we continue polling. - try { - string? newSerial = null; - bool processExitedWithZero = false; - while (newSerial == null) { - timeoutCts.Token.ThrowIfCancellationRequested (); - - // Detect early process exit for fast failure - if (emulatorProcess.HasExited && !processExitedWithZero) { - if (emulatorProcess.ExitCode != 0) { - emulatorProcess.Dispose (); + // + // Dispose the Process handle when done — the emulator process keeps running. + using (emulatorProcess) { + try { + string? newSerial = null; + bool processExitedWithZero = false; + while (newSerial == null) { + timeoutCts.Token.ThrowIfCancellationRequested (); + + // Detect early process exit for fast failure. + // Guard against InvalidOperationException in case no OS process + // is associated with the object (e.g. broken emulator binary). + try { + if (emulatorProcess.HasExited && !processExitedWithZero) { + if (emulatorProcess.ExitCode != 0) { + return new EmulatorBootResult { + Success = false, + ErrorKind = EmulatorBootErrorKind.LaunchFailed, + ErrorMessage = $"Emulator process for '{deviceOrAvdName}' exited with code {emulatorProcess.ExitCode} before becoming available.", + }; + } + // Exit code 0: emulator likely forked (common on macOS). + // The real emulator runs as a separate process — keep polling. + logger.Invoke (TraceLevel.Verbose, $"Emulator launcher process exited with code 0 (likely forked). Continuing to poll adb devices."); + processExitedWithZero = true; + } + } catch (InvalidOperationException ex) { return new EmulatorBootResult { Success = false, ErrorKind = EmulatorBootErrorKind.LaunchFailed, - ErrorMessage = $"Emulator process for '{deviceOrAvdName}' exited with code {emulatorProcess.ExitCode} before becoming available.", + ErrorMessage = $"Emulator process for '{deviceOrAvdName}' is no longer available: {ex.Message}", }; } - // Exit code 0: emulator likely forked (common on macOS). - // The real emulator runs as a separate process — keep polling. - logger.Invoke (TraceLevel.Verbose, $"Emulator launcher process exited with code 0 (likely forked). Continuing to poll adb devices."); - processExitedWithZero = true; - } - - await Task.Delay (options.PollInterval, timeoutCts.Token).ConfigureAwait (false); - devices = await adbRunner.ListDevicesAsync (timeoutCts.Token).ConfigureAwait (false); - newSerial = FindRunningAvdSerial (devices, deviceOrAvdName); - } + await Task.Delay (options.PollInterval, timeoutCts.Token).ConfigureAwait (false); - logger.Invoke (TraceLevel.Info, $"Emulator appeared as '{newSerial}', waiting for full boot..."); - var result = await WaitForFullBootAsync (adbRunner, newSerial, options, timeoutCts.Token).ConfigureAwait (false); + devices = await adbRunner.ListDevicesAsync (timeoutCts.Token).ConfigureAwait (false); + newSerial = FindRunningAvdSerial (devices, deviceOrAvdName); + } - // Release the Process handle — the emulator process itself keeps running. - // We no longer need stdout/stderr forwarding since boot is complete. - emulatorProcess.Dispose (); - return result; - } catch (OperationCanceledException) when (!cancellationToken.IsCancellationRequested) { - TryKillProcess (emulatorProcess); - return new EmulatorBootResult { - Success = false, - ErrorKind = EmulatorBootErrorKind.Timeout, - ErrorMessage = $"Timed out waiting for emulator '{deviceOrAvdName}' to boot within {options.BootTimeout.TotalSeconds}s.", - }; - } catch { - TryKillProcess (emulatorProcess); - throw; + logger.Invoke (TraceLevel.Info, $"Emulator appeared as '{newSerial}', waiting for full boot..."); + return await WaitForFullBootAsync (adbRunner, newSerial, options, timeoutCts.Token).ConfigureAwait (false); + } catch (OperationCanceledException) when (!cancellationToken.IsCancellationRequested) { + TryKillProcess (emulatorProcess); + return new EmulatorBootResult { + Success = false, + ErrorKind = EmulatorBootErrorKind.Timeout, + ErrorMessage = $"Timed out waiting for emulator '{deviceOrAvdName}' to boot within {options.BootTimeout.TotalSeconds}s.", + }; + } catch { + TryKillProcess (emulatorProcess); + throw; + } } } @@ -276,8 +287,6 @@ void TryKillProcess (Process process) } catch (Exception ex) { // Best-effort: process may have already exited logger.Invoke (TraceLevel.Verbose, $"Failed to stop emulator process: {ex.Message}"); - } finally { - process.Dispose (); } } diff --git a/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/EmulatorRunnerTests.cs b/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/EmulatorRunnerTests.cs index 39fdca5b79c..8a6fc862320 100644 --- a/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/EmulatorRunnerTests.cs +++ b/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/EmulatorRunnerTests.cs @@ -480,6 +480,38 @@ public void BootEmulatorAsync_EmptyDeviceName_Throws () runner.BootEmulatorAsync ("", mockAdb)); } + [Test] + public async Task InvalidEmulatorBinary_ReturnsLaunchFailed () + { + var (tempDir, emuPath) = CreateFakeEmulatorSdk (); + + // Overwrite with a script that exits immediately with error code 1 + if (OS.IsWindows) { + File.WriteAllText (emuPath, "@echo off\r\nexit /b 1\r\n"); + } else { + File.WriteAllText (emuPath, "#!/bin/sh\nexit 1\n"); + } + + try { + var devices = new List (); + var mockAdb = new MockAdbRunner (devices); + + var runner = new EmulatorRunner (emuPath); + var options = new EmulatorBootOptions { + BootTimeout = TimeSpan.FromSeconds (5), + PollInterval = TimeSpan.FromMilliseconds (50), + }; + + var result = await runner.BootEmulatorAsync ("Test_AVD", mockAdb, options); + + Assert.IsFalse (result.Success); + Assert.AreEqual (EmulatorBootErrorKind.LaunchFailed, result.ErrorKind); + Assert.That (result.ErrorMessage, Does.Contain ("exited with code")); + } finally { + Directory.Delete (tempDir, true); + } + } + // --- Helpers --- static (string tempDir, string emulatorPath) CreateFakeEmulatorSdk () From 7795c2acd419e7d05a9dfe113c781c24ec4bc2d5 Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Fri, 8 May 2026 15:45:44 -0500 Subject: [PATCH 282/308] Remove dead ThrowIfCancellationRequested() in WaitForFullBootAsync The while (!cancellationToken.IsCancellationRequested) loop condition already guarantees the token is not cancelled at the start of each iteration, making the immediate ThrowIfCancellationRequested() call unreachable. Actual cancellation propagates via OperationCanceledException from the awaited adb call and Task.Delay. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Runners/EmulatorRunner.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Runners/EmulatorRunner.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Runners/EmulatorRunner.cs index 4a4bd6e8baf..6d105f97299 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Runners/EmulatorRunner.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Runners/EmulatorRunner.cs @@ -291,9 +291,7 @@ async Task WaitForFullBootAsync ( // cancellationToken (a linked CTS with CancelAfter). This method simply // polls until boot completes or the token is cancelled. while (!cancellationToken.IsCancellationRequested) { - cancellationToken.ThrowIfCancellationRequested (); - - var bootCompleted = await adbRunner.GetShellPropertyAsync (serial, "sys.boot_completed", cancellationToken).ConfigureAwait (false); + var bootCompleted= await adbRunner.GetShellPropertyAsync (serial, "sys.boot_completed", cancellationToken).ConfigureAwait (false); if (string.Equals (bootCompleted, "1", StringComparison.Ordinal)) { var pmResult = await adbRunner.RunShellCommandAsync (serial, "pm path android", cancellationToken).ConfigureAwait (false); if (pmResult != null && pmResult.StartsWith ("package:", StringComparison.Ordinal)) { From 0c61ac53e0125228c0106192bb6020af56899456 Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Fri, 8 May 2026 16:04:54 -0500 Subject: [PATCH 283/308] Fix formatting of bootCompleted variable assignment --- .../Xamarin.Android.Tools.AndroidSdk/Runners/EmulatorRunner.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Runners/EmulatorRunner.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Runners/EmulatorRunner.cs index 6d105f97299..24bac14ac19 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Runners/EmulatorRunner.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Runners/EmulatorRunner.cs @@ -291,7 +291,7 @@ async Task WaitForFullBootAsync ( // cancellationToken (a linked CTS with CancelAfter). This method simply // polls until boot completes or the token is cancelled. while (!cancellationToken.IsCancellationRequested) { - var bootCompleted= await adbRunner.GetShellPropertyAsync (serial, "sys.boot_completed", cancellationToken).ConfigureAwait (false); + var bootCompleted = await adbRunner.GetShellPropertyAsync (serial, "sys.boot_completed", cancellationToken).ConfigureAwait (false); if (string.Equals (bootCompleted, "1", StringComparison.Ordinal)) { var pmResult = await adbRunner.RunShellCommandAsync (serial, "pm path android", cancellationToken).ConfigureAwait (false); if (pmResult != null && pmResult.StartsWith ("package:", StringComparison.Ordinal)) { From c2348e0e3fa481e8541f58aab616f5c264ffed46 Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Tue, 12 May 2026 09:08:36 -0500 Subject: [PATCH 284/308] Merge pull request #362 from dotnet/op-fix-runner-throwiffailed-stdout Pass stdout to ThrowIfFailed in three runner list methods --- .../src/Xamarin.Android.Tools.AndroidSdk/Runners/AdbRunner.cs | 2 +- .../Runners/AvdManagerRunner.cs | 2 +- .../Xamarin.Android.Tools.AndroidSdk/Runners/EmulatorRunner.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Runners/AdbRunner.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Runners/AdbRunner.cs index 0eadc70e379..e46900ed333 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Runners/AdbRunner.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Runners/AdbRunner.cs @@ -58,7 +58,7 @@ public virtual async Task> ListDevicesAsync (Cancel var psi = ProcessUtils.CreateProcessStartInfo (adbPath, "devices", "-l"); var exitCode = await ProcessUtils.StartProcess (psi, stdout, stderr, cancellationToken, environmentVariables).ConfigureAwait (false); - ProcessUtils.ThrowIfFailed (exitCode, "adb devices -l", stderr); + ProcessUtils.ThrowIfFailed (exitCode, "adb devices -l", stderr, stdout); var devices = ParseAdbDevicesOutput (stdout.ToString ().Split ('\n')); diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Runners/AvdManagerRunner.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Runners/AvdManagerRunner.cs index 954f1379158..cb0dcfdaa0d 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Runners/AvdManagerRunner.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Runners/AvdManagerRunner.cs @@ -43,7 +43,7 @@ public async Task> ListAvdsAsync (CancellationToken cance logger.Invoke (TraceLevel.Verbose, "Running: avdmanager list avd"); var exitCode = await ProcessUtils.StartProcess (psi, stdout, stderr, cancellationToken, environmentVariables).ConfigureAwait (false); - ProcessUtils.ThrowIfFailed (exitCode, "avdmanager list avd", stderr); + ProcessUtils.ThrowIfFailed (exitCode, "avdmanager list avd", stderr, stdout); return ParseAvdListOutput (stdout.ToString ()); } diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Runners/EmulatorRunner.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Runners/EmulatorRunner.cs index b6dadacdb96..04b9b2d89ff 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Runners/EmulatorRunner.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Runners/EmulatorRunner.cs @@ -105,7 +105,7 @@ public async Task> ListAvdNamesAsync (CancellationToken ca logger.Invoke (TraceLevel.Verbose, "Running: emulator -list-avds"); var exitCode = await ProcessUtils.StartProcess (psi, stdout, stderr, cancellationToken, environmentVariables).ConfigureAwait (false); - ProcessUtils.ThrowIfFailed (exitCode, "emulator -list-avds", stderr); + ProcessUtils.ThrowIfFailed (exitCode, "emulator -list-avds", stderr, stdout); return ParseListAvdsOutput (stdout.ToString ()); } From f5abc5a1e35a67ee9fe412e94f7ae4c71e58092e Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Fri, 15 May 2026 15:34:39 +0200 Subject: [PATCH 285/308] Launch emulator in signal-isolated process on Unix to survive Ctrl+C (#361) * Initial plan * Launch emulator in signal-isolated process group on Unix to survive Ctrl+C On Unix, wrap the emulator launch through /bin/sh with 'trap "" INT; exec ...' which sets SIGINT to SIG_IGN before exec'ing the emulator. POSIX guarantees SIG_IGN is preserved across exec, so the emulator process ignores SIGINT (Ctrl+C) from the parent terminal. Also fix pre-existing bug where emulatorProcess.ExitCode was accessed after Dispose() in BootEmulatorAsync. Agent-Logs-Url: https://github.com/dotnet/android-tools/sessions/9eb96c77-ebf5-45a1-8e00-d54a177d6ef3 Co-authored-by: jonathanpeppers <840039+jonathanpeppers@users.noreply.github.com> * Address PR review feedback - Add POSIX spec link to the trap/exec code comment - Remove section separator comments (// --- Helpers --- etc.) - Assert kill exit code in SurvivesSigint test to prevent false positives - Add timeout to chmod.WaitForExit() to prevent CI hangs - Move process cleanup to finally blocks to avoid leaking processes in CI Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: jonathanpeppers <840039+jonathanpeppers@users.noreply.github.com> Co-authored-by: Jonathan Peppers Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Runners/EmulatorRunner.cs | 23 ++++++- .../EmulatorRunnerTests.cs | 65 +++++++++++++++++-- 2 files changed, 82 insertions(+), 6 deletions(-) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Runners/EmulatorRunner.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Runners/EmulatorRunner.cs index 04b9b2d89ff..eff26f578bb 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Runners/EmulatorRunner.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Runners/EmulatorRunner.cs @@ -6,6 +6,7 @@ using System.Diagnostics; using System.IO; using System.Linq; +using System.Text; using System.Threading; using System.Threading.Tasks; @@ -57,7 +58,23 @@ public Process LaunchEmulator (string avdName, bool coldBoot = false, List WaitForFullBootAsync ( cancellationToken.ThrowIfCancellationRequested (); return new EmulatorBootResult { Success = false, ErrorKind = EmulatorBootErrorKind.Cancelled, ErrorMessage = "Boot cancelled." }; } + + /// Quotes a string for safe use in a POSIX shell command. + /// Wraps in single quotes and escapes embedded single quotes. + static string ShellQuote (string arg) => "'" + arg.Replace ("'", "'\\''") + "'"; } diff --git a/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/EmulatorRunnerTests.cs b/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/EmulatorRunnerTests.cs index 8a6fc862320..c94da2ece99 100644 --- a/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/EmulatorRunnerTests.cs +++ b/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/EmulatorRunnerTests.cs @@ -97,8 +97,6 @@ public void LaunchEmulator_ThrowsOnWhitespaceAvdName () Assert.Throws (() => runner.LaunchEmulator (" ")); } - // --- BootEmulatorAsync tests (ported from dotnet/android BootAndroidEmulatorTests) --- - [Test] public async Task AlreadyOnlineDevice_PassesThrough () { @@ -205,7 +203,7 @@ public async Task LaunchFailure_ReturnsError () var result = await runner.BootEmulatorAsync ("Pixel_7_API_35", mockAdb, options); Assert.IsFalse (result.Success); - Assert.That (result.ErrorMessage, Does.Contain ("Failed to launch")); + Assert.AreEqual (EmulatorBootErrorKind.LaunchFailed, result.ErrorKind); } [Test] @@ -295,8 +293,6 @@ public async Task MultipleEmulators_FindsCorrectAvd () Assert.AreEqual ("emulator-5556", result.Serial, "Should find the correct AVD among multiple emulators"); } - // --- Tests ported from dotnet/android BootAndroidEmulatorTests --- - [Test] public async Task AlreadyOnlinePhysicalDevice_PassesThrough () { @@ -480,6 +476,36 @@ public void BootEmulatorAsync_EmptyDeviceName_Throws () runner.BootEmulatorAsync ("", mockAdb)); } + [Test] + [Platform ("Linux,MacOsX")] + public void LaunchEmulator_SurvivesSigint () + { + var (tempDir, emuPath) = CreateFakeEmulatorSdk (); + Process? process = null; + try { + var runner = new EmulatorRunner (emuPath); + process = runner.LaunchEmulator ("TestAVD"); + + Assert.IsFalse (process.HasExited, "Process should be running after launch"); + + // Send SIGINT to the emulator process + var killPsi = ProcessUtils.CreateProcessStartInfo ("kill", "-INT", process.Id.ToString ()); + using var kill = new Process { StartInfo = killPsi }; + kill.Start (); + Assert.IsTrue (kill.WaitForExit (5000), "kill command should exit promptly"); + Assert.AreEqual (0, kill.ExitCode, "kill -INT should succeed"); + + // Give the signal a moment to be delivered + Thread.Sleep (500); + + Assert.IsFalse (process.HasExited, "Emulator process should survive SIGINT"); + } finally { + try { process?.Kill (); process?.WaitForExit (5000); } catch { } + process?.Dispose (); + Directory.Delete (tempDir, true); + } + } + [Test] public async Task InvalidEmulatorBinary_ReturnsLaunchFailed () { @@ -512,6 +538,35 @@ public async Task InvalidEmulatorBinary_ReturnsLaunchFailed () } } + [Test] + [Platform ("Linux,MacOsX")] + public void ShellQuote_EscapesSingleQuotes () + { + var tempDir = Path.Combine (Path.GetTempPath (), $"emu-quote-test-{Path.GetRandomFileName ()}"); + var emulatorDir = Path.Combine (tempDir, "emu'dir"); + Directory.CreateDirectory (emulatorDir); + + var emuPath = Path.Combine (emulatorDir, "emulator"); + File.WriteAllText (emuPath, "#!/bin/sh\nsleep 60\n"); + var psi = ProcessUtils.CreateProcessStartInfo ("chmod", "+x", emuPath); + using (var chmod = new Process { StartInfo = psi }) { + chmod.Start (); + Assert.IsTrue (chmod.WaitForExit (5000), "chmod should exit promptly"); + } + + Process? process = null; + try { + var runner = new EmulatorRunner (emuPath); + process = runner.LaunchEmulator ("TestAVD"); + + Assert.IsFalse (process.HasExited, "Process should start even with single-quote in path"); + } finally { + try { process?.Kill (); process?.WaitForExit (5000); } catch { } + process?.Dispose (); + Directory.Delete (tempDir, true); + } + } + // --- Helpers --- static (string tempDir, string emulatorPath) CreateFakeEmulatorSdk () From 2d89805c191eca97fb318a36c991d942e9666408 Mon Sep 17 00:00:00 2001 From: Jonathan Dick Date: Wed, 27 May 2026 15:08:39 -0400 Subject: [PATCH 286/308] Update Microsoft.Build package references (#369) ## Summary - Update Microsoft.Build package references from 17.14.28 to 18.6.3 - Add the matching System.Reflection.Metadata package reference used by the build task project ## Validation - dotnet restore src/Microsoft.Android.Build.BaseTasks/Microsoft.Android.Build.BaseTasks.csproj --disable-parallel --verbosity minimal - dotnet build src/Microsoft.Android.Build.BaseTasks/Microsoft.Android.Build.BaseTasks.csproj --no-restore --verbosity minimal - dotnet restore tests/Microsoft.Android.Build.BaseTasks-Tests/Microsoft.Android.Build.BaseTasks-Tests.csproj --disable-parallel --verbosity minimal - dotnet test tests/Microsoft.Android.Build.BaseTasks-Tests/Microsoft.Android.Build.BaseTasks-Tests.csproj --no-restore --verbosity minimal Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../MSBuildReferences.projitems | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems index 4357bd67c55..fd2717ea2d8 100644 --- a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems +++ b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems @@ -4,7 +4,8 @@ - 17.14.28 + 18.6.3 + 10.0.3 3.3.0 7.1.0-final.1.21458.1 @@ -14,6 +15,7 @@ + From f11f5c62a00339141722795874f3d634183ab6c4 Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Wed, 27 May 2026 14:17:39 -0500 Subject: [PATCH 287/308] Add Dependabot config for weekly NuGet updates (#370) Enables Dependabot to keep our NuGet dependencies current without manual tracking. Weekly cadence keeps update noise manageable while still surfacing security and version bumps in a timely fashion. Adds `.github/dependabot.yml` with a single `nuget` ecosystem entry rooted at the repo, scheduled weekly. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- external/xamarin-android-tools/.github/dependabot.yml | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 external/xamarin-android-tools/.github/dependabot.yml diff --git a/external/xamarin-android-tools/.github/dependabot.yml b/external/xamarin-android-tools/.github/dependabot.yml new file mode 100644 index 00000000000..446b951ea26 --- /dev/null +++ b/external/xamarin-android-tools/.github/dependabot.yml @@ -0,0 +1,6 @@ +version: 2 +updates: + - package-ecosystem: "nuget" + directory: "/" + schedule: + interval: "weekly" From 22d3d746359bd823c78f0aaea1635b558c6bf494 Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Thu, 28 May 2026 08:22:46 -0500 Subject: [PATCH 288/308] [BaseTasks] Remove unused System.Reflection.Metadata reference (#377) The System.Reflection.Metadata namespace is not actually used in Files.cs, and the corresponding PackageReference in MSBuildReferences.projitems is not needed by this project. Remove both, but keep the $(SystemReflectionMetadataPackageVersion) property since downstream consumers may still rely on it. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../src/Microsoft.Android.Build.BaseTasks/Files.cs | 1 - .../MSBuildReferences.projitems | 1 - 2 files changed, 2 deletions(-) diff --git a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Files.cs b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Files.cs index de4194273d2..bdc464185fd 100644 --- a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Files.cs +++ b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/Files.cs @@ -11,7 +11,6 @@ using Xamarin.Tools.Zip; using Microsoft.Build.Utilities; using System.Threading; -using System.Reflection.Metadata; using System.Runtime.InteropServices; using System.Collections; diff --git a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems index fd2717ea2d8..720f8c705be 100644 --- a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems +++ b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems @@ -15,7 +15,6 @@ - From 25d87b2fa2a32ee5ae175b6deabe9f0703174c90 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 28 May 2026 08:27:22 -0500 Subject: [PATCH 289/308] Bump Microsoft.DotNet.Arcade.Sdk from 6.0.0-beta.20615.1 to 6.0.0-beta.26072.1 (#374) --- updated-dependencies: - dependency-name: Microsoft.DotNet.Arcade.Sdk dependency-version: 6.0.0-beta.26072.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- external/xamarin-android-tools/global.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/external/xamarin-android-tools/global.json b/external/xamarin-android-tools/global.json index be2ca665125..03e7d36e420 100644 --- a/external/xamarin-android-tools/global.json +++ b/external/xamarin-android-tools/global.json @@ -1,5 +1,5 @@ { - "msbuild-sdks": { - "Microsoft.DotNet.Arcade.Sdk": "6.0.0-beta.20615.1" - } -} + "msbuild-sdks": { + "Microsoft.DotNet.Arcade.Sdk": "6.0.0-beta.26072.1" + } +} \ No newline at end of file From 44e7c4634a2316e88c724cd2107093db621540c8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 28 May 2026 08:27:42 -0500 Subject: [PATCH 290/308] Bump BenchmarkDotNet from 0.14.0 to 0.15.8 (#371) --- updated-dependencies: - dependency-name: BenchmarkDotNet dependency-version: 0.15.8 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- external/xamarin-android-tools/Directory.Build.targets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/xamarin-android-tools/Directory.Build.targets b/external/xamarin-android-tools/Directory.Build.targets index 56abd64c2b8..830389e9797 100644 --- a/external/xamarin-android-tools/Directory.Build.targets +++ b/external/xamarin-android-tools/Directory.Build.targets @@ -27,7 +27,7 @@ - + From 6d8615194acc6e09abe35d477b33b9bd72240125 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 28 May 2026 08:28:02 -0500 Subject: [PATCH 291/308] Bump GitInfo from 2.1.2 to 3.6.0 (#372) --- updated-dependencies: - dependency-name: GitInfo dependency-version: 3.6.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- external/xamarin-android-tools/Directory.Build.targets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/xamarin-android-tools/Directory.Build.targets b/external/xamarin-android-tools/Directory.Build.targets index 830389e9797..0a60bff7b24 100644 --- a/external/xamarin-android-tools/Directory.Build.targets +++ b/external/xamarin-android-tools/Directory.Build.targets @@ -1,6 +1,6 @@ - + false From d397b18471addded778aecc56a739c16a22f8f68 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 28 May 2026 08:28:21 -0500 Subject: [PATCH 292/308] Bump Microsoft.NET.Test.Sdk from 17.5.0-preview-20221003-04 to 18.6.0 (#375) --- updated-dependencies: - dependency-name: Microsoft.NET.Test.Sdk dependency-version: 18.6.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- external/xamarin-android-tools/Directory.Build.targets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/xamarin-android-tools/Directory.Build.targets b/external/xamarin-android-tools/Directory.Build.targets index 0a60bff7b24..d3a3a396e51 100644 --- a/external/xamarin-android-tools/Directory.Build.targets +++ b/external/xamarin-android-tools/Directory.Build.targets @@ -22,7 +22,7 @@ - + From a07e2b0c78b95ca20ed31a384f6984628ea90fed Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 28 May 2026 11:20:29 -0500 Subject: [PATCH 293/308] Bump Microsoft.CodeAnalysis.PublicApiAnalyzers from 3.3.4 to 4.14.0 (#373) --- updated-dependencies: - dependency-name: Microsoft.CodeAnalysis.PublicApiAnalyzers dependency-version: 4.14.0 dependency-type: direct:production update-type: version-update:semver-major ... ### Add compiler-generated record members to PublicAPI.Unshipped.txt Microsoft.CodeAnalysis.PublicApiAnalyzers 4.14.0 now flags compiler-generated record members (Equals, GetHashCode, ToString, operators, EqualityContract, PrintMembers, copy constructor, Deconstruct, $) as missing from the declared public API. Append the missing entries for all public records in the Xamarin.Android.Tools.AndroidSdk assembly to clear the RS0016 warnings introduced by the analyzer upgrade in #373. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../PublicAPI/net10.0/PublicAPI.Unshipped.txt | 109 ++++++++++++++++++ .../netstandard2.0/PublicAPI.Unshipped.txt | 109 ++++++++++++++++++ .../Xamarin.Android.Tools.AndroidSdk.csproj | 2 +- 3 files changed, 219 insertions(+), 1 deletion(-) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/PublicAPI/net10.0/PublicAPI.Unshipped.txt b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/PublicAPI/net10.0/PublicAPI.Unshipped.txt index dd3c96f16cf..6ed1bba0cbd 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/PublicAPI/net10.0/PublicAPI.Unshipped.txt +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/PublicAPI/net10.0/PublicAPI.Unshipped.txt @@ -204,3 +204,112 @@ Xamarin.Android.Tools.AvdDeviceProfile Xamarin.Android.Tools.AvdDeviceProfile.AvdDeviceProfile(string! Id) -> void Xamarin.Android.Tools.AvdDeviceProfile.Id.get -> string! Xamarin.Android.Tools.AvdDeviceProfile.Id.init -> void +override Xamarin.Android.Tools.AdbPortRule.Equals(object? obj) -> bool +override Xamarin.Android.Tools.AdbPortRule.GetHashCode() -> int +override Xamarin.Android.Tools.AdbPortRule.ToString() -> string! +override Xamarin.Android.Tools.AdbPortSpec.Equals(object? obj) -> bool +override Xamarin.Android.Tools.AdbPortSpec.GetHashCode() -> int +override Xamarin.Android.Tools.AvdDeviceProfile.Equals(object? obj) -> bool +override Xamarin.Android.Tools.AvdDeviceProfile.GetHashCode() -> int +override Xamarin.Android.Tools.AvdDeviceProfile.ToString() -> string! +override Xamarin.Android.Tools.AvdInfo.Equals(object? obj) -> bool +override Xamarin.Android.Tools.AvdInfo.GetHashCode() -> int +override Xamarin.Android.Tools.AvdInfo.ToString() -> string! +override Xamarin.Android.Tools.EmulatorBootOptions.Equals(object? obj) -> bool +override Xamarin.Android.Tools.EmulatorBootOptions.GetHashCode() -> int +override Xamarin.Android.Tools.EmulatorBootOptions.ToString() -> string! +override Xamarin.Android.Tools.EmulatorBootResult.Equals(object? obj) -> bool +override Xamarin.Android.Tools.EmulatorBootResult.GetHashCode() -> int +override Xamarin.Android.Tools.EmulatorBootResult.ToString() -> string! +override Xamarin.Android.Tools.JdkInstallProgress.Equals(object? obj) -> bool +override Xamarin.Android.Tools.JdkInstallProgress.GetHashCode() -> int +override Xamarin.Android.Tools.JdkInstallProgress.ToString() -> string! +override Xamarin.Android.Tools.SdkBootstrapProgress.Equals(object? obj) -> bool +override Xamarin.Android.Tools.SdkBootstrapProgress.GetHashCode() -> int +override Xamarin.Android.Tools.SdkBootstrapProgress.ToString() -> string! +override Xamarin.Android.Tools.SdkLicense.Equals(object? obj) -> bool +override Xamarin.Android.Tools.SdkLicense.GetHashCode() -> int +override Xamarin.Android.Tools.SdkLicense.ToString() -> string! +override Xamarin.Android.Tools.SdkPackage.Equals(object? obj) -> bool +override Xamarin.Android.Tools.SdkPackage.GetHashCode() -> int +override Xamarin.Android.Tools.SdkPackage.ToString() -> string! +static Xamarin.Android.Tools.AdbPortRule.operator !=(Xamarin.Android.Tools.AdbPortRule? left, Xamarin.Android.Tools.AdbPortRule? right) -> bool +static Xamarin.Android.Tools.AdbPortRule.operator ==(Xamarin.Android.Tools.AdbPortRule? left, Xamarin.Android.Tools.AdbPortRule? right) -> bool +static Xamarin.Android.Tools.AdbPortSpec.operator !=(Xamarin.Android.Tools.AdbPortSpec? left, Xamarin.Android.Tools.AdbPortSpec? right) -> bool +static Xamarin.Android.Tools.AdbPortSpec.operator ==(Xamarin.Android.Tools.AdbPortSpec? left, Xamarin.Android.Tools.AdbPortSpec? right) -> bool +static Xamarin.Android.Tools.AvdDeviceProfile.operator !=(Xamarin.Android.Tools.AvdDeviceProfile? left, Xamarin.Android.Tools.AvdDeviceProfile? right) -> bool +static Xamarin.Android.Tools.AvdDeviceProfile.operator ==(Xamarin.Android.Tools.AvdDeviceProfile? left, Xamarin.Android.Tools.AvdDeviceProfile? right) -> bool +static Xamarin.Android.Tools.AvdInfo.operator !=(Xamarin.Android.Tools.AvdInfo? left, Xamarin.Android.Tools.AvdInfo? right) -> bool +static Xamarin.Android.Tools.AvdInfo.operator ==(Xamarin.Android.Tools.AvdInfo? left, Xamarin.Android.Tools.AvdInfo? right) -> bool +static Xamarin.Android.Tools.EmulatorBootOptions.operator !=(Xamarin.Android.Tools.EmulatorBootOptions? left, Xamarin.Android.Tools.EmulatorBootOptions? right) -> bool +static Xamarin.Android.Tools.EmulatorBootOptions.operator ==(Xamarin.Android.Tools.EmulatorBootOptions? left, Xamarin.Android.Tools.EmulatorBootOptions? right) -> bool +static Xamarin.Android.Tools.EmulatorBootResult.operator !=(Xamarin.Android.Tools.EmulatorBootResult? left, Xamarin.Android.Tools.EmulatorBootResult? right) -> bool +static Xamarin.Android.Tools.EmulatorBootResult.operator ==(Xamarin.Android.Tools.EmulatorBootResult? left, Xamarin.Android.Tools.EmulatorBootResult? right) -> bool +static Xamarin.Android.Tools.JdkInstallProgress.operator !=(Xamarin.Android.Tools.JdkInstallProgress? left, Xamarin.Android.Tools.JdkInstallProgress? right) -> bool +static Xamarin.Android.Tools.JdkInstallProgress.operator ==(Xamarin.Android.Tools.JdkInstallProgress? left, Xamarin.Android.Tools.JdkInstallProgress? right) -> bool +static Xamarin.Android.Tools.SdkBootstrapProgress.operator !=(Xamarin.Android.Tools.SdkBootstrapProgress? left, Xamarin.Android.Tools.SdkBootstrapProgress? right) -> bool +static Xamarin.Android.Tools.SdkBootstrapProgress.operator ==(Xamarin.Android.Tools.SdkBootstrapProgress? left, Xamarin.Android.Tools.SdkBootstrapProgress? right) -> bool +static Xamarin.Android.Tools.SdkLicense.operator !=(Xamarin.Android.Tools.SdkLicense? left, Xamarin.Android.Tools.SdkLicense? right) -> bool +static Xamarin.Android.Tools.SdkLicense.operator ==(Xamarin.Android.Tools.SdkLicense? left, Xamarin.Android.Tools.SdkLicense? right) -> bool +static Xamarin.Android.Tools.SdkPackage.operator !=(Xamarin.Android.Tools.SdkPackage? left, Xamarin.Android.Tools.SdkPackage? right) -> bool +static Xamarin.Android.Tools.SdkPackage.operator ==(Xamarin.Android.Tools.SdkPackage? left, Xamarin.Android.Tools.SdkPackage? right) -> bool +virtual Xamarin.Android.Tools.AdbPortRule.$() -> Xamarin.Android.Tools.AdbPortRule! +virtual Xamarin.Android.Tools.AdbPortRule.EqualityContract.get -> System.Type! +virtual Xamarin.Android.Tools.AdbPortRule.Equals(Xamarin.Android.Tools.AdbPortRule? other) -> bool +virtual Xamarin.Android.Tools.AdbPortRule.PrintMembers(System.Text.StringBuilder! builder) -> bool +virtual Xamarin.Android.Tools.AdbPortSpec.$() -> Xamarin.Android.Tools.AdbPortSpec! +virtual Xamarin.Android.Tools.AdbPortSpec.EqualityContract.get -> System.Type! +virtual Xamarin.Android.Tools.AdbPortSpec.Equals(Xamarin.Android.Tools.AdbPortSpec? other) -> bool +virtual Xamarin.Android.Tools.AdbPortSpec.PrintMembers(System.Text.StringBuilder! builder) -> bool +virtual Xamarin.Android.Tools.AvdDeviceProfile.$() -> Xamarin.Android.Tools.AvdDeviceProfile! +virtual Xamarin.Android.Tools.AvdDeviceProfile.EqualityContract.get -> System.Type! +virtual Xamarin.Android.Tools.AvdDeviceProfile.Equals(Xamarin.Android.Tools.AvdDeviceProfile? other) -> bool +virtual Xamarin.Android.Tools.AvdDeviceProfile.PrintMembers(System.Text.StringBuilder! builder) -> bool +virtual Xamarin.Android.Tools.AvdInfo.$() -> Xamarin.Android.Tools.AvdInfo! +virtual Xamarin.Android.Tools.AvdInfo.EqualityContract.get -> System.Type! +virtual Xamarin.Android.Tools.AvdInfo.Equals(Xamarin.Android.Tools.AvdInfo? other) -> bool +virtual Xamarin.Android.Tools.AvdInfo.PrintMembers(System.Text.StringBuilder! builder) -> bool +virtual Xamarin.Android.Tools.EmulatorBootOptions.$() -> Xamarin.Android.Tools.EmulatorBootOptions! +virtual Xamarin.Android.Tools.EmulatorBootOptions.EqualityContract.get -> System.Type! +virtual Xamarin.Android.Tools.EmulatorBootOptions.Equals(Xamarin.Android.Tools.EmulatorBootOptions? other) -> bool +virtual Xamarin.Android.Tools.EmulatorBootOptions.PrintMembers(System.Text.StringBuilder! builder) -> bool +virtual Xamarin.Android.Tools.EmulatorBootResult.$() -> Xamarin.Android.Tools.EmulatorBootResult! +virtual Xamarin.Android.Tools.EmulatorBootResult.EqualityContract.get -> System.Type! +virtual Xamarin.Android.Tools.EmulatorBootResult.Equals(Xamarin.Android.Tools.EmulatorBootResult? other) -> bool +virtual Xamarin.Android.Tools.EmulatorBootResult.PrintMembers(System.Text.StringBuilder! builder) -> bool +virtual Xamarin.Android.Tools.JdkInstallProgress.$() -> Xamarin.Android.Tools.JdkInstallProgress! +virtual Xamarin.Android.Tools.JdkInstallProgress.EqualityContract.get -> System.Type! +virtual Xamarin.Android.Tools.JdkInstallProgress.Equals(Xamarin.Android.Tools.JdkInstallProgress? other) -> bool +virtual Xamarin.Android.Tools.JdkInstallProgress.PrintMembers(System.Text.StringBuilder! builder) -> bool +virtual Xamarin.Android.Tools.SdkBootstrapProgress.$() -> Xamarin.Android.Tools.SdkBootstrapProgress! +virtual Xamarin.Android.Tools.SdkBootstrapProgress.EqualityContract.get -> System.Type! +virtual Xamarin.Android.Tools.SdkBootstrapProgress.Equals(Xamarin.Android.Tools.SdkBootstrapProgress? other) -> bool +virtual Xamarin.Android.Tools.SdkBootstrapProgress.PrintMembers(System.Text.StringBuilder! builder) -> bool +virtual Xamarin.Android.Tools.SdkLicense.$() -> Xamarin.Android.Tools.SdkLicense! +virtual Xamarin.Android.Tools.SdkLicense.EqualityContract.get -> System.Type! +virtual Xamarin.Android.Tools.SdkLicense.Equals(Xamarin.Android.Tools.SdkLicense? other) -> bool +virtual Xamarin.Android.Tools.SdkLicense.PrintMembers(System.Text.StringBuilder! builder) -> bool +virtual Xamarin.Android.Tools.SdkPackage.$() -> Xamarin.Android.Tools.SdkPackage! +virtual Xamarin.Android.Tools.SdkPackage.EqualityContract.get -> System.Type! +virtual Xamarin.Android.Tools.SdkPackage.Equals(Xamarin.Android.Tools.SdkPackage? other) -> bool +virtual Xamarin.Android.Tools.SdkPackage.PrintMembers(System.Text.StringBuilder! builder) -> bool +Xamarin.Android.Tools.AdbPortRule.AdbPortRule(Xamarin.Android.Tools.AdbPortRule! original) -> void +Xamarin.Android.Tools.AdbPortRule.Deconstruct(out Xamarin.Android.Tools.AdbPortSpec! Remote, out Xamarin.Android.Tools.AdbPortSpec! Local) -> void +Xamarin.Android.Tools.AdbPortSpec.AdbPortSpec(Xamarin.Android.Tools.AdbPortSpec! original) -> void +Xamarin.Android.Tools.AdbPortSpec.Deconstruct(out Xamarin.Android.Tools.AdbProtocol Protocol, out int Port) -> void +Xamarin.Android.Tools.AvdDeviceProfile.AvdDeviceProfile(Xamarin.Android.Tools.AvdDeviceProfile! original) -> void +Xamarin.Android.Tools.AvdDeviceProfile.Deconstruct(out string! Id) -> void +Xamarin.Android.Tools.AvdInfo.AvdInfo(Xamarin.Android.Tools.AvdInfo! original) -> void +Xamarin.Android.Tools.AvdInfo.Deconstruct(out string! Name, out string? DeviceProfile, out string? Path) -> void +Xamarin.Android.Tools.EmulatorBootOptions.EmulatorBootOptions() -> void +Xamarin.Android.Tools.EmulatorBootOptions.EmulatorBootOptions(Xamarin.Android.Tools.EmulatorBootOptions! original) -> void +Xamarin.Android.Tools.EmulatorBootResult.EmulatorBootResult() -> void +Xamarin.Android.Tools.EmulatorBootResult.EmulatorBootResult(Xamarin.Android.Tools.EmulatorBootResult! original) -> void +Xamarin.Android.Tools.JdkInstallProgress.Deconstruct(out Xamarin.Android.Tools.JdkInstallPhase Phase, out double PercentComplete, out string? Message) -> void +Xamarin.Android.Tools.JdkInstallProgress.JdkInstallProgress(Xamarin.Android.Tools.JdkInstallProgress! original) -> void +Xamarin.Android.Tools.SdkBootstrapProgress.Deconstruct(out Xamarin.Android.Tools.SdkBootstrapPhase Phase, out int PercentComplete, out string! Message) -> void +Xamarin.Android.Tools.SdkBootstrapProgress.SdkBootstrapProgress(Xamarin.Android.Tools.SdkBootstrapProgress! original) -> void +Xamarin.Android.Tools.SdkLicense.Deconstruct(out string! Id, out string! Text) -> void +Xamarin.Android.Tools.SdkLicense.SdkLicense(Xamarin.Android.Tools.SdkLicense! original) -> void +Xamarin.Android.Tools.SdkPackage.Deconstruct(out string! Path, out string? Version, out string? Description, out bool IsInstalled) -> void +Xamarin.Android.Tools.SdkPackage.SdkPackage(Xamarin.Android.Tools.SdkPackage! original) -> void diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt index dd3c96f16cf..6ed1bba0cbd 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt @@ -204,3 +204,112 @@ Xamarin.Android.Tools.AvdDeviceProfile Xamarin.Android.Tools.AvdDeviceProfile.AvdDeviceProfile(string! Id) -> void Xamarin.Android.Tools.AvdDeviceProfile.Id.get -> string! Xamarin.Android.Tools.AvdDeviceProfile.Id.init -> void +override Xamarin.Android.Tools.AdbPortRule.Equals(object? obj) -> bool +override Xamarin.Android.Tools.AdbPortRule.GetHashCode() -> int +override Xamarin.Android.Tools.AdbPortRule.ToString() -> string! +override Xamarin.Android.Tools.AdbPortSpec.Equals(object? obj) -> bool +override Xamarin.Android.Tools.AdbPortSpec.GetHashCode() -> int +override Xamarin.Android.Tools.AvdDeviceProfile.Equals(object? obj) -> bool +override Xamarin.Android.Tools.AvdDeviceProfile.GetHashCode() -> int +override Xamarin.Android.Tools.AvdDeviceProfile.ToString() -> string! +override Xamarin.Android.Tools.AvdInfo.Equals(object? obj) -> bool +override Xamarin.Android.Tools.AvdInfo.GetHashCode() -> int +override Xamarin.Android.Tools.AvdInfo.ToString() -> string! +override Xamarin.Android.Tools.EmulatorBootOptions.Equals(object? obj) -> bool +override Xamarin.Android.Tools.EmulatorBootOptions.GetHashCode() -> int +override Xamarin.Android.Tools.EmulatorBootOptions.ToString() -> string! +override Xamarin.Android.Tools.EmulatorBootResult.Equals(object? obj) -> bool +override Xamarin.Android.Tools.EmulatorBootResult.GetHashCode() -> int +override Xamarin.Android.Tools.EmulatorBootResult.ToString() -> string! +override Xamarin.Android.Tools.JdkInstallProgress.Equals(object? obj) -> bool +override Xamarin.Android.Tools.JdkInstallProgress.GetHashCode() -> int +override Xamarin.Android.Tools.JdkInstallProgress.ToString() -> string! +override Xamarin.Android.Tools.SdkBootstrapProgress.Equals(object? obj) -> bool +override Xamarin.Android.Tools.SdkBootstrapProgress.GetHashCode() -> int +override Xamarin.Android.Tools.SdkBootstrapProgress.ToString() -> string! +override Xamarin.Android.Tools.SdkLicense.Equals(object? obj) -> bool +override Xamarin.Android.Tools.SdkLicense.GetHashCode() -> int +override Xamarin.Android.Tools.SdkLicense.ToString() -> string! +override Xamarin.Android.Tools.SdkPackage.Equals(object? obj) -> bool +override Xamarin.Android.Tools.SdkPackage.GetHashCode() -> int +override Xamarin.Android.Tools.SdkPackage.ToString() -> string! +static Xamarin.Android.Tools.AdbPortRule.operator !=(Xamarin.Android.Tools.AdbPortRule? left, Xamarin.Android.Tools.AdbPortRule? right) -> bool +static Xamarin.Android.Tools.AdbPortRule.operator ==(Xamarin.Android.Tools.AdbPortRule? left, Xamarin.Android.Tools.AdbPortRule? right) -> bool +static Xamarin.Android.Tools.AdbPortSpec.operator !=(Xamarin.Android.Tools.AdbPortSpec? left, Xamarin.Android.Tools.AdbPortSpec? right) -> bool +static Xamarin.Android.Tools.AdbPortSpec.operator ==(Xamarin.Android.Tools.AdbPortSpec? left, Xamarin.Android.Tools.AdbPortSpec? right) -> bool +static Xamarin.Android.Tools.AvdDeviceProfile.operator !=(Xamarin.Android.Tools.AvdDeviceProfile? left, Xamarin.Android.Tools.AvdDeviceProfile? right) -> bool +static Xamarin.Android.Tools.AvdDeviceProfile.operator ==(Xamarin.Android.Tools.AvdDeviceProfile? left, Xamarin.Android.Tools.AvdDeviceProfile? right) -> bool +static Xamarin.Android.Tools.AvdInfo.operator !=(Xamarin.Android.Tools.AvdInfo? left, Xamarin.Android.Tools.AvdInfo? right) -> bool +static Xamarin.Android.Tools.AvdInfo.operator ==(Xamarin.Android.Tools.AvdInfo? left, Xamarin.Android.Tools.AvdInfo? right) -> bool +static Xamarin.Android.Tools.EmulatorBootOptions.operator !=(Xamarin.Android.Tools.EmulatorBootOptions? left, Xamarin.Android.Tools.EmulatorBootOptions? right) -> bool +static Xamarin.Android.Tools.EmulatorBootOptions.operator ==(Xamarin.Android.Tools.EmulatorBootOptions? left, Xamarin.Android.Tools.EmulatorBootOptions? right) -> bool +static Xamarin.Android.Tools.EmulatorBootResult.operator !=(Xamarin.Android.Tools.EmulatorBootResult? left, Xamarin.Android.Tools.EmulatorBootResult? right) -> bool +static Xamarin.Android.Tools.EmulatorBootResult.operator ==(Xamarin.Android.Tools.EmulatorBootResult? left, Xamarin.Android.Tools.EmulatorBootResult? right) -> bool +static Xamarin.Android.Tools.JdkInstallProgress.operator !=(Xamarin.Android.Tools.JdkInstallProgress? left, Xamarin.Android.Tools.JdkInstallProgress? right) -> bool +static Xamarin.Android.Tools.JdkInstallProgress.operator ==(Xamarin.Android.Tools.JdkInstallProgress? left, Xamarin.Android.Tools.JdkInstallProgress? right) -> bool +static Xamarin.Android.Tools.SdkBootstrapProgress.operator !=(Xamarin.Android.Tools.SdkBootstrapProgress? left, Xamarin.Android.Tools.SdkBootstrapProgress? right) -> bool +static Xamarin.Android.Tools.SdkBootstrapProgress.operator ==(Xamarin.Android.Tools.SdkBootstrapProgress? left, Xamarin.Android.Tools.SdkBootstrapProgress? right) -> bool +static Xamarin.Android.Tools.SdkLicense.operator !=(Xamarin.Android.Tools.SdkLicense? left, Xamarin.Android.Tools.SdkLicense? right) -> bool +static Xamarin.Android.Tools.SdkLicense.operator ==(Xamarin.Android.Tools.SdkLicense? left, Xamarin.Android.Tools.SdkLicense? right) -> bool +static Xamarin.Android.Tools.SdkPackage.operator !=(Xamarin.Android.Tools.SdkPackage? left, Xamarin.Android.Tools.SdkPackage? right) -> bool +static Xamarin.Android.Tools.SdkPackage.operator ==(Xamarin.Android.Tools.SdkPackage? left, Xamarin.Android.Tools.SdkPackage? right) -> bool +virtual Xamarin.Android.Tools.AdbPortRule.$() -> Xamarin.Android.Tools.AdbPortRule! +virtual Xamarin.Android.Tools.AdbPortRule.EqualityContract.get -> System.Type! +virtual Xamarin.Android.Tools.AdbPortRule.Equals(Xamarin.Android.Tools.AdbPortRule? other) -> bool +virtual Xamarin.Android.Tools.AdbPortRule.PrintMembers(System.Text.StringBuilder! builder) -> bool +virtual Xamarin.Android.Tools.AdbPortSpec.$() -> Xamarin.Android.Tools.AdbPortSpec! +virtual Xamarin.Android.Tools.AdbPortSpec.EqualityContract.get -> System.Type! +virtual Xamarin.Android.Tools.AdbPortSpec.Equals(Xamarin.Android.Tools.AdbPortSpec? other) -> bool +virtual Xamarin.Android.Tools.AdbPortSpec.PrintMembers(System.Text.StringBuilder! builder) -> bool +virtual Xamarin.Android.Tools.AvdDeviceProfile.$() -> Xamarin.Android.Tools.AvdDeviceProfile! +virtual Xamarin.Android.Tools.AvdDeviceProfile.EqualityContract.get -> System.Type! +virtual Xamarin.Android.Tools.AvdDeviceProfile.Equals(Xamarin.Android.Tools.AvdDeviceProfile? other) -> bool +virtual Xamarin.Android.Tools.AvdDeviceProfile.PrintMembers(System.Text.StringBuilder! builder) -> bool +virtual Xamarin.Android.Tools.AvdInfo.$() -> Xamarin.Android.Tools.AvdInfo! +virtual Xamarin.Android.Tools.AvdInfo.EqualityContract.get -> System.Type! +virtual Xamarin.Android.Tools.AvdInfo.Equals(Xamarin.Android.Tools.AvdInfo? other) -> bool +virtual Xamarin.Android.Tools.AvdInfo.PrintMembers(System.Text.StringBuilder! builder) -> bool +virtual Xamarin.Android.Tools.EmulatorBootOptions.$() -> Xamarin.Android.Tools.EmulatorBootOptions! +virtual Xamarin.Android.Tools.EmulatorBootOptions.EqualityContract.get -> System.Type! +virtual Xamarin.Android.Tools.EmulatorBootOptions.Equals(Xamarin.Android.Tools.EmulatorBootOptions? other) -> bool +virtual Xamarin.Android.Tools.EmulatorBootOptions.PrintMembers(System.Text.StringBuilder! builder) -> bool +virtual Xamarin.Android.Tools.EmulatorBootResult.$() -> Xamarin.Android.Tools.EmulatorBootResult! +virtual Xamarin.Android.Tools.EmulatorBootResult.EqualityContract.get -> System.Type! +virtual Xamarin.Android.Tools.EmulatorBootResult.Equals(Xamarin.Android.Tools.EmulatorBootResult? other) -> bool +virtual Xamarin.Android.Tools.EmulatorBootResult.PrintMembers(System.Text.StringBuilder! builder) -> bool +virtual Xamarin.Android.Tools.JdkInstallProgress.$() -> Xamarin.Android.Tools.JdkInstallProgress! +virtual Xamarin.Android.Tools.JdkInstallProgress.EqualityContract.get -> System.Type! +virtual Xamarin.Android.Tools.JdkInstallProgress.Equals(Xamarin.Android.Tools.JdkInstallProgress? other) -> bool +virtual Xamarin.Android.Tools.JdkInstallProgress.PrintMembers(System.Text.StringBuilder! builder) -> bool +virtual Xamarin.Android.Tools.SdkBootstrapProgress.$() -> Xamarin.Android.Tools.SdkBootstrapProgress! +virtual Xamarin.Android.Tools.SdkBootstrapProgress.EqualityContract.get -> System.Type! +virtual Xamarin.Android.Tools.SdkBootstrapProgress.Equals(Xamarin.Android.Tools.SdkBootstrapProgress? other) -> bool +virtual Xamarin.Android.Tools.SdkBootstrapProgress.PrintMembers(System.Text.StringBuilder! builder) -> bool +virtual Xamarin.Android.Tools.SdkLicense.$() -> Xamarin.Android.Tools.SdkLicense! +virtual Xamarin.Android.Tools.SdkLicense.EqualityContract.get -> System.Type! +virtual Xamarin.Android.Tools.SdkLicense.Equals(Xamarin.Android.Tools.SdkLicense? other) -> bool +virtual Xamarin.Android.Tools.SdkLicense.PrintMembers(System.Text.StringBuilder! builder) -> bool +virtual Xamarin.Android.Tools.SdkPackage.$() -> Xamarin.Android.Tools.SdkPackage! +virtual Xamarin.Android.Tools.SdkPackage.EqualityContract.get -> System.Type! +virtual Xamarin.Android.Tools.SdkPackage.Equals(Xamarin.Android.Tools.SdkPackage? other) -> bool +virtual Xamarin.Android.Tools.SdkPackage.PrintMembers(System.Text.StringBuilder! builder) -> bool +Xamarin.Android.Tools.AdbPortRule.AdbPortRule(Xamarin.Android.Tools.AdbPortRule! original) -> void +Xamarin.Android.Tools.AdbPortRule.Deconstruct(out Xamarin.Android.Tools.AdbPortSpec! Remote, out Xamarin.Android.Tools.AdbPortSpec! Local) -> void +Xamarin.Android.Tools.AdbPortSpec.AdbPortSpec(Xamarin.Android.Tools.AdbPortSpec! original) -> void +Xamarin.Android.Tools.AdbPortSpec.Deconstruct(out Xamarin.Android.Tools.AdbProtocol Protocol, out int Port) -> void +Xamarin.Android.Tools.AvdDeviceProfile.AvdDeviceProfile(Xamarin.Android.Tools.AvdDeviceProfile! original) -> void +Xamarin.Android.Tools.AvdDeviceProfile.Deconstruct(out string! Id) -> void +Xamarin.Android.Tools.AvdInfo.AvdInfo(Xamarin.Android.Tools.AvdInfo! original) -> void +Xamarin.Android.Tools.AvdInfo.Deconstruct(out string! Name, out string? DeviceProfile, out string? Path) -> void +Xamarin.Android.Tools.EmulatorBootOptions.EmulatorBootOptions() -> void +Xamarin.Android.Tools.EmulatorBootOptions.EmulatorBootOptions(Xamarin.Android.Tools.EmulatorBootOptions! original) -> void +Xamarin.Android.Tools.EmulatorBootResult.EmulatorBootResult() -> void +Xamarin.Android.Tools.EmulatorBootResult.EmulatorBootResult(Xamarin.Android.Tools.EmulatorBootResult! original) -> void +Xamarin.Android.Tools.JdkInstallProgress.Deconstruct(out Xamarin.Android.Tools.JdkInstallPhase Phase, out double PercentComplete, out string? Message) -> void +Xamarin.Android.Tools.JdkInstallProgress.JdkInstallProgress(Xamarin.Android.Tools.JdkInstallProgress! original) -> void +Xamarin.Android.Tools.SdkBootstrapProgress.Deconstruct(out Xamarin.Android.Tools.SdkBootstrapPhase Phase, out int PercentComplete, out string! Message) -> void +Xamarin.Android.Tools.SdkBootstrapProgress.SdkBootstrapProgress(Xamarin.Android.Tools.SdkBootstrapProgress! original) -> void +Xamarin.Android.Tools.SdkLicense.Deconstruct(out string! Id, out string! Text) -> void +Xamarin.Android.Tools.SdkLicense.SdkLicense(Xamarin.Android.Tools.SdkLicense! original) -> void +Xamarin.Android.Tools.SdkPackage.Deconstruct(out string! Path, out string? Version, out string? Description, out bool IsInstalled) -> void +Xamarin.Android.Tools.SdkPackage.SdkPackage(Xamarin.Android.Tools.SdkPackage! original) -> void diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj index 4daa59c38b8..e46a29c0143 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj @@ -37,7 +37,7 @@ all runtime; build; native; contentfiles; analyzers - + From 3cd8ed0b3dc4690751e0f37b47ae10c483dc46d1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 2 Jun 2026 08:36:14 -0500 Subject: [PATCH 294/308] Bump System.IO.Hashing from 10.0.7 to 10.0.8 (#381) --- updated-dependencies: - dependency-name: System.IO.Hashing dependency-version: 10.0.8 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- external/xamarin-android-tools/Directory.Build.targets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/xamarin-android-tools/Directory.Build.targets b/external/xamarin-android-tools/Directory.Build.targets index d3a3a396e51..c0840c1b7a6 100644 --- a/external/xamarin-android-tools/Directory.Build.targets +++ b/external/xamarin-android-tools/Directory.Build.targets @@ -28,7 +28,7 @@ - + From 67460a326899098079f234a9194cdb743dfc716d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 2 Jun 2026 08:36:33 -0500 Subject: [PATCH 295/308] Bump Microsoft.SourceLink.GitHub from 1.1.1 to 10.0.300 (#378) --- updated-dependencies: - dependency-name: Microsoft.SourceLink.GitHub dependency-version: 10.0.300 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- external/xamarin-android-tools/Directory.Build.targets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/xamarin-android-tools/Directory.Build.targets b/external/xamarin-android-tools/Directory.Build.targets index c0840c1b7a6..64a61db130a 100644 --- a/external/xamarin-android-tools/Directory.Build.targets +++ b/external/xamarin-android-tools/Directory.Build.targets @@ -23,7 +23,7 @@ - + From c57175eb720683af85eb55d3c0adaec63b7759ed Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 2 Jun 2026 10:25:39 -0500 Subject: [PATCH 296/308] Bump NUnit3TestAdapter from 4.0.0 to 6.2.0 (#380) --- updated-dependencies: - dependency-name: NUnit3TestAdapter dependency-version: 6.2.0 dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- external/xamarin-android-tools/Directory.Build.targets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/xamarin-android-tools/Directory.Build.targets b/external/xamarin-android-tools/Directory.Build.targets index 64a61db130a..5c0b145bc48 100644 --- a/external/xamarin-android-tools/Directory.Build.targets +++ b/external/xamarin-android-tools/Directory.Build.targets @@ -26,7 +26,7 @@ - + From 739e6db960f07a5d7ddaa70516911337abbb7187 Mon Sep 17 00:00:00 2001 From: Rui Marinho Date: Tue, 2 Jun 2026 17:28:29 +0100 Subject: [PATCH 297/308] [AdbRunner] Add ADB forward port management (#351) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit (follow-up to #305) Adds the symmetric forward-port pair to the reverse-port methods that landed in #305. Same `AdbPortSpec` / `AdbPortRule` / `AdbProtocol` types — just four new methods. | Method | adb command | |-------------------------------------------------|--------------------------------------------| | ForwardPortAsync(serial, local, remote) | adb -s forward | | RemoveForwardPortAsync(serial, local) | adb -s forward --remove | | RemoveAllForwardPortsAsync(serial) | adb -s forward --remove-all | | ListForwardPortsAsync(serial) | adb forward --list (filtered by serial) | `adb forward` and `adb reverse` are not interchangeable — they connect opposite directions. `forward` is host->device (the IDE/harness reaches a service running on the device) and is the path used for JDWP debugger attach (`forward tcp:N jdwp:`), perf-tracing endpoints exposed by the runtime, and host-side DevFlow agent connect when the agent listens on a device port. Output-format note for `--list`: `adb forward --list` emits one line per rule across all devices in the form ` ` (different from `(reverse) `). `ListForwardPortsAsync` uses the unscoped `adb forward --list` and filters to the requested serial in `ParseForwardListOutput`. Serial match is case-sensitive (matches adb). - 12 new parser tests in `ParseForwardListOutput_*` mirroring the reverse parser tests (single rule, multiple rules, serial filtering, empty output, malformed lines, non-tcp specs, Windows line endings, tab separation, case sensitivity). - 7 new parameter-validation tests covering empty serial / null spec for the four new public methods. - VS Code MAUI extension ServiceHub->CLI migration (`forwardPort` in `MauiAndroidPlatform.ts` — debugger configurations, perf tooling). - MAUI DevTools CLI (dotnet/maui-labs#197) — `maui android port forward` group, sibling of the existing `reverse` surface. - Visual Studio `ClientTools.Platform` — same paths that drive reverse today. Discussed in https://github.com/dotnet/android-tools/pull/305#issuecomment-4352578817 ### Address review feedback: capture stdout in ThrowIfFailed, parser asymmetry comment, drop null!, remove test region dividers - ForwardPortAsync/RemoveForwardPortAsync/RemoveAllForwardPortsAsync now capture stdout and pass it to ProcessUtils.ThrowIfFailed (matches repo convention; adb sometimes writes errors to stdout). - Added block on ParseForwardListOutput calling out the field-order asymmetry vs ParseReverseListOutput (forward: serial local remote; reverse: (reverse) remote local). - Replaced '(AdbPortSpec) null!' with '(AdbPortSpec) null' in 3 forward-port test sites to match reverse-test convention and repo no-null-forgiving rule. - Removed all '// --- ... ---' region-like divider comments in AdbRunnerTests.cs (per jonathanpeppers feedback in PR #351). ### Fix RemoveAllForwardPortsAsync to honour per-serial scope The underlying 'adb forward --remove-all' (and the wire-protocol equivalent 'host-serial::killforward-all') is daemon-global -- the '-s ' flag does not scope it. The previous implementation would silently remove forwards for every connected device despite the method's per-device API contract. Reimplement by listing forwards for the given serial via ListForwardPortsAsync and removing them individually via RemoveForwardPortAsync. Update the XML docs to describe the actual behaviour. Add two new tests using a recording subclass of AdbRunner that overrides ListForwardPortsAsync and RemoveForwardPortAsync to verify (1) only ports for the requested serial are removed, and (2) an empty listing is a no-op. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../PublicAPI/net10.0/PublicAPI.Unshipped.txt | 4 + .../netstandard2.0/PublicAPI.Unshipped.txt | 4 + .../Runners/AdbRunner.cs | 139 ++++++++ .../AdbRunnerTests.cs | 302 ++++++++++++++++-- 4 files changed, 427 insertions(+), 22 deletions(-) diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/PublicAPI/net10.0/PublicAPI.Unshipped.txt b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/PublicAPI/net10.0/PublicAPI.Unshipped.txt index 6ed1bba0cbd..21b4baba56a 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/PublicAPI/net10.0/PublicAPI.Unshipped.txt +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/PublicAPI/net10.0/PublicAPI.Unshipped.txt @@ -199,6 +199,10 @@ virtual Xamarin.Android.Tools.AdbRunner.ListReversePortsAsync(string! serial, Sy virtual Xamarin.Android.Tools.AdbRunner.RemoveAllReversePortsAsync(string! serial, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task! virtual Xamarin.Android.Tools.AdbRunner.RemoveReversePortAsync(string! serial, Xamarin.Android.Tools.AdbPortSpec! remote, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task! virtual Xamarin.Android.Tools.AdbRunner.ReversePortAsync(string! serial, Xamarin.Android.Tools.AdbPortSpec! remote, Xamarin.Android.Tools.AdbPortSpec! local, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task! +virtual Xamarin.Android.Tools.AdbRunner.ForwardPortAsync(string! serial, Xamarin.Android.Tools.AdbPortSpec! local, Xamarin.Android.Tools.AdbPortSpec! remote, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task! +virtual Xamarin.Android.Tools.AdbRunner.ListForwardPortsAsync(string! serial, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task!>! +virtual Xamarin.Android.Tools.AdbRunner.RemoveAllForwardPortsAsync(string! serial, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task! +virtual Xamarin.Android.Tools.AdbRunner.RemoveForwardPortAsync(string! serial, Xamarin.Android.Tools.AdbPortSpec! local, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task! Xamarin.Android.Tools.AvdManagerRunner.ListDeviceProfilesAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task!>! Xamarin.Android.Tools.AvdDeviceProfile Xamarin.Android.Tools.AvdDeviceProfile.AvdDeviceProfile(string! Id) -> void diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt index 6ed1bba0cbd..21b4baba56a 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt @@ -199,6 +199,10 @@ virtual Xamarin.Android.Tools.AdbRunner.ListReversePortsAsync(string! serial, Sy virtual Xamarin.Android.Tools.AdbRunner.RemoveAllReversePortsAsync(string! serial, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task! virtual Xamarin.Android.Tools.AdbRunner.RemoveReversePortAsync(string! serial, Xamarin.Android.Tools.AdbPortSpec! remote, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task! virtual Xamarin.Android.Tools.AdbRunner.ReversePortAsync(string! serial, Xamarin.Android.Tools.AdbPortSpec! remote, Xamarin.Android.Tools.AdbPortSpec! local, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task! +virtual Xamarin.Android.Tools.AdbRunner.ForwardPortAsync(string! serial, Xamarin.Android.Tools.AdbPortSpec! local, Xamarin.Android.Tools.AdbPortSpec! remote, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task! +virtual Xamarin.Android.Tools.AdbRunner.ListForwardPortsAsync(string! serial, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task!>! +virtual Xamarin.Android.Tools.AdbRunner.RemoveAllForwardPortsAsync(string! serial, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task! +virtual Xamarin.Android.Tools.AdbRunner.RemoveForwardPortAsync(string! serial, Xamarin.Android.Tools.AdbPortSpec! local, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task! Xamarin.Android.Tools.AvdManagerRunner.ListDeviceProfilesAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task!>! Xamarin.Android.Tools.AvdDeviceProfile Xamarin.Android.Tools.AvdDeviceProfile.AvdDeviceProfile(string! Id) -> void diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Runners/AdbRunner.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Runners/AdbRunner.cs index e46900ed333..eefb155c24a 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Runners/AdbRunner.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Runners/AdbRunner.cs @@ -355,6 +355,145 @@ internal static IReadOnlyList ParseReverseListOutput (IEnumerable + /// Sets up forward port forwarding via 'adb -s <serial> forward <local> <remote>'. + /// The host-side <local> socket is forwarded to the device-side <remote> socket, + /// the symmetric pair to . + /// + /// Device serial number. + /// Local (host-side) port spec. + /// Remote (device-side) port spec. + /// Cancellation token. + public virtual async Task ForwardPortAsync (string serial, AdbPortSpec local, AdbPortSpec remote, CancellationToken cancellationToken = default) + { + if (string.IsNullOrWhiteSpace (serial)) + throw new ArgumentException ("Serial must not be empty.", nameof (serial)); + if (local is null) + throw new ArgumentNullException (nameof (local)); + if (remote is null) + throw new ArgumentNullException (nameof (remote)); + if (local.Port <= 0 || local.Port > 65535) + throw new ArgumentOutOfRangeException (nameof (local), local.Port, "Port must be between 1 and 65535."); + if (remote.Port <= 0 || remote.Port > 65535) + throw new ArgumentOutOfRangeException (nameof (remote), remote.Port, "Port must be between 1 and 65535."); + + var psi = ProcessUtils.CreateProcessStartInfo (adbPath, "-s", serial, "forward", local.ToSocketSpec (), remote.ToSocketSpec ()); + using var stdout = new StringWriter (); + using var stderr = new StringWriter (); + var exitCode = await ProcessUtils.StartProcess (psi, stdout, stderr, cancellationToken, environmentVariables).ConfigureAwait (false); + ProcessUtils.ThrowIfFailed (exitCode, $"adb -s {serial} forward {local} {remote}", stderr, stdout); + } + + /// + /// Removes a specific forward port forwarding rule via + /// 'adb -s <serial> forward --remove <local>'. + /// + /// Device serial number. + /// Local (host-side) port spec to remove. + /// Cancellation token. + public virtual async Task RemoveForwardPortAsync (string serial, AdbPortSpec local, CancellationToken cancellationToken = default) + { + if (string.IsNullOrWhiteSpace (serial)) + throw new ArgumentException ("Serial must not be empty.", nameof (serial)); + if (local is null) + throw new ArgumentNullException (nameof (local)); + if (local.Port <= 0 || local.Port > 65535) + throw new ArgumentOutOfRangeException (nameof (local), local.Port, "Port must be between 1 and 65535."); + + var psi = ProcessUtils.CreateProcessStartInfo (adbPath, "-s", serial, "forward", "--remove", local.ToSocketSpec ()); + using var stdout = new StringWriter (); + using var stderr = new StringWriter (); + var exitCode = await ProcessUtils.StartProcess (psi, stdout, stderr, cancellationToken, environmentVariables).ConfigureAwait (false); + ProcessUtils.ThrowIfFailed (exitCode, $"adb -s {serial} forward --remove {local}", stderr, stdout); + } + + /// + /// Removes all forward port forwarding rules for the specified device. + /// + /// + /// The underlying adb forward --remove-all command (and its wire-protocol + /// equivalent host-serial:<serial>:killforward-all) operates globally on the + /// adb daemon — the -s <serial> flag does not scope it, and calling it + /// would remove forwards for every connected device. To honour the per-device + /// contract of this method, we list the forwards for + /// via and remove them individually via + /// . + /// + public virtual async Task RemoveAllForwardPortsAsync (string serial, CancellationToken cancellationToken = default) + { + if (string.IsNullOrWhiteSpace (serial)) + throw new ArgumentException ("Serial must not be empty.", nameof (serial)); + + var rules = await ListForwardPortsAsync (serial, cancellationToken).ConfigureAwait (false); + foreach (var rule in rules) { + cancellationToken.ThrowIfCancellationRequested (); + await RemoveForwardPortAsync (serial, rule.Local, cancellationToken).ConfigureAwait (false); + } + } + + /// + /// Lists active forward port forwarding rules for the specified device via + /// 'adb forward --list'. + /// The underlying command always lists rules across all devices, so the + /// result is filtered to entries matching . + /// + public virtual async Task> ListForwardPortsAsync (string serial, CancellationToken cancellationToken = default) + { + if (string.IsNullOrWhiteSpace (serial)) + throw new ArgumentException ("Serial must not be empty.", nameof (serial)); + + using var stdout = new StringWriter (); + using var stderr = new StringWriter (); + var psi = ProcessUtils.CreateProcessStartInfo (adbPath, "forward", "--list"); + var exitCode = await ProcessUtils.StartProcess (psi, stdout, stderr, cancellationToken, environmentVariables).ConfigureAwait (false); + ProcessUtils.ThrowIfFailed (exitCode, $"adb forward --list", stderr, stdout); + + return ParseForwardListOutput (stdout.ToString ().Split ('\n'), serial); + } + + /// + /// Parses the output of 'adb forward --list'. + /// Each line is "<serial> <local> <remote>", e.g. "emulator-5554 tcp:5000 tcp:6000". + /// Only rules matching are returned. Lines with + /// unparseable socket specs are skipped. + /// + /// + /// Note the field-order asymmetry vs : + /// forward --list: <serial> <local> <remote> + /// reverse --list: (reverse) <remote> <local> + /// Both parsers construct an whose constructor takes + /// (Remote, Local), so the order in which we pass the parsed parts differs between + /// the two parsers — keep that in mind when modifying either of them. + /// + internal static IReadOnlyList ParseForwardListOutput (IEnumerable lines, string serial) + { + var rules = new List (); + if (string.IsNullOrEmpty (serial)) + return rules; + + foreach (var line in lines) { + var trimmed = line.Trim (); + if (string.IsNullOrEmpty (trimmed)) + continue; + + // Expected format: " " — see above for + // the field-order asymmetry with reverse --list. + var parts = trimmed.Split ((char[]?) null, StringSplitOptions.RemoveEmptyEntries); + if (parts.Length < 3) + continue; + + if (!string.Equals (parts [0], serial, StringComparison.Ordinal)) + continue; + + var local = AdbPortSpec.TryParse (parts [1]); + var remote = AdbPortSpec.TryParse (parts [2]); + if (local is { } l && remote is { } r) + rules.Add (new AdbPortRule (r, l)); + } + + return rules; + } + /// /// Parses the output lines from 'adb devices -l'. /// Accepts an to avoid allocating a joined string. diff --git a/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/AdbRunnerTests.cs b/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/AdbRunnerTests.cs index 00b5ccc643a..acd29f1d865 100644 --- a/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/AdbRunnerTests.cs +++ b/external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/AdbRunnerTests.cs @@ -28,7 +28,6 @@ namespace Xamarin.Android.Tools.Tests; [TestFixture] public class AdbRunnerTests { - // --- ParseAdbDevicesOutput tests --- // Consumer: dotnet/android GetAvailableAndroidDevices.cs, MAUI DevTools (via ListDevicesAsync) [Test] @@ -263,7 +262,6 @@ public void ParseAdbDevicesOutput_AdbDaemonStarting () Assert.AreEqual (2, devices.Count, "Should parse devices even with daemon startup messages"); } - // --- BuildDeviceDescription tests --- // Consumer: dotnet/android GetAvailableAndroidDevices.cs [Test] @@ -301,7 +299,6 @@ public void BuildDeviceDescription_EmulatorWithUppercaseAvdName () Assert.AreEqual ("Pixel 9 Pro XL", description, "Offline emulator should still get AVD name with uppercase preserved"); } - // --- FormatDisplayName tests --- // Consumer: dotnet/android GetAvailableAndroidDevicesTests, BuildDeviceDescription (via AVD name formatting) [Test] @@ -373,7 +370,6 @@ public void FormatDisplayName_DoesNotReplaceApiInsideWords () Assert.AreEqual ("Erapidevice", AdbRunner.FormatDisplayName ("erapidevice")); } - // --- MapAdbStateToStatus tests --- // Consumer: ParseAdbDevicesOutput (internal mapping), public for custom consumers [Test] @@ -386,7 +382,6 @@ public void MapAdbStateToStatus_AllStates () Assert.AreEqual (AdbDeviceStatus.Unknown, AdbRunner.MapAdbStateToStatus ("something-else")); } - // --- MergeDevicesAndEmulators tests --- // Consumer: dotnet/android GetAvailableAndroidDevices.cs [Test] @@ -510,7 +505,6 @@ public void MergeDevicesAndEmulators_EmptyAdbDevices_ReturnsAllAvailable () Assert.AreEqual ("Pixel 9 API 36 (Not Running)", result [1].Description); } - // --- AdbPath tests --- // Consumer: MAUI DevTools Adb provider (AdbPath, IsAvailable properties) [Test] @@ -638,7 +632,6 @@ public void MapAdbStateToStatus_Sideload_ReturnsUnknown () Assert.AreEqual (AdbDeviceStatus.Unknown, AdbRunner.MapAdbStateToStatus ("sideload")); } - // --- WaitForDeviceAsync tests --- // Consumer: MAUI DevTools Adb provider (WaitForDeviceAsync) [Test] @@ -657,8 +650,6 @@ public void WaitForDeviceAsync_ZeroTimeout_ThrowsArgumentOutOfRange () async () => await runner.WaitForDeviceAsync (timeout: System.TimeSpan.Zero)); } - // --- FirstNonEmptyLine tests --- - [Test] public void FirstNonEmptyLine_ReturnsFirstLine () { @@ -716,7 +707,6 @@ public void FirstNonEmptyLine_PmPathOutput () Assert.AreEqual ("package:/system/framework/framework-res.apk", AdbRunner.FirstNonEmptyLine (output)); } - // --- ParseReverseListOutput tests --- // Consumer: MAUI DevTools (via ListReversePortsAsync), vscode-maui ServiceHub replacement [Test] @@ -863,7 +853,166 @@ public void ParseReverseListOutput_TabSeparated () Assert.AreEqual (8081, rules [1].Remote.Port); } - // --- AdbPortSpec tests --- + // Consumer: MAUI DevTools (via ListForwardPortsAsync — host→device tunnel for JDWP debugger + // attach, perf endpoints, host-side DevFlow agent connect), vscode-maui ServiceHub replacement. + // Output format differs from reverse: "(reverse) " → " ". + + [Test] + public void ParseForwardListOutput_SingleRule () + { + var output = new [] { + "emulator-5554 tcp:5000 tcp:6000", + }; + + var rules = AdbRunner.ParseForwardListOutput (output, "emulator-5554"); + + Assert.AreEqual (1, rules.Count); + Assert.AreEqual (AdbProtocol.Tcp, rules [0].Local.Protocol); + Assert.AreEqual (5000, rules [0].Local.Port); + Assert.AreEqual (AdbProtocol.Tcp, rules [0].Remote.Protocol); + Assert.AreEqual (6000, rules [0].Remote.Port); + } + + [Test] + public void ParseForwardListOutput_MultipleRulesSameDevice () + { + var output = new [] { + "emulator-5554 tcp:5000 tcp:6000", + "emulator-5554 tcp:8081 tcp:8081", + "emulator-5554 tcp:9222 tcp:9223", + }; + + var rules = AdbRunner.ParseForwardListOutput (output, "emulator-5554"); + + Assert.AreEqual (3, rules.Count); + Assert.AreEqual (5000, rules [0].Local.Port); + Assert.AreEqual (6000, rules [0].Remote.Port); + Assert.AreEqual (8081, rules [1].Local.Port); + Assert.AreEqual (8081, rules [1].Remote.Port); + Assert.AreEqual (9222, rules [2].Local.Port); + Assert.AreEqual (9223, rules [2].Remote.Port); + } + + [Test] + public void ParseForwardListOutput_FiltersByDeviceSerial () + { + // adb forward --list returns rules across ALL devices; we must filter. + var output = new [] { + "emulator-5554 tcp:5000 tcp:5000", + "emulator-5556 tcp:5001 tcp:5001", + "emulator-5554 tcp:8081 tcp:8081", + "abcd1234device tcp:9000 tcp:9000", + }; + + var rules = AdbRunner.ParseForwardListOutput (output, "emulator-5554"); + + Assert.AreEqual (2, rules.Count); + Assert.AreEqual (5000, rules [0].Local.Port); + Assert.AreEqual (8081, rules [1].Local.Port); + } + + [Test] + public void ParseForwardListOutput_EmptyOutput () + { + var output = new [] { "", " " }; + var rules = AdbRunner.ParseForwardListOutput (output, "emulator-5554"); + Assert.AreEqual (0, rules.Count); + } + + [Test] + public void ParseForwardListOutput_NoLines () + { + var rules = AdbRunner.ParseForwardListOutput (Array.Empty (), "emulator-5554"); + Assert.AreEqual (0, rules.Count); + } + + [Test] + public void ParseForwardListOutput_EmptySerial_ReturnsEmpty () + { + var output = new [] { "emulator-5554 tcp:5000 tcp:5000" }; + var rules = AdbRunner.ParseForwardListOutput (output, ""); + Assert.AreEqual (0, rules.Count); + } + + [Test] + public void ParseForwardListOutput_NoMatchingDevice () + { + var output = new [] { + "emulator-5554 tcp:5000 tcp:5000", + "emulator-5556 tcp:5001 tcp:5001", + }; + var rules = AdbRunner.ParseForwardListOutput (output, "missing-device"); + Assert.AreEqual (0, rules.Count); + } + + [Test] + public void ParseForwardListOutput_MalformedLine_InsufficientParts () + { + var output = new [] { + "emulator-5554 tcp:5000", // missing remote spec + }; + + var rules = AdbRunner.ParseForwardListOutput (output, "emulator-5554"); + Assert.AreEqual (0, rules.Count); + } + + [Test] + public void ParseForwardListOutput_NonTcpSpecs_SkipsUnparseable () + { + var output = new [] { + "emulator-5554 tcp:9222 localabstract:chrome_devtools_remote", + "emulator-5554 tcp:5000 tcp:5000", + }; + + var rules = AdbRunner.ParseForwardListOutput (output, "emulator-5554"); + + // localabstract:chrome_devtools_remote has a non-numeric port, so it is skipped + Assert.AreEqual (1, rules.Count); + Assert.AreEqual (5000, rules [0].Local.Port); + Assert.AreEqual (5000, rules [0].Remote.Port); + } + + [Test] + public void ParseForwardListOutput_WindowsLineEndings () + { + var output = new [] { + "emulator-5554 tcp:5000 tcp:6000\r", + "emulator-5554 tcp:8081 tcp:8081\r", + }; + + var rules = AdbRunner.ParseForwardListOutput (output, "emulator-5554"); + + Assert.AreEqual (2, rules.Count); + Assert.AreEqual (5000, rules [0].Local.Port); + Assert.AreEqual (6000, rules [0].Remote.Port); + Assert.AreEqual (8081, rules [1].Local.Port); + } + + [Test] + public void ParseForwardListOutput_TabSeparated () + { + var output = new [] { + "emulator-5554\ttcp:5000\ttcp:6000", + }; + + var rules = AdbRunner.ParseForwardListOutput (output, "emulator-5554"); + + Assert.AreEqual (1, rules.Count); + Assert.AreEqual (5000, rules [0].Local.Port); + Assert.AreEqual (6000, rules [0].Remote.Port); + } + + [Test] + public void ParseForwardListOutput_SerialMatch_IsCaseSensitive () + { + // adb device serials are case-sensitive. + var output = new [] { + "emulator-5554 tcp:5000 tcp:5000", + }; + + var rules = AdbRunner.ParseForwardListOutput (output, "EMULATOR-5554"); + Assert.AreEqual (0, rules.Count); + } [Test] public void AdbPortSpec_TryParse_ValidTcp () @@ -968,8 +1117,6 @@ public void AdbPortSpec_TryParse_Roundtrip () Assert.AreEqual (original, parsed); } - // --- AdbPortRule tests --- - [Test] public void AdbPortRule_ValueEquality () { @@ -1005,8 +1152,6 @@ public void AdbPortRule_ToString () Assert.That (str, Does.Contain ("tcp:3000")); } - // --- ReversePortAsync parameter validation tests --- - [Test] public void ReversePortAsync_EmptySerial_ThrowsArgumentException () { @@ -1031,8 +1176,6 @@ public void ReversePortAsync_NullLocal_ThrowsArgumentNull () async () => await runner.ReversePortAsync ("emulator-5554", new AdbPortSpec (AdbProtocol.Tcp, 5000), (AdbPortSpec) null)); } - // --- RemoveReversePortAsync parameter validation tests --- - [Test] public void RemoveReversePortAsync_EmptySerial_ThrowsArgumentException () { @@ -1049,8 +1192,6 @@ public void RemoveReversePortAsync_NullRemote_ThrowsArgumentNull () async () => await runner.RemoveReversePortAsync ("emulator-5554", (AdbPortSpec) null)); } - // --- RemoveAllReversePortsAsync parameter validation tests --- - [Test] public void RemoveAllReversePortsAsync_EmptySerial_ThrowsArgumentException () { @@ -1059,8 +1200,6 @@ public void RemoveAllReversePortsAsync_EmptySerial_ThrowsArgumentException () async () => await runner.RemoveAllReversePortsAsync ("")); } - // --- ListReversePortsAsync parameter validation tests --- - [Test] public void ListReversePortsAsync_EmptySerial_ThrowsArgumentException () { @@ -1069,7 +1208,126 @@ public void ListReversePortsAsync_EmptySerial_ThrowsArgumentException () async () => await runner.ListReversePortsAsync ("")); } - // --- GetEmulatorAvdNameAsync + ListDevicesAsync tests --- + [Test] + public void ForwardPortAsync_EmptySerial_ThrowsArgumentException () + { + var runner = new AdbRunner ("/fake/sdk/platform-tools/adb"); + Assert.ThrowsAsync ( + async () => await runner.ForwardPortAsync ("", new AdbPortSpec (AdbProtocol.Tcp, 5000), new AdbPortSpec (AdbProtocol.Tcp, 5000))); + } + + [Test] + public void ForwardPortAsync_NullLocal_ThrowsArgumentNull () + { + var runner = new AdbRunner ("/fake/sdk/platform-tools/adb"); + Assert.ThrowsAsync ( + async () => await runner.ForwardPortAsync ("emulator-5554", (AdbPortSpec) null, new AdbPortSpec (AdbProtocol.Tcp, 5000))); + } + + [Test] + public void ForwardPortAsync_NullRemote_ThrowsArgumentNull () + { + var runner = new AdbRunner ("/fake/sdk/platform-tools/adb"); + Assert.ThrowsAsync ( + async () => await runner.ForwardPortAsync ("emulator-5554", new AdbPortSpec (AdbProtocol.Tcp, 5000), (AdbPortSpec) null)); + } + + [Test] + public void RemoveForwardPortAsync_EmptySerial_ThrowsArgumentException () + { + var runner = new AdbRunner ("/fake/sdk/platform-tools/adb"); + Assert.ThrowsAsync ( + async () => await runner.RemoveForwardPortAsync ("", new AdbPortSpec (AdbProtocol.Tcp, 5000))); + } + + [Test] + public void RemoveForwardPortAsync_NullLocal_ThrowsArgumentNull () + { + var runner = new AdbRunner ("/fake/sdk/platform-tools/adb"); + Assert.ThrowsAsync ( + async () => await runner.RemoveForwardPortAsync ("emulator-5554", (AdbPortSpec) null)); + } + + [Test] + public void RemoveAllForwardPortsAsync_EmptySerial_ThrowsArgumentException () + { + var runner = new AdbRunner ("/fake/sdk/platform-tools/adb"); + Assert.ThrowsAsync ( + async () => await runner.RemoveAllForwardPortsAsync ("")); + } + + [Test] + public async Task RemoveAllForwardPortsAsync_RemovesOnlyPortsForGivenSerial () + { + var listedSerials = new List (); + var removed = new List<(string Serial, AdbPortSpec Local)> (); + var runner = new RecordingAdbRunner ( + listForwards: (serial, _) => { + listedSerials.Add (serial); + return Task.FromResult> (new [] { + new AdbPortRule (new AdbPortSpec (AdbProtocol.Tcp, 6000), new AdbPortSpec (AdbProtocol.Tcp, 5000)), + new AdbPortRule (new AdbPortSpec (AdbProtocol.Tcp, 6001), new AdbPortSpec (AdbProtocol.Tcp, 5001)), + }); + }, + removeForward: (serial, local, _) => { + removed.Add ((serial, local)); + return Task.CompletedTask; + }); + + await runner.RemoveAllForwardPortsAsync ("emulator-5554"); + + Assert.AreEqual (new [] { "emulator-5554" }, listedSerials, "ListForwardPortsAsync should be called exactly once with the target serial."); + Assert.AreEqual (2, removed.Count, "Both listed rules should be removed."); + Assert.That (removed.Select (r => r.Serial), Is.All.EqualTo ("emulator-5554"), "Removes must target only the requested serial."); + Assert.AreEqual (5000, removed [0].Local.Port); + Assert.AreEqual (5001, removed [1].Local.Port); + } + + [Test] + public async Task RemoveAllForwardPortsAsync_EmptyList_IsNoOp () + { + var removed = new List<(string Serial, AdbPortSpec Local)> (); + var runner = new RecordingAdbRunner ( + listForwards: (_, __) => Task.FromResult> (Array.Empty ()), + removeForward: (serial, local, _) => { + removed.Add ((serial, local)); + return Task.CompletedTask; + }); + + await runner.RemoveAllForwardPortsAsync ("emulator-5554"); + + Assert.IsEmpty (removed, "No removes should be issued when the listing is empty."); + } + + sealed class RecordingAdbRunner : AdbRunner + { + readonly Func>> listForwards; + readonly Func removeForward; + + public RecordingAdbRunner ( + Func>> listForwards, + Func removeForward) + : base ("/fake/sdk/platform-tools/adb") + { + this.listForwards = listForwards; + this.removeForward = removeForward; + } + + public override Task> ListForwardPortsAsync (string serial, System.Threading.CancellationToken cancellationToken = default) + => listForwards (serial, cancellationToken); + + public override Task RemoveForwardPortAsync (string serial, AdbPortSpec local, System.Threading.CancellationToken cancellationToken = default) + => removeForward (serial, local, cancellationToken); + } + + [Test] + public void ListForwardPortsAsync_EmptySerial_ThrowsArgumentException () + { + var runner = new AdbRunner ("/fake/sdk/platform-tools/adb"); + Assert.ThrowsAsync ( + async () => await runner.ListForwardPortsAsync ("")); + } + // These tests use a fake 'adb' script to control process output, // verifying AVD detection order and offline emulator handling. From 034a7f1b0b3632da08c0cf10bd72b3ef42db9c9c Mon Sep 17 00:00:00 2001 From: Jonathan Dick Date: Thu, 11 Jun 2026 17:02:00 -0400 Subject: [PATCH 298/308] [msbuild] add System.Security.Cryptography.Xml transitive dependency (#386) The following package has a security fix: Note that we are updating this for general cleanliness/hardening, not addressing a security concern. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../MSBuildReferences.projitems | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems index 720f8c705be..e5ccd74facc 100644 --- a/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems +++ b/external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/MSBuildReferences.projitems @@ -4,8 +4,8 @@ - 18.6.3 - 10.0.3 + 18.7.1 + 10.0.4 3.3.0 7.1.0-final.1.21458.1 @@ -15,6 +15,7 @@ + From dde0c5b2f001437fcf89d3105acf17afe555dae0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 11 Jun 2026 14:27:36 -0700 Subject: [PATCH 299/308] Bump System.IO.Hashing from 10.0.8 to 10.0.9 (#387) --- updated-dependencies: - dependency-name: System.IO.Hashing dependency-version: 10.0.9 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- external/xamarin-android-tools/Directory.Build.targets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/xamarin-android-tools/Directory.Build.targets b/external/xamarin-android-tools/Directory.Build.targets index 5c0b145bc48..a7e63167bba 100644 --- a/external/xamarin-android-tools/Directory.Build.targets +++ b/external/xamarin-android-tools/Directory.Build.targets @@ -28,7 +28,7 @@ - + From cc95da77f3f07bfe754a80f0516d1758361c8edb Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Fri, 12 Jun 2026 14:05:18 -0500 Subject: [PATCH 300/308] Upgrade gh-aw to v0.79.6 and recompile workflows (#388) Bumps the gh-aw compiler from v0.68.3 to v0.79.6 and regenerates the lock files via `gh aw compile`. Source `.md` files are unchanged. Notable supply-chain improvements in the regenerated output: - Container images are now pinned by digest (firewall agent, api-proxy, squid, mcpg, github-mcp-server) in addition to tag. - Action SHAs refreshed (e.g., actions/checkout v6.0.2 -> v6.0.3, actions/github-script v9 -> v9.0.0, github/gh-aw-actions/setup v0.68.3 -> v0.79.6). - Lock-file schema bumped v3 -> v4 (adds body_hash to metadata). Also adds the auto-generated `agentics-maintenance.yml` that v0.79.6 emits as a sibling maintenance workflow. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../.github/aw/actions-lock.json | 16 +- .../workflows/agentics-maintenance.yml | 607 ++++++++++++++++++ .../workflows/android-tools-reviewer.lock.yml | 606 +++++++++++++---- 3 files changed, 1093 insertions(+), 136 deletions(-) create mode 100644 external/xamarin-android-tools/.github/workflows/agentics-maintenance.yml diff --git a/external/xamarin-android-tools/.github/aw/actions-lock.json b/external/xamarin-android-tools/.github/aw/actions-lock.json index 97077a3bc29..34cc08a154f 100644 --- a/external/xamarin-android-tools/.github/aw/actions-lock.json +++ b/external/xamarin-android-tools/.github/aw/actions-lock.json @@ -1,9 +1,19 @@ { "entries": { - "github/gh-aw-actions/setup@v0.68.3": { + "actions/github-script@v9.0.0": { + "repo": "actions/github-script", + "version": "v9.0.0", + "sha": "3a2844b7e9c422d3c10d287c895573f7108da1b3" + }, + "github/gh-aw-actions/setup-cli@v0.79.6": { + "repo": "github/gh-aw-actions/setup-cli", + "version": "v0.79.6", + "sha": "5c2fe865bb4dc46e1450f6ee0d0541d759aea73a" + }, + "github/gh-aw-actions/setup@v0.79.6": { "repo": "github/gh-aw-actions/setup", - "version": "v0.68.3", - "sha": "ba90f2186d7ad780ec640f364005fa24e797b360" + "version": "v0.79.6", + "sha": "5c2fe865bb4dc46e1450f6ee0d0541d759aea73a" } } } diff --git a/external/xamarin-android-tools/.github/workflows/agentics-maintenance.yml b/external/xamarin-android-tools/.github/workflows/agentics-maintenance.yml new file mode 100644 index 00000000000..6bd015f59ae --- /dev/null +++ b/external/xamarin-android-tools/.github/workflows/agentics-maintenance.yml @@ -0,0 +1,607 @@ +# ___ _ _ +# / _ \ | | (_) +# | |_| | __ _ ___ _ __ | |_ _ ___ +# | _ |/ _` |/ _ \ '_ \| __| |/ __| +# | | | | (_| | __/ | | | |_| | (__ +# \_| |_/\__, |\___|_| |_|\__|_|\___| +# __/ | +# _ _ |___/ +# | | | | / _| | +# | | | | ___ _ __ _ __| |_| | _____ ____ +# | |/\| |/ _ \ '__| |/ /| _| |/ _ \ \ /\ / / ___| +# \ /\ / (_) | | | | ( | | | | (_) \ V V /\__ \ +# \/ \/ \___/|_| |_|\_\|_| |_|\___/ \_/\_/ |___/ +# +# This file was automatically generated by pkg/workflow/maintenance_workflow.go (v0.79.6). DO NOT EDIT. +# +# To regenerate this workflow, run: +# gh aw compile +# Not all edits will cause changes to this file. +# +# For more information: https://github.github.com/gh-aw/introduction/overview/ +# +# Alternative regeneration methods: +# make recompile +# +# Or use the gh-aw CLI directly: +# ./gh-aw compile --validate --verbose +# +# The workflow is generated when any workflow uses the 'expires' field +# in create-discussions, create-issues, or create-pull-request safe-outputs configuration. +# Schedule frequency is automatically determined by the shortest expiration time. +# +name: Agentic Maintenance + +on: + schedule: + - cron: "37 0 * * *" # Daily (based on minimum expires: 30 days) + workflow_dispatch: + inputs: + operation: + description: 'Optional maintenance operation to run' + required: false + type: choice + default: '' + options: + - '' + - 'disable' + - 'enable' + - 'update' + - 'upgrade' + - 'safe_outputs' + - 'create_labels' + - 'activity_report' + - 'close_agentic_workflows_issues' + - 'clean_cache_memories' + - 'update_pull_request_branches' + - 'validate' + - 'forecast' + run_url: + description: 'Run URL or run ID to replay safe outputs from (e.g. https://github.com/owner/repo/actions/runs/12345 or 12345). Required when operation is safe_outputs.' + required: false + type: string + default: '' + workflow_call: + inputs: + operation: + description: 'Optional maintenance operation to run (disable, enable, update, upgrade, safe_outputs, create_labels, activity_report, close_agentic_workflows_issues, clean_cache_memories, update_pull_request_branches, validate, forecast)' + required: false + type: string + default: '' + run_url: + description: 'Run URL or run ID to replay safe outputs from (e.g. https://github.com/owner/repo/actions/runs/12345 or 12345). Required when operation is safe_outputs.' + required: false + type: string + default: '' + outputs: + operation_completed: + description: 'The maintenance operation that was completed (empty when none ran or a scheduled job ran)' + value: ${{ jobs.run_operation.outputs.operation || inputs.operation }} + applied_run_url: + description: 'The run URL that safe outputs were applied from' + value: ${{ jobs.apply_safe_outputs.outputs.run_url }} + +permissions: {} + +jobs: + close-expired-entities: + if: ${{ (!(github.event.repository.fork)) && github.event_name != 'push' && (github.event_name != 'workflow_dispatch' && github.event_name != 'workflow_call' || inputs.operation == '') }} + runs-on: ubuntu-slim + permissions: + discussions: write + issues: write + pull-requests: write + steps: + - name: Setup Scripts + uses: github/gh-aw-actions/setup@5c2fe865bb4dc46e1450f6ee0d0541d759aea73a # v0.79.6 + with: + destination: ${{ runner.temp }}/gh-aw/actions + + - name: Close expired discussions + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/close_expired_discussions.cjs'); + await main(); + + - name: Close expired issues + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/close_expired_issues.cjs'); + await main(); + + - name: Close expired pull requests + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/close_expired_pull_requests.cjs'); + await main(); + + cleanup-cache-memory: + if: ${{ (!(github.event.repository.fork)) && github.event_name != 'push' && (github.event_name != 'workflow_dispatch' && github.event_name != 'workflow_call' || inputs.operation == '' || inputs.operation == 'clean_cache_memories') }} + runs-on: ubuntu-slim + permissions: + actions: write + steps: + - name: Setup Scripts + uses: github/gh-aw-actions/setup@5c2fe865bb4dc46e1450f6ee0d0541d759aea73a # v0.79.6 + with: + destination: ${{ runner.temp }}/gh-aw/actions + + - name: Cleanup outdated cache-memory entries + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/cleanup_cache_memory.cjs'); + await main(); + + run_operation: + if: ${{ (github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && inputs.operation != '' && inputs.operation != 'safe_outputs' && inputs.operation != 'create_labels' && inputs.operation != 'activity_report' && inputs.operation != 'close_agentic_workflows_issues' && inputs.operation != 'clean_cache_memories' && inputs.operation != 'update_pull_request_branches' && inputs.operation != 'validate' && inputs.operation != 'forecast' && (!(github.event.repository.fork)) }} + runs-on: ubuntu-slim + permissions: + actions: write + contents: write + pull-requests: write + outputs: + operation: ${{ steps.record.outputs.operation }} + steps: + - name: Checkout repository + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + with: + persist-credentials: false + + - name: Setup Scripts + uses: github/gh-aw-actions/setup@5c2fe865bb4dc46e1450f6ee0d0541d759aea73a # v0.79.6 + with: + destination: ${{ runner.temp }}/gh-aw/actions + + - name: Check admin/maintainer permissions + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/check_team_member.cjs'); + await main(); + + - name: Install gh-aw + uses: github/gh-aw-actions/setup-cli@5c2fe865bb4dc46e1450f6ee0d0541d759aea73a # v0.79.6 + with: + version: v0.79.6 + + - name: Run operation + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_AW_OPERATION: ${{ inputs.operation }} + GH_AW_CMD_PREFIX: gh aw + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/run_operation_update_upgrade.cjs'); + await main(); + + - name: Record outputs + id: record + run: echo "operation=${{ inputs.operation }}" >> "$GITHUB_OUTPUT" + + update_pull_request_branches: + if: ${{ (github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && inputs.operation == 'update_pull_request_branches' && (!(github.event.repository.fork)) }} + runs-on: ubuntu-slim + permissions: + contents: write + pull-requests: write + steps: + - name: Setup Scripts + uses: github/gh-aw-actions/setup@5c2fe865bb4dc46e1450f6ee0d0541d759aea73a # v0.79.6 + with: + destination: ${{ runner.temp }}/gh-aw/actions + + - name: Check admin/maintainer permissions + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/check_team_member.cjs'); + await main(); + + - name: Update pull request branches + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/update_pull_request_branches.cjs'); + await main(); + + apply_safe_outputs: + if: ${{ (github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && inputs.operation == 'safe_outputs' && (!(github.event.repository.fork)) }} + runs-on: ubuntu-slim + permissions: + actions: read + contents: write + discussions: write + issues: write + pull-requests: write + outputs: + run_url: ${{ steps.record.outputs.run_url }} + steps: + - name: Checkout actions folder + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + with: + sparse-checkout: | + actions + persist-credentials: false + + - name: Setup Scripts + uses: github/gh-aw-actions/setup@5c2fe865bb4dc46e1450f6ee0d0541d759aea73a # v0.79.6 + with: + destination: ${{ runner.temp }}/gh-aw/actions + + - name: Check admin/maintainer permissions + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/check_team_member.cjs'); + await main(); + + - name: Apply Safe Outputs + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_AW_RUN_URL: ${{ inputs.run_url }} + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/apply_safe_outputs_replay.cjs'); + await main(); + + - name: Record outputs + id: record + run: echo "run_url=${{ inputs.run_url }}" >> "$GITHUB_OUTPUT" + + create_labels: + if: ${{ (github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && inputs.operation == 'create_labels' && (!(github.event.repository.fork)) }} + runs-on: ubuntu-slim + permissions: + contents: read + issues: write + steps: + - name: Checkout repository + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + with: + persist-credentials: false + + - name: Setup Scripts + uses: github/gh-aw-actions/setup@5c2fe865bb4dc46e1450f6ee0d0541d759aea73a # v0.79.6 + with: + destination: ${{ runner.temp }}/gh-aw/actions + + - name: Check admin/maintainer permissions + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/check_team_member.cjs'); + await main(); + + - name: Install gh-aw + uses: github/gh-aw-actions/setup-cli@5c2fe865bb4dc46e1450f6ee0d0541d759aea73a # v0.79.6 + with: + version: v0.79.6 + + - name: Create missing labels + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_CMD_PREFIX: gh aw + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/create_labels.cjs'); + await main(); + + activity_report: + if: ${{ (github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && inputs.operation == 'activity_report' && (!(github.event.repository.fork)) }} + runs-on: ubuntu-slim + timeout-minutes: 120 + permissions: + actions: read + contents: read + issues: write + steps: + - name: Checkout repository + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + with: + persist-credentials: false + + - name: Setup Scripts + uses: github/gh-aw-actions/setup@5c2fe865bb4dc46e1450f6ee0d0541d759aea73a # v0.79.6 + with: + destination: ${{ runner.temp }}/gh-aw/actions + + - name: Check admin/maintainer permissions + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/check_team_member.cjs'); + await main(); + + - name: Install gh-aw + uses: github/gh-aw-actions/setup-cli@5c2fe865bb4dc46e1450f6ee0d0541d759aea73a # v0.79.6 + with: + version: v0.79.6 + + - name: Restore activity report logs cache + id: activity_report_logs_cache + uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + path: ./.cache/gh-aw/activity-report-logs + key: ${{ runner.os }}-activity-report-logs-${{ github.repository }}-${{ github.ref_name }}-${{ github.run_id }} + restore-keys: | + ${{ runner.os }}-activity-report-logs-${{ github.repository }}- + ${{ runner.os }}-activity-report-logs- + - name: Download activity report logs + timeout-minutes: 20 + shell: bash + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_AW_CMD_PREFIX: gh aw + run: | + ${GH_AW_CMD_PREFIX} logs \ + --repo "${{ github.repository }}" \ + --start-date -1w \ + --count 100 \ + --output ./.cache/gh-aw/activity-report-logs \ + --format markdown \ + > ./.cache/gh-aw/activity-report-logs/report.md + + - name: Save activity report logs cache + if: ${{ always() }} + uses: actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + path: ./.cache/gh-aw/activity-report-logs + key: ${{ steps.activity_report_logs_cache.outputs.cache-primary-key }} + + - name: Generate activity report issue + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const fs = require('node:fs'); + const reportPath = './.cache/gh-aw/activity-report-logs/report.md'; + if (!fs.existsSync(reportPath)) { + core.warning('Activity report markdown not found at ' + reportPath + '; skipping issue creation.'); + return; + } + let reportBody = ''; + try { + reportBody = fs.readFileSync(reportPath, 'utf8').trim(); + } catch (error) { + core.warning('Failed to read activity report markdown at ' + reportPath + ': ' + error.message); + return; + } + if (!reportBody) { + core.warning('Activity report markdown is empty at ' + reportPath + '; skipping issue creation.'); + return; + } + const repoSlug = context.repo.owner + '/' + context.repo.repo; + const body = [ + '### Agentic workflow activity report', + '', + 'Repository: ' + repoSlug, + 'Generated at: ' + new Date().toISOString(), + '', + reportBody, + ].join('\n'); + const createdIssue = await github.rest.issues.create({ + owner: context.repo.owner, + repo: context.repo.repo, + title: '[aw] agentic status report', + body, + labels: ['agentic-workflows'], + }); + core.info('Created issue #' + createdIssue.data.number + ': ' + createdIssue.data.html_url); + + forecast_report: + if: ${{ (github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && inputs.operation == 'forecast' && (!(github.event.repository.fork)) }} + runs-on: ubuntu-slim + timeout-minutes: 60 + permissions: + actions: read + contents: read + issues: write + steps: + - name: Checkout repository + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + with: + persist-credentials: false + + - name: Setup Scripts + uses: github/gh-aw-actions/setup@5c2fe865bb4dc46e1450f6ee0d0541d759aea73a # v0.79.6 + with: + destination: ${{ runner.temp }}/gh-aw/actions + + - name: Check admin/maintainer permissions + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/check_team_member.cjs'); + await main(); + + - name: Install gh-aw + uses: github/gh-aw-actions/setup-cli@5c2fe865bb4dc46e1450f6ee0d0541d759aea73a # v0.79.6 + with: + version: v0.79.6 + + - name: Restore forecast report logs cache + id: forecast_report_logs_cache + uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + path: ./.github/aw/logs + key: ${{ runner.os }}-forecast-report-logs-${{ github.repository }}-${{ github.ref_name }}-${{ github.run_id }} + restore-keys: | + ${{ runner.os }}-forecast-report-logs-${{ github.repository }}- + ${{ runner.os }}-forecast-report-logs- + + - name: Generate forecast report + id: generate_forecast_report + timeout-minutes: 30 + shell: bash + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + DEBUG: "*" + GH_AW_CMD_PREFIX: gh aw + run: | + mkdir -p ./.cache/gh-aw/forecast + set +e + ${GH_AW_CMD_PREFIX} forecast --repo "${{ github.repository }}" --timeout 30 --verbose --json > ./.cache/gh-aw/forecast/report.json + forecast_exit_code=$? + set -e + if [ "${forecast_exit_code}" -eq 124 ]; then + echo '{"outcome":"timeout","message":"Forecast computation timed out after 30 minutes."}' > ./.cache/gh-aw/forecast/error.json + echo "::error::Forecast computation timed out after 30 minutes." + exit 1 + fi + if [ "${forecast_exit_code}" -ne 0 ]; then + echo '{"outcome":"error","message":"Forecast computation failed before producing a report."}' > ./.cache/gh-aw/forecast/error.json + echo "::error::Forecast computation failed with exit code ${forecast_exit_code}." + exit 1 + fi + + - name: Debug forecast logs folder + if: ${{ always() }} + shell: bash + run: | + if [ ! -d ./.github/aw/logs ]; then + echo "Logs directory not found: ./.github/aw/logs" + exit 0 + fi + echo "Files under ./.github/aw/logs:" + find ./.github/aw/logs -type f | sort + + - name: Save forecast report logs cache + if: ${{ always() }} + uses: actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + path: ./.github/aw/logs + key: ${{ runner.os }}-forecast-report-logs-${{ github.repository }}-${{ github.ref_name }}-${{ github.run_id }} + + - name: Generate forecast issue + if: ${{ always() }} + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + FORECAST_STEP_OUTCOME: ${{ steps.generate_forecast_report.outcome }} + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/create_forecast_issue.cjs'); + await main(); + + close_agentic_workflows_issues: + if: ${{ (github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && inputs.operation == 'close_agentic_workflows_issues' && (!(github.event.repository.fork)) }} + runs-on: ubuntu-slim + permissions: + issues: write + steps: + - name: Setup Scripts + uses: github/gh-aw-actions/setup@5c2fe865bb4dc46e1450f6ee0d0541d759aea73a # v0.79.6 + with: + destination: ${{ runner.temp }}/gh-aw/actions + + - name: Check admin/maintainer permissions + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/check_team_member.cjs'); + await main(); + + - name: Close no-repro agentic-workflows issues + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/close_agentic_workflows_issues.cjs'); + await main(); + + validate_workflows: + if: ${{ (github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && inputs.operation == 'validate' && (!(github.event.repository.fork)) }} + runs-on: ubuntu-latest + permissions: + contents: read + issues: write + steps: + - name: Checkout repository + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + with: + persist-credentials: false + + - name: Setup Scripts + uses: github/gh-aw-actions/setup@5c2fe865bb4dc46e1450f6ee0d0541d759aea73a # v0.79.6 + with: + destination: ${{ runner.temp }}/gh-aw/actions + + - name: Check admin/maintainer permissions + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/check_team_member.cjs'); + await main(); + + - name: Install gh-aw + uses: github/gh-aw-actions/setup-cli@5c2fe865bb4dc46e1450f6ee0d0541d759aea73a # v0.79.6 + with: + version: v0.79.6 + + - name: Validate workflows and file issue on findings + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_CMD_PREFIX: gh aw + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/run_validate_workflows.cjs'); + await main(); diff --git a/external/xamarin-android-tools/.github/workflows/android-tools-reviewer.lock.yml b/external/xamarin-android-tools/.github/workflows/android-tools-reviewer.lock.yml index ed52e7a9764..516e3374e0d 100644 --- a/external/xamarin-android-tools/.github/workflows/android-tools-reviewer.lock.yml +++ b/external/xamarin-android-tools/.github/workflows/android-tools-reviewer.lock.yml @@ -1,5 +1,5 @@ -# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"10f45ab5acbfd8f4152d6d465324cc535ddca73e66b888311f9198f9d09b2193","compiler_version":"v0.68.3","strict":true,"agent_id":"copilot","agent_model":"claude-opus-4.6"} -# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"373c709c69115d41ff229c7e5df9f8788daa9553","version":"v9"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"ba90f2186d7ad780ec640f364005fa24e797b360","version":"v0.68.3"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.20"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.20"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.20"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.2.19"},{"image":"ghcr.io/github/github-mcp-server:v0.32.0"},{"image":"node:lts-alpine"}]} +# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"10f45ab5acbfd8f4152d6d465324cc535ddca73e66b888311f9198f9d09b2193","body_hash":"1b21a58fc25436cc68541bee79e0d82f2b44fb168383670638bc2cfc46f89483","compiler_version":"v0.79.6","strict":true,"agent_id":"copilot","agent_model":"claude-opus-4.6","engine_versions":{"copilot":"1.0.60"}} +# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"df4cb1c069e1874edd31b4311f1884172cec0e10","version":"v6.0.3"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"5c2fe865bb4dc46e1450f6ee0d0541d759aea73a","version":"v0.79.6"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.2","digest":"sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.2@sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.2","digest":"sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.2@sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.2","digest":"sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.2@sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.25","digest":"sha256:c10331ad17668ef89f38f5e356678788a40b0cd5fef96e8f92e1d9c1de47cbaa","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.25@sha256:c10331ad17668ef89f38f5e356678788a40b0cd5fef96e8f92e1d9c1de47cbaa"},{"image":"ghcr.io/github/github-mcp-server:v1.1.2","digest":"sha256:30197479d8036c7811892bc07e06f9a05c9ef3cdd79bc59f256d50647f95788c","pinned_image":"ghcr.io/github/github-mcp-server:v1.1.2@sha256:30197479d8036c7811892bc07e06f9a05c9ef3cdd79bc59f256d50647f95788c"}]} # ___ _ _ # / _ \ | | (_) # | |_| | __ _ ___ _ __ | |_ _ ___ @@ -14,7 +14,7 @@ # \ /\ / (_) | | | | ( | | | | (_) \ V V /\__ \ # \/ \/ \___/|_| |_|\_\|_| |_|\___/ \_/\_/ |___/ # -# This file was automatically generated by gh-aw (v0.68.3). DO NOT EDIT. +# This file was automatically generated by gh-aw (v0.79.6). DO NOT EDIT. # # To update this file, edit the corresponding .md file and run: # gh aw compile @@ -30,22 +30,22 @@ # - GITHUB_TOKEN # # Custom actions used: -# - actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 +# - actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 # - actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 -# - actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 +# - actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 +# - actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 # - actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 -# - github/gh-aw-actions/setup@ba90f2186d7ad780ec640f364005fa24e797b360 # v0.68.3 +# - github/gh-aw-actions/setup@5c2fe865bb4dc46e1450f6ee0d0541d759aea73a # v0.79.6 # # Container images used: -# - ghcr.io/github/gh-aw-firewall/agent:0.25.20 -# - ghcr.io/github/gh-aw-firewall/api-proxy:0.25.20 -# - ghcr.io/github/gh-aw-firewall/squid:0.25.20 -# - ghcr.io/github/gh-aw-mcpg:v0.2.19 -# - ghcr.io/github/github-mcp-server:v0.32.0 -# - node:lts-alpine +# - ghcr.io/github/gh-aw-firewall/agent:0.27.2@sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6 +# - ghcr.io/github/gh-aw-firewall/api-proxy:0.27.2@sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4 +# - ghcr.io/github/gh-aw-firewall/squid:0.27.2@sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591 +# - ghcr.io/github/gh-aw-mcpg:v0.3.25@sha256:c10331ad17668ef89f38f5e356678788a40b0cd5fef96e8f92e1d9c1de47cbaa +# - ghcr.io/github/github-mcp-server:v1.1.2@sha256:30197479d8036c7811892bc07e06f9a05c9ef3cdd79bc59f256d50647f95788c name: "Android Tools PR Reviewer" -"on": +on: issue_comment: types: - created @@ -70,17 +70,24 @@ jobs: permissions: actions: read contents: read - discussions: write issues: write pull-requests: write + env: + GH_AW_MAX_DAILY_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_MAX_DAILY_AI_CREDITS || '5000' }} outputs: body: ${{ steps.sanitized.outputs.body }} comment_id: ${{ steps.add-comment.outputs.comment-id }} comment_repo: ${{ steps.add-comment.outputs.comment-repo }} comment_url: ${{ steps.add-comment.outputs.comment-url }} + daily_effective_workflow_exceeded: ${{ steps.daily-effective-workflow-guardrail.outputs.daily_effective_workflow_exceeded == 'true' }} + daily_effective_workflow_threshold: ${{ steps.daily-effective-workflow-guardrail.outputs.daily_effective_workflow_threshold || '' }} + daily_effective_workflow_total_effective_tokens: ${{ steps.daily-effective-workflow-guardrail.outputs.daily_effective_workflow_total_effective_tokens || '' }} + engine_id: ${{ steps.generate_aw_info.outputs.engine_id }} lockdown_check_failed: ${{ steps.generate_aw_info.outputs.lockdown_check_failed == 'true' }} model: ${{ steps.generate_aw_info.outputs.model }} secret_verification_result: ${{ steps.validate-secret.outputs.verification_result }} + setup-parent-span-id: ${{ steps.setup.outputs.parent-span-id || steps.setup.outputs.span-id }} + setup-span-id: ${{ steps.setup.outputs.span-id }} setup-trace-id: ${{ steps.setup.outputs.trace-id }} slash_command: ${{ needs.pre_activation.outputs.matched_command }} stale_lock_file_failed: ${{ steps.check-lock-file.outputs.stale_lock_file_failed == 'true' }} @@ -89,41 +96,67 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@ba90f2186d7ad780ec640f364005fa24e797b360 # v0.68.3 + uses: github/gh-aw-actions/setup@5c2fe865bb4dc46e1450f6ee0d0541d759aea73a # v0.79.6 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} trace-id: ${{ needs.pre_activation.outputs.setup-trace-id }} + parent-span-id: ${{ needs.pre_activation.outputs.setup-parent-span-id || needs.pre_activation.outputs.setup-span-id }} + safe-output-artifact-client: ${{ env.GH_AW_MAX_DAILY_AI_CREDITS != '' }} + env: + GH_AW_SETUP_WORKFLOW_NAME: "Android Tools PR Reviewer" + GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/android-tools-reviewer.lock.yml@${{ github.ref }} + GH_AW_INFO_VERSION: "1.0.60" + GH_AW_INFO_AWF_VERSION: "v0.27.2" + GH_AW_INFO_ENGINE_ID: "copilot" - name: Generate agentic run info id: generate_aw_info env: GH_AW_INFO_ENGINE_ID: "copilot" GH_AW_INFO_ENGINE_NAME: "GitHub Copilot CLI" GH_AW_INFO_MODEL: "claude-opus-4.6" - GH_AW_INFO_VERSION: "1.0.21" - GH_AW_INFO_AGENT_VERSION: "1.0.21" - GH_AW_INFO_CLI_VERSION: "v0.68.3" + GH_AW_INFO_VERSION: "1.0.60" + GH_AW_INFO_AGENT_VERSION: "1.0.60" + GH_AW_INFO_CLI_VERSION: "v0.79.6" GH_AW_INFO_WORKFLOW_NAME: "Android Tools PR Reviewer" GH_AW_INFO_EXPERIMENTAL: "false" GH_AW_INFO_SUPPORTS_TOOLS_ALLOWLIST: "true" GH_AW_INFO_STAGED: "false" GH_AW_INFO_ALLOWED_DOMAINS: '["defaults","dotnet","github","aka.ms","microsoft.com"]' GH_AW_INFO_FIREWALL_ENABLED: "true" - GH_AW_INFO_AWF_VERSION: "v0.25.20" + GH_AW_INFO_AWF_VERSION: "v0.27.2" GH_AW_INFO_AWMG_VERSION: "" GH_AW_INFO_FIREWALL_TYPE: "squid" GH_AW_COMPILED_STRICT: "true" - uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); setupGlobals(core, github, context, exec, io, getOctokit); const { main } = require('${{ runner.temp }}/gh-aw/actions/generate_aw_info.cjs'); await main(core, context); + - name: Check daily workflow token guardrail + id: daily-effective-workflow-guardrail + if: ${{ env.GH_AW_MAX_DAILY_AI_CREDITS != '' }} + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_WORKFLOW_NAME: "Android Tools PR Reviewer" + GH_AW_WORKFLOW_ID: "android-tools-reviewer" + GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + GH_AW_WORKFLOW_DISPATCH_AW_CONTEXT: ${{ github.event.inputs.aw_context || '' }} + GH_AW_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_AW_MAX_DAILY_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_MAX_DAILY_AI_CREDITS || '5000' }} + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/check_daily_aic_workflow_guardrail.cjs'); + await main(); - name: Add eyes reaction for immediate feedback id: react if: github.event_name == 'issues' || github.event_name == 'issue_comment' || github.event_name == 'pull_request_review_comment' || github.event_name == 'discussion' || github.event_name == 'discussion_comment' || github.event_name == 'pull_request' && github.event.pull_request.head.repo.id == github.repository_id - uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: GH_AW_REACTION: "eyes" with: @@ -139,17 +172,30 @@ jobs: env: COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} - name: Checkout .github and .agents folders - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: persist-credentials: false sparse-checkout: | .github .agents + .antigravity + .claude + .codex + .crush + .gemini + .opencode + .pi sparse-checkout-cone-mode: true fetch-depth: 1 + - name: Save agent config folders for base branch restoration + env: + GH_AW_AGENT_FOLDERS: ".agents .antigravity .claude .codex .crush .gemini .github .opencode .pi" + GH_AW_AGENT_FILES: ".crush.json AGENTS.md ANTIGRAVITY.md CLAUDE.md GEMINI.md PI.md opencode.jsonc" + # poutine:ignore untrusted_checkout_exec + run: bash "${RUNNER_TEMP}/gh-aw/actions/save_base_github_folders.sh" - name: Check workflow lock file id: check-lock-file - uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: GH_AW_WORKFLOW_FILE: "android-tools-reviewer.lock.yml" GH_AW_CONTEXT_WORKFLOW_REF: "${{ github.workflow_ref }}" @@ -160,9 +206,9 @@ jobs: const { main } = require('${{ runner.temp }}/gh-aw/actions/check_workflow_timestamp_api.cjs'); await main(); - name: Check compile-agentic version - uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: - GH_AW_COMPILED_VERSION: "v0.68.3" + GH_AW_COMPILED_VERSION: "v0.79.6" with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); @@ -171,7 +217,9 @@ jobs: await main(); - name: Compute current body text id: sanitized - uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_ALLOWED_DOMAINS: "*.githubusercontent.com,*.vsblob.vsassets.io,aka.ms,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.nuget.org,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,azuresearch-usnc.nuget.org,azuresearch-ussc.nuget.org,builds.dotnet.microsoft.com,ci.dot.net,codeload.github.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,dc.services.visualstudio.com,dist.nuget.org,docs.github.com,dot.net,dotnet.microsoft.com,dotnetcli.blob.core.windows.net,github-cloud.githubusercontent.com,github-cloud.s3.amazonaws.com,github.blog,github.com,github.githubassets.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,lfs.github.com,microsoft.com,nuget.org,nuget.pkg.github.com,nugetregistryv2prod.blob.core.windows.net,objects.githubusercontent.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,oneocsp.microsoft.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,patch-diff.githubusercontent.com,pkgs.dev.azure.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com,www.microsoft.com" with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); @@ -181,7 +229,7 @@ jobs: - name: Add comment with workflow run link id: add-comment if: github.event_name == 'issues' || github.event_name == 'issue_comment' || github.event_name == 'pull_request_review_comment' || github.event_name == 'discussion' || github.event_name == 'discussion_comment' || github.event_name == 'pull_request' && github.event.pull_request.head.repo.id == github.repository_id - uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: GH_AW_WORKFLOW_NAME: "Android Tools PR Reviewer" with: @@ -194,11 +242,11 @@ jobs: env: GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt GH_AW_SAFE_OUTPUTS: ${{ runner.temp }}/gh-aw/safeoutputs/outputs.jsonl + GH_AW_EXPR_1A3A194A: ${{ github.event.discussion.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'discussion' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }} + GH_AW_EXPR_463A214A: ${{ github.event.pull_request.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'pull_request' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }} + GH_AW_EXPR_802A9F6A: ${{ github.event.issue.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'issue' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }} + GH_AW_EXPR_FF1D34CE: ${{ github.event.comment.id || fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').comment_id }} GH_AW_GITHUB_ACTOR: ${{ github.actor }} - GH_AW_GITHUB_EVENT_COMMENT_ID: ${{ github.event.comment.id }} - GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: ${{ github.event.discussion.number }} - GH_AW_GITHUB_EVENT_ISSUE_NUMBER: ${{ github.event.issue.number }} - GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number }} GH_AW_GITHUB_REPOSITORY: ${{ github.repository }} GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }} @@ -207,59 +255,63 @@ jobs: run: | bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh" { - cat << 'GH_AW_PROMPT_994477edffd80981_EOF' + cat << 'GH_AW_PROMPT_6dabf3c3eafb9cae_EOF' - GH_AW_PROMPT_994477edffd80981_EOF + GH_AW_PROMPT_6dabf3c3eafb9cae_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/xpia.md" cat "${RUNNER_TEMP}/gh-aw/prompts/temp_folder_prompt.md" cat "${RUNNER_TEMP}/gh-aw/prompts/markdown.md" cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_prompt.md" - cat << 'GH_AW_PROMPT_994477edffd80981_EOF' + cat << 'GH_AW_PROMPT_6dabf3c3eafb9cae_EOF' Tools: create_pull_request_review_comment(max:50), submit_pull_request_review, missing_tool, missing_data, noop + GH_AW_PROMPT_6dabf3c3eafb9cae_EOF + cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md" + cat << 'GH_AW_PROMPT_6dabf3c3eafb9cae_EOF' The following GitHub context information is available for this workflow: - {{#if __GH_AW_GITHUB_ACTOR__ }} + {{#if github.actor}} - **actor**: __GH_AW_GITHUB_ACTOR__ {{/if}} - {{#if __GH_AW_GITHUB_REPOSITORY__ }} + {{#if github.repository}} - **repository**: __GH_AW_GITHUB_REPOSITORY__ {{/if}} - {{#if __GH_AW_GITHUB_WORKSPACE__ }} + {{#if github.workspace}} - **workspace**: __GH_AW_GITHUB_WORKSPACE__ {{/if}} - {{#if __GH_AW_GITHUB_EVENT_ISSUE_NUMBER__ }} - - **issue-number**: #__GH_AW_GITHUB_EVENT_ISSUE_NUMBER__ + {{#if github.event.issue.number || (github.aw.context.item_type == 'issue' && github.aw.context.item_number)}} + - **issue-number**: #__GH_AW_EXPR_802A9F6A__ {{/if}} - {{#if __GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER__ }} - - **discussion-number**: #__GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER__ + {{#if github.event.discussion.number || (github.aw.context.item_type == 'discussion' && github.aw.context.item_number)}} + - **discussion-number**: #__GH_AW_EXPR_1A3A194A__ {{/if}} - {{#if __GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER__ }} - - **pull-request-number**: #__GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER__ + {{#if github.event.pull_request.number || (github.aw.context.item_type == 'pull_request' && github.aw.context.item_number)}} + - **pull-request-number**: #__GH_AW_EXPR_463A214A__ {{/if}} - {{#if __GH_AW_GITHUB_EVENT_COMMENT_ID__ }} - - **comment-id**: __GH_AW_GITHUB_EVENT_COMMENT_ID__ + {{#if github.event.comment.id || github.aw.context.comment_id}} + - **comment-id**: __GH_AW_EXPR_FF1D34CE__ {{/if}} - {{#if __GH_AW_GITHUB_RUN_ID__ }} + {{#if github.run_id}} - **workflow-run-id**: __GH_AW_GITHUB_RUN_ID__ {{/if}} - GH_AW_PROMPT_994477edffd80981_EOF + GH_AW_PROMPT_6dabf3c3eafb9cae_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md" if [ "$GITHUB_EVENT_NAME" = "issue_comment" ] && [ -n "$GH_AW_IS_PR_COMMENT" ] || [ "$GITHUB_EVENT_NAME" = "pull_request_review_comment" ] || [ "$GITHUB_EVENT_NAME" = "pull_request_review" ]; then cat "${RUNNER_TEMP}/gh-aw/prompts/pr_context_prompt.md" fi - cat << 'GH_AW_PROMPT_994477edffd80981_EOF' + cat << 'GH_AW_PROMPT_6dabf3c3eafb9cae_EOF' {{#runtime-import .github/workflows/android-tools-reviewer.md}} - GH_AW_PROMPT_994477edffd80981_EOF + GH_AW_PROMPT_6dabf3c3eafb9cae_EOF } > "$GH_AW_PROMPT" - name: Interpolate variables and render templates - uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + GH_AW_ENGINE_ID: "copilot" with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); @@ -267,18 +319,19 @@ jobs: const { main } = require('${{ runner.temp }}/gh-aw/actions/interpolate_prompt.cjs'); await main(); - name: Substitute placeholders - uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + GH_AW_EXPR_1A3A194A: ${{ github.event.discussion.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'discussion' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }} + GH_AW_EXPR_463A214A: ${{ github.event.pull_request.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'pull_request' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }} + GH_AW_EXPR_802A9F6A: ${{ github.event.issue.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'issue' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }} + GH_AW_EXPR_FF1D34CE: ${{ github.event.comment.id || fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').comment_id }} GH_AW_GITHUB_ACTOR: ${{ github.actor }} - GH_AW_GITHUB_EVENT_COMMENT_ID: ${{ github.event.comment.id }} - GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: ${{ github.event.discussion.number }} - GH_AW_GITHUB_EVENT_ISSUE_NUMBER: ${{ github.event.issue.number }} - GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number }} GH_AW_GITHUB_REPOSITORY: ${{ github.repository }} GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }} GH_AW_IS_PR_COMMENT: ${{ github.event.issue.pull_request && 'true' || '' }} + GH_AW_MCP_CLI_SERVERS_LIST: '- `safeoutputs` — run `safeoutputs --help` to see available tools' GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ACTIVATED: ${{ needs.pre_activation.outputs.activated }} GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_MATCHED_COMMAND: ${{ needs.pre_activation.outputs.matched_command }} with: @@ -292,15 +345,16 @@ jobs: return await substitutePlaceholders({ file: process.env.GH_AW_PROMPT, substitutions: { + GH_AW_EXPR_1A3A194A: process.env.GH_AW_EXPR_1A3A194A, + GH_AW_EXPR_463A214A: process.env.GH_AW_EXPR_463A214A, + GH_AW_EXPR_802A9F6A: process.env.GH_AW_EXPR_802A9F6A, + GH_AW_EXPR_FF1D34CE: process.env.GH_AW_EXPR_FF1D34CE, GH_AW_GITHUB_ACTOR: process.env.GH_AW_GITHUB_ACTOR, - GH_AW_GITHUB_EVENT_COMMENT_ID: process.env.GH_AW_GITHUB_EVENT_COMMENT_ID, - GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER: process.env.GH_AW_GITHUB_EVENT_DISCUSSION_NUMBER, - GH_AW_GITHUB_EVENT_ISSUE_NUMBER: process.env.GH_AW_GITHUB_EVENT_ISSUE_NUMBER, - GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER: process.env.GH_AW_GITHUB_EVENT_PULL_REQUEST_NUMBER, GH_AW_GITHUB_REPOSITORY: process.env.GH_AW_GITHUB_REPOSITORY, GH_AW_GITHUB_RUN_ID: process.env.GH_AW_GITHUB_RUN_ID, GH_AW_GITHUB_WORKSPACE: process.env.GH_AW_GITHUB_WORKSPACE, GH_AW_IS_PR_COMMENT: process.env.GH_AW_IS_PR_COMMENT, + GH_AW_MCP_CLI_SERVERS_LIST: process.env.GH_AW_MCP_CLI_SERVERS_LIST, GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ACTIVATED: process.env.GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ACTIVATED, GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_MATCHED_COMMAND: process.env.GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_MATCHED_COMMAND } @@ -320,15 +374,24 @@ jobs: uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: activation + include-hidden-files: true path: | /tmp/gh-aw/aw_info.json + /tmp/gh-aw/model_multipliers.json + /tmp/gh-aw/models.json /tmp/gh-aw/aw-prompts/prompt.txt + /tmp/gh-aw/aw-prompts/prompt-template.txt + /tmp/gh-aw/aw-prompts/prompt-import-tree.json /tmp/gh-aw/github_rate_limits.jsonl + /tmp/gh-aw/base + /tmp/gh-aw/.github/agents + /tmp/gh-aw/.github/skills if-no-files-found: ignore retention-days: 1 agent: needs: activation + if: needs.activation.outputs.daily_effective_workflow_exceeded != 'true' runs-on: ubuntu-latest permissions: contents: read @@ -341,25 +404,38 @@ jobs: GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs GH_AW_WORKFLOW_ID_SANITIZED: androidtoolsreviewer outputs: - agentic_engine_timeout: ${{ steps.detect-copilot-errors.outputs.agentic_engine_timeout || 'false' }} + agentic_engine_timeout: ${{ steps.detect-agent-errors.outputs.agentic_engine_timeout || 'false' }} + ai_credits_rate_limit_error: ${{ steps.parse-mcp-gateway.outputs.ai_credits_rate_limit_error || 'false' }} + aic: ${{ steps.parse-mcp-gateway.outputs.aic }} + ambient_context: ${{ steps.parse-mcp-gateway.outputs.ambient_context }} checkout_pr_success: ${{ steps.checkout-pr.outputs.checkout_pr_success || 'true' }} effective_tokens: ${{ steps.parse-mcp-gateway.outputs.effective_tokens }} has_patch: ${{ steps.collect_output.outputs.has_patch }} - inference_access_error: ${{ steps.detect-copilot-errors.outputs.inference_access_error || 'false' }} - mcp_policy_error: ${{ steps.detect-copilot-errors.outputs.mcp_policy_error || 'false' }} + inference_access_error: ${{ steps.detect-agent-errors.outputs.inference_access_error || 'false' }} + mcp_policy_error: ${{ steps.detect-agent-errors.outputs.mcp_policy_error || 'false' }} model: ${{ needs.activation.outputs.model }} - model_not_supported_error: ${{ steps.detect-copilot-errors.outputs.model_not_supported_error || 'false' }} + model_not_supported_error: ${{ steps.detect-agent-errors.outputs.model_not_supported_error || 'false' }} output: ${{ steps.collect_output.outputs.output }} output_types: ${{ steps.collect_output.outputs.output_types }} + setup-parent-span-id: ${{ steps.setup.outputs.parent-span-id || steps.setup.outputs.span-id }} + setup-span-id: ${{ steps.setup.outputs.span-id }} setup-trace-id: ${{ steps.setup.outputs.trace-id }} + unknown_model_ai_credits: ${{ steps.parse-mcp-gateway.outputs.unknown_model_ai_credits || 'false' }} steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@ba90f2186d7ad780ec640f364005fa24e797b360 # v0.68.3 + uses: github/gh-aw-actions/setup@5c2fe865bb4dc46e1450f6ee0d0541d759aea73a # v0.79.6 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} trace-id: ${{ needs.activation.outputs.setup-trace-id }} + parent-span-id: ${{ needs.activation.outputs.setup-parent-span-id || needs.activation.outputs.setup-span-id }} + env: + GH_AW_SETUP_WORKFLOW_NAME: "Android Tools PR Reviewer" + GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/android-tools-reviewer.lock.yml@${{ github.ref }} + GH_AW_INFO_VERSION: "1.0.60" + GH_AW_INFO_AWF_VERSION: "v0.27.2" + GH_AW_INFO_ENGINE_ID: "copilot" - name: Set runtime paths id: set-runtime-paths run: | @@ -369,7 +445,7 @@ jobs: echo "GH_AW_SAFE_OUTPUTS_TOOLS_PATH=${RUNNER_TEMP}/gh-aw/safeoutputs/tools.json" } >> "$GITHUB_OUTPUT" - name: Checkout repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: persist-credentials: false - name: Create gh-aw temp directory @@ -394,8 +470,8 @@ jobs: - name: Checkout PR branch id: checkout-pr if: | - github.event.pull_request || github.event.issue.pull_request - uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + github.event.pull_request || github.event.issue.pull_request || github.event_name == 'workflow_dispatch' && fromJSON(github.event.inputs.aw_context || '{}').item_type == 'pull_request' + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: GH_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} with: @@ -406,11 +482,11 @@ jobs: const { main } = require('${{ runner.temp }}/gh-aw/actions/checkout_pr_branch.cjs'); await main(); - name: Install GitHub Copilot CLI - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.21 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.60 env: GH_HOST: github.com - name: Install AWF binary - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.20 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.2 - name: Parse integrity filter lists id: parse-guard-vars env: @@ -418,17 +494,37 @@ jobs: GH_AW_TRUSTED_USERS_VAR: ${{ vars.GH_AW_GITHUB_TRUSTED_USERS || '' }} GH_AW_APPROVAL_LABELS_VAR: ${{ vars.GH_AW_GITHUB_APPROVAL_LABELS || '' }} run: bash "${RUNNER_TEMP}/gh-aw/actions/parse_guard_list.sh" + - name: Download activation artifact + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: activation + path: /tmp/gh-aw + - name: Restore agent config folders from base branch + if: steps.checkout-pr.outcome == 'success' + env: + GH_AW_AGENT_FOLDERS: ".agents .antigravity .claude .codex .crush .gemini .github .opencode .pi" + GH_AW_AGENT_FILES: ".crush.json AGENTS.md ANTIGRAVITY.md CLAUDE.md GEMINI.md PI.md opencode.jsonc" + run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_base_github_folders.sh" + - name: Restore inline sub-agents from activation artifact + env: + GH_AW_SUB_AGENT_DIR: ".github/agents" + GH_AW_SUB_AGENT_EXT: ".agent.md" + run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_inline_sub_agents.sh" + - name: Restore inline skills from activation artifact + env: + GH_AW_SKILL_DIR: ".github/skills" + run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_inline_skills.sh" - name: Download container images - run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.25.20 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.20 ghcr.io/github/gh-aw-firewall/squid:0.25.20 ghcr.io/github/gh-aw-mcpg:v0.2.19 ghcr.io/github/github-mcp-server:v0.32.0 node:lts-alpine - - name: Write Safe Outputs Config + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.2@sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6 ghcr.io/github/gh-aw-firewall/api-proxy:0.27.2@sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4 ghcr.io/github/gh-aw-firewall/squid:0.27.2@sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591 ghcr.io/github/gh-aw-mcpg:v0.3.25@sha256:c10331ad17668ef89f38f5e356678788a40b0cd5fef96e8f92e1d9c1de47cbaa ghcr.io/github/github-mcp-server:v1.1.2@sha256:30197479d8036c7811892bc07e06f9a05c9ef3cdd79bc59f256d50647f95788c + - name: Generate Safe Outputs Config run: | mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs - cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_603f5e61db821820_EOF' + cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_161badeedaa0bf19_EOF' {"create_pull_request_review_comment":{"max":50,"side":"RIGHT"},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"report_incomplete":{},"submit_pull_request_review":{"allowed_events":["COMMENT","REQUEST_CHANGES"],"max":1}} - GH_AW_SAFE_OUTPUTS_CONFIG_603f5e61db821820_EOF - - name: Write Safe Outputs Tools + GH_AW_SAFE_OUTPUTS_CONFIG_161badeedaa0bf19_EOF + - name: Generate Safe Outputs Tools env: GH_AW_TOOLS_META_JSON: | { @@ -566,11 +662,18 @@ jobs: "REQUEST_CHANGES", "COMMENT" ] + }, + "pull_request_number": { + "optionalPositiveInteger": true + }, + "repo": { + "type": "string", + "maxLength": 256 } } } } - uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); @@ -626,11 +729,12 @@ jobs: GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} run: | set -eo pipefail - mkdir -p /tmp/gh-aw/mcp-config + mkdir -p "${RUNNER_TEMP}/gh-aw/mcp-config" # Export gateway environment variables for MCP config and gateway script - export MCP_GATEWAY_PORT="80" + export MCP_GATEWAY_PORT="8080" export MCP_GATEWAY_DOMAIN="host.docker.internal" + export MCP_GATEWAY_HOST_DOMAIN="localhost" MCP_GATEWAY_API_KEY=$(openssl rand -base64 45 | tr -d '/+=') echo "::add-mask::${MCP_GATEWAY_API_KEY}" export MCP_GATEWAY_API_KEY @@ -640,15 +744,24 @@ jobs: export DEBUG="*" export GH_AW_ENGINE="copilot" - export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host -v /var/run/docker.sock:/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/github/gh-aw-mcpg:v0.2.19' + MCP_GATEWAY_UID=$(id -u 2>/dev/null || echo '0') + MCP_GATEWAY_GID=$(id -g 2>/dev/null || echo '0') + case "${DOCKER_HOST:-}" in + unix://* ) DOCKER_SOCK_PATH="${DOCKER_HOST#unix://}" ;; + /* ) DOCKER_SOCK_PATH="$DOCKER_HOST" ;; + * ) DOCKER_SOCK_PATH=/var/run/docker.sock ;; + esac + DOCKER_SOCK_GID=$(stat -c '%g' "$DOCKER_SOCK_PATH" 2>/dev/null || echo '0') + export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host --add-host host.docker.internal:127.0.0.1 --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v '"${DOCKER_SOCK_PATH}"':/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DOCKER_HOST=unix:///var/run/docker.sock -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/github/gh-aw-mcpg:v0.3.25' mkdir -p /home/runner/.copilot - cat << GH_AW_MCP_CONFIG_c8e52fbe22218e9c_EOF | bash "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.sh" + GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node) + cat << GH_AW_MCP_CONFIG_5df4e2192112624c_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" { "mcpServers": { "github": { "type": "stdio", - "container": "ghcr.io/github/github-mcp-server:v0.32.0", + "container": "ghcr.io/github/github-mcp-server:v1.1.2", "env": { "GITHUB_HOST": "\${GITHUB_SERVER_URL}", "GITHUB_PERSONAL_ACCESS_TOKEN": "\${GITHUB_MCP_SERVER_TOKEN}", @@ -687,37 +800,80 @@ jobs: "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" } } - GH_AW_MCP_CONFIG_c8e52fbe22218e9c_EOF - - name: Download activation artifact - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + GH_AW_MCP_CONFIG_5df4e2192112624c_EOF + - name: Mount MCP servers as CLIs + id: mount-mcp-clis + continue-on-error: true + env: + MCP_GATEWAY_API_KEY: ${{ steps.start-mcp-gateway.outputs.gateway-api-key }} + MCP_GATEWAY_DOMAIN: ${{ steps.start-mcp-gateway.outputs.gateway-domain }} + MCP_GATEWAY_PORT: ${{ steps.start-mcp-gateway.outputs.gateway-port }} + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 with: - name: activation - path: /tmp/gh-aw - - name: Clean git credentials + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('${{ runner.temp }}/gh-aw/actions/mount_mcp_as_cli.cjs'); + await main(); + - name: Clean credentials continue-on-error: true run: bash "${RUNNER_TEMP}/gh-aw/actions/clean_git_credentials.sh" + - name: Audit pre-agent workspace + id: pre_agent_audit + continue-on-error: true + run: bash "${RUNNER_TEMP}/gh-aw/actions/audit_pre_agent_workspace.sh" - name: Execute GitHub Copilot CLI id: agentic_execution # Copilot CLI tool arguments (sorted): timeout-minutes: 20 run: | set -o pipefail + printf '%s' "$(date +%s%3N)" > /tmp/gh-aw/agent_cli_start_ms.txt + trap 'rm -f /home/runner/.copilot/settings.json' EXIT + mkdir -p /home/runner/.copilot + printf '%s' '{"builtInAgents":{"rubberDuck":false}}' > /home/runner/.copilot/settings.json touch /tmp/gh-aw/agent-step-summary.md + GH_AW_NODE_BIN=$(command -v node 2>/dev/null || true) + export GH_AW_NODE_BIN + export COPILOT_API_KEY="$COPILOT_DUMMY_BYOK" (umask 177 && touch /tmp/gh-aw/agent-stdio.log) + GH_AW_MAX_AI_CREDITS="${{ vars.GH_AW_DEFAULT_MAX_AI_CREDITS || '1000' }}" + printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.2/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"*.githubusercontent.com\",\"*.vsblob.vsassets.io\",\"aka.ms\",\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"api.nuget.org\",\"api.snapcraft.io\",\"archive.ubuntu.com\",\"azure.archive.ubuntu.com\",\"azuresearch-usnc.nuget.org\",\"azuresearch-ussc.nuget.org\",\"builds.dotnet.microsoft.com\",\"ci.dot.net\",\"codeload.github.com\",\"crl.geotrust.com\",\"crl.globalsign.com\",\"crl.identrust.com\",\"crl.sectigo.com\",\"crl.thawte.com\",\"crl.usertrust.com\",\"crl.verisign.com\",\"crl3.digicert.com\",\"crl4.digicert.com\",\"crls.ssl.com\",\"dc.services.visualstudio.com\",\"dist.nuget.org\",\"docs.github.com\",\"dot.net\",\"dotnet.microsoft.com\",\"dotnetcli.blob.core.windows.net\",\"github-cloud.githubusercontent.com\",\"github-cloud.s3.amazonaws.com\",\"github.blog\",\"github.com\",\"github.githubassets.com\",\"host.docker.internal\",\"json-schema.org\",\"json.schemastore.org\",\"keyserver.ubuntu.com\",\"lfs.github.com\",\"microsoft.com\",\"nuget.org\",\"nuget.pkg.github.com\",\"nugetregistryv2prod.blob.core.windows.net\",\"objects.githubusercontent.com\",\"ocsp.digicert.com\",\"ocsp.geotrust.com\",\"ocsp.globalsign.com\",\"ocsp.identrust.com\",\"ocsp.sectigo.com\",\"ocsp.ssl.com\",\"ocsp.thawte.com\",\"ocsp.usertrust.com\",\"ocsp.verisign.com\",\"oneocsp.microsoft.com\",\"packagecloud.io\",\"packages.cloud.google.com\",\"packages.microsoft.com\",\"patch-diff.githubusercontent.com\",\"pkgs.dev.azure.com\",\"ppa.launchpad.net\",\"raw.githubusercontent.com\",\"registry.npmjs.org\",\"s.symcb.com\",\"s.symcd.com\",\"security.ubuntu.com\",\"telemetry.enterprise.githubcopilot.com\",\"ts-crl.ws.symantec.com\",\"ts-ocsp.ws.symantec.com\",\"www.googleapis.com\",\"www.microsoft.com\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.4\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"large\":[\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"vision\":[\"copilot/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.27.2,squid=sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591,agent=sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6,api-proxy=sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4,cli-proxy=sha256:02f3ec08f32dc26c5427920c6a2e2f3036238fce44802f2f11ef49ed8621b5d0\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json" + GH_AW_MODEL_MULTIPLIERS_PATH="/tmp/gh-aw/model_multipliers.json" node "${RUNNER_TEMP}/gh-aw/actions/merge_awf_model_multipliers.cjs" + cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json + export GH_AW_MODELS_JSON_PATH="/tmp/gh-aw/models.json" + GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="" + if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then + GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="--docker-host-path-prefix /tmp/gh-aw" + fi + GH_AW_TOOL_CACHE_MOUNT="" + GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:-/opt/hostedtoolcache}" + if [ -d "$GH_AW_TOOL_CACHE" ]; then + if [[ "$GH_AW_TOOL_CACHE" != /opt/* ]]; then + GH_AW_TOOL_CACHE_MOUNT="$GH_AW_TOOL_CACHE:$GH_AW_TOOL_CACHE:ro" + fi + elif [ -d "/home/runner/work/_tool" ]; then + GH_AW_TOOL_CACHE_MOUNT="/home/runner/work/_tool:/home/runner/work/_tool:ro" + fi # shellcheck disable=SC1003 - sudo -E awf --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --env-all --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --allow-domains '*.githubusercontent.com,*.vsblob.vsassets.io,aka.ms,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.nuget.org,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,azuresearch-usnc.nuget.org,azuresearch-ussc.nuget.org,builds.dotnet.microsoft.com,ci.dot.net,codeload.github.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,dc.services.visualstudio.com,dist.nuget.org,docs.github.com,dot.net,dotnet.microsoft.com,dotnetcli.blob.core.windows.net,github-cloud.githubusercontent.com,github-cloud.s3.amazonaws.com,github.blog,github.com,github.githubassets.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,lfs.github.com,microsoft.com,nuget.org,nuget.pkg.github.com,nugetregistryv2prod.blob.core.windows.net,objects.githubusercontent.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,oneocsp.microsoft.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,pkgs.dev.azure.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com,www.microsoft.com' --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --image-tag 0.25.20 --skip-pull --enable-api-proxy \ - -- /bin/bash -c 'node ${RUNNER_TEMP}/gh-aw/actions/copilot_driver.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log + sudo -E awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS} --env-all --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --skip-pull \ + -- /bin/bash -c 'set +o histexpand; export PATH="${RUNNER_TEMP}/gh-aw/mcp-cli/bin:$PATH" && GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:-/opt/hostedtoolcache}"; export PATH="$(find "$GH_AW_TOOL_CACHE" /opt/hostedtoolcache /home/runner/work/_tool -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; GH_AW_NPM_GLOBAL_ROOT="$(npm root -g 2>/dev/null || true)"; if [ -n "$GH_AW_NPM_GLOBAL_ROOT" ]; then export NODE_PATH="${GH_AW_NPM_GLOBAL_ROOT}${NODE_PATH:+:${NODE_PATH}}"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log env: + AWF_REFLECT_ENABLED: 1 COPILOT_AGENT_RUNNER_TYPE: STANDALONE + COPILOT_DUMMY_BYOK: dummy-byok-key-for-offline-mode COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} COPILOT_MODEL: claude-opus-4.6 + GH_AW_MAX_TURNS: ${{ vars.GH_AW_DEFAULT_MAX_TURNS || '' }} GH_AW_MCP_CONFIG: /home/runner/.copilot/mcp-config.json GH_AW_PHASE: agent GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} - GH_AW_VERSION: v0.68.3 + GH_AW_TIMEOUT_MINUTES: 20 + GH_AW_VERSION: v0.79.6 GITHUB_API_URL: ${{ github.api_url }} GITHUB_AW: true + GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows GITHUB_HEAD_REF: ${{ github.head_ref }} GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} GITHUB_REF_NAME: ${{ github.ref_name }} @@ -728,12 +884,13 @@ jobs: GIT_AUTHOR_NAME: github-actions[bot] GIT_COMMITTER_EMAIL: github-actions[bot]@users.noreply.github.com GIT_COMMITTER_NAME: github-actions[bot] + RUNNER_TEMP: ${{ runner.temp }} XDG_CONFIG_HOME: /home/runner - - name: Detect Copilot errors - id: detect-copilot-errors + - name: Detect agent errors if: always() + id: detect-agent-errors continue-on-error: true - run: node "${RUNNER_TEMP}/gh-aw/actions/detect_copilot_errors.cjs" + run: node "${RUNNER_TEMP}/gh-aw/actions/detect_agent_errors.cjs" - name: Configure Git credentials env: REPO_NAME: ${{ github.repository }} @@ -762,7 +919,7 @@ jobs: bash "${RUNNER_TEMP}/gh-aw/actions/stop_mcp_gateway.sh" "$GATEWAY_PID" - name: Redact secrets in logs if: always() - uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); @@ -788,13 +945,13 @@ jobs: - name: Ingest agent output id: collect_output if: always() - uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} - GH_AW_ALLOWED_DOMAINS: "*.githubusercontent.com,*.vsblob.vsassets.io,aka.ms,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.nuget.org,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,azuresearch-usnc.nuget.org,azuresearch-ussc.nuget.org,builds.dotnet.microsoft.com,ci.dot.net,codeload.github.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,dc.services.visualstudio.com,dist.nuget.org,docs.github.com,dot.net,dotnet.microsoft.com,dotnetcli.blob.core.windows.net,github-cloud.githubusercontent.com,github-cloud.s3.amazonaws.com,github.blog,github.com,github.githubassets.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,lfs.github.com,microsoft.com,nuget.org,nuget.pkg.github.com,nugetregistryv2prod.blob.core.windows.net,objects.githubusercontent.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,oneocsp.microsoft.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,pkgs.dev.azure.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com,www.microsoft.com" + GH_AW_ALLOWED_DOMAINS: "*.githubusercontent.com,*.vsblob.vsassets.io,aka.ms,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.nuget.org,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,azuresearch-usnc.nuget.org,azuresearch-ussc.nuget.org,builds.dotnet.microsoft.com,ci.dot.net,codeload.github.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,dc.services.visualstudio.com,dist.nuget.org,docs.github.com,dot.net,dotnet.microsoft.com,dotnetcli.blob.core.windows.net,github-cloud.githubusercontent.com,github-cloud.s3.amazonaws.com,github.blog,github.com,github.githubassets.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,lfs.github.com,microsoft.com,nuget.org,nuget.pkg.github.com,nugetregistryv2prod.blob.core.windows.net,objects.githubusercontent.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,oneocsp.microsoft.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,patch-diff.githubusercontent.com,pkgs.dev.azure.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com,www.microsoft.com" GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_API_URL: ${{ github.api_url }} - GH_AW_COMMAND: review + GH_AW_COMMANDS: "[\"review\"]" with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); @@ -803,7 +960,7 @@ jobs: await main(); - name: Parse agent logs for step summary if: always() - uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: GH_AW_AGENT_OUTPUT: /tmp/gh-aw/sandbox/agent/logs/ with: @@ -815,7 +972,7 @@ jobs: - name: Parse MCP Gateway logs for step summary if: always() id: parse-mcp-gateway - uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); @@ -828,9 +985,9 @@ jobs: env: AWF_LOGS_DIR: /tmp/gh-aw/sandbox/firewall/logs run: | - # Fix permissions on firewall logs so they can be uploaded as artifacts + # Fix permissions on firewall logs/audit dirs so they can be uploaded as artifacts # AWF runs with sudo, creating files owned by root - sudo chmod -R a+r /tmp/gh-aw/sandbox/firewall/logs 2>/dev/null || true + sudo chmod -R a+rX /tmp/gh-aw/sandbox/firewall 2>/dev/null || true # Only run awf logs summary if awf command exists (it may not be installed if workflow failed before install step) if command -v awf &> /dev/null; then awf logs summary | tee -a "$GITHUB_STEP_SUMMARY" @@ -840,13 +997,23 @@ jobs: - name: Parse token usage for step summary if: always() continue-on-error: true - uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); setupGlobals(core, github, context, exec, io, getOctokit); const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_token_usage.cjs'); await main(); + - name: Print AWF reflect summary + if: always() + continue-on-error: true + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/awf_reflect_summary.cjs'); + await main(); - name: Write agent output placeholder if missing if: always() run: | @@ -868,14 +1035,17 @@ jobs: !/tmp/gh-aw/proxy-logs/proxy-tls/ /tmp/gh-aw/agent_usage.json /tmp/gh-aw/agent-stdio.log + /tmp/gh-aw/pre-agent-audit.txt /tmp/gh-aw/agent/ /tmp/gh-aw/github_rate_limits.jsonl /tmp/gh-aw/safeoutputs.jsonl /tmp/gh-aw/agent_output.json /tmp/gh-aw/aw-*.patch /tmp/gh-aw/aw-*.bundle + /tmp/gh-aw/awf-config.json /tmp/gh-aw/sandbox/firewall/logs/ /tmp/gh-aw/sandbox/firewall/audit/ + /tmp/gh-aw/sandbox/firewall/awf-reflect.json if-no-files-found: ignore conclusion: @@ -886,7 +1056,7 @@ jobs: - safe_outputs if: > always() && (needs.agent.result != 'skipped' || needs.activation.outputs.lockdown_check_failed == 'true' || - needs.activation.outputs.stale_lock_file_failed == 'true') + needs.activation.outputs.stale_lock_file_failed == 'true' || needs.activation.outputs.daily_effective_workflow_exceeded == 'true') runs-on: ubuntu-slim permissions: contents: read @@ -894,6 +1064,7 @@ jobs: concurrency: group: "gh-aw-conclusion-android-tools-reviewer" cancel-in-progress: false + queue: max outputs: incomplete_count: ${{ steps.report_incomplete.outputs.incomplete_count }} noop_message: ${{ steps.noop.outputs.noop_message }} @@ -902,11 +1073,18 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@ba90f2186d7ad780ec640f364005fa24e797b360 # v0.68.3 + uses: github/gh-aw-actions/setup@5c2fe865bb4dc46e1450f6ee0d0541d759aea73a # v0.79.6 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} trace-id: ${{ needs.activation.outputs.setup-trace-id }} + parent-span-id: ${{ needs.activation.outputs.setup-parent-span-id || needs.activation.outputs.setup-span-id }} + env: + GH_AW_SETUP_WORKFLOW_NAME: "Android Tools PR Reviewer" + GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/android-tools-reviewer.lock.yml@${{ github.ref }} + GH_AW_INFO_VERSION: "1.0.60" + GH_AW_INFO_AWF_VERSION: "v0.27.2" + GH_AW_INFO_ENGINE_ID: "copilot" - name: Download agent output artifact id: download-agent-output continue-on-error: true @@ -921,16 +1099,55 @@ jobs: mkdir -p /tmp/gh-aw/ find "/tmp/gh-aw/" -type f -print echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT" + - name: Collect usage artifact files + if: always() + continue-on-error: true + run: | + mkdir -p /tmp/gh-aw/usage/agent /tmp/gh-aw/usage/detection + echo "Usage artifact source file status:" + for file in /tmp/gh-aw/aw-info.jsonl /tmp/gh-aw/agent_usage.jsonl /tmp/gh-aw/detection_usage.jsonl /tmp/gh-aw/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/threat-detection/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/threat-detection/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/threat-detection/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl; do + [ -f "$file" ] && echo "FOUND: $file" || echo "MISSING: $file" + done + [ -f /tmp/gh-aw/aw-info.jsonl ] && cp /tmp/gh-aw/aw-info.jsonl /tmp/gh-aw/usage/aw-info.jsonl || true + [ -f /tmp/gh-aw/agent_usage.jsonl ] && cp /tmp/gh-aw/agent_usage.jsonl /tmp/gh-aw/usage/agent_usage.jsonl || true + [ -f /tmp/gh-aw/detection_usage.jsonl ] && cp /tmp/gh-aw/detection_usage.jsonl /tmp/gh-aw/usage/detection_usage.jsonl || true + [ -f /tmp/gh-aw/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl || true + [ -f /tmp/gh-aw/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl || true + [ -f /tmp/gh-aw/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl || true + [ -f /tmp/gh-aw/threat-detection/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/threat-detection/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl || true + [ -f /tmp/gh-aw/threat-detection/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/threat-detection/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl || true + [ -f /tmp/gh-aw/threat-detection/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/threat-detection/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl || true + [ -f /tmp/gh-aw/usage/agent/token_usage.jsonl ] || : > /tmp/gh-aw/usage/agent/token_usage.jsonl + [ -f /tmp/gh-aw/usage/detection/token_usage.jsonl ] || : > /tmp/gh-aw/usage/detection/token_usage.jsonl + find /tmp/gh-aw/usage -type f -print | sort + - name: Upload usage artifact + if: always() + continue-on-error: true + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: usage + path: | + /tmp/gh-aw/usage/aw-info.jsonl + /tmp/gh-aw/usage/agent_usage.jsonl + /tmp/gh-aw/usage/detection_usage.jsonl + /tmp/gh-aw/usage/agent/token_usage.jsonl + /tmp/gh-aw/usage/detection/token_usage.jsonl + if-no-files-found: ignore - name: Process no-op messages id: noop - uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} GH_AW_NOOP_MAX: "1" GH_AW_WORKFLOW_NAME: "Android Tools PR Reviewer" + GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/android-tools-reviewer.md" GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }} GH_AW_NOOP_REPORT_AS_ISSUE: "true" + GH_AW_AIC: ${{ needs.agent.outputs.aic }} + GH_AW_THREAT_DETECTION_AIC: ${{ needs.detection.outputs.aic }} + GH_AW_AMBIENT_CONTEXT: ${{ needs.agent.outputs.ambient_context }} + GH_AW_WORKFLOW_ID: "android-tools-reviewer" with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | @@ -940,10 +1157,11 @@ jobs: await main(); - name: Log detection run id: detection_runs - uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} GH_AW_WORKFLOW_NAME: "Android Tools PR Reviewer" + GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/android-tools-reviewer.md" GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} GH_AW_DETECTION_CONCLUSION: ${{ needs.detection.outputs.detection_conclusion }} GH_AW_DETECTION_REASON: ${{ needs.detection.outputs.detection_reason }} @@ -956,11 +1174,12 @@ jobs: await main(); - name: Record missing tool id: missing_tool - uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} GH_AW_MISSING_TOOL_CREATE_ISSUE: "true" GH_AW_WORKFLOW_NAME: "Android Tools PR Reviewer" + GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/android-tools-reviewer.md" with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | @@ -970,11 +1189,12 @@ jobs: await main(); - name: Record incomplete id: report_incomplete - uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} GH_AW_REPORT_INCOMPLETE_CREATE_ISSUE: "true" GH_AW_WORKFLOW_NAME: "Android Tools PR Reviewer" + GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/android-tools-reviewer.md" with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} script: | @@ -985,24 +1205,38 @@ jobs: - name: Handle agent failure id: handle_agent_failure if: always() - uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} GH_AW_WORKFLOW_NAME: "Android Tools PR Reviewer" + GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/android-tools-reviewer.md" GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }} GH_AW_WORKFLOW_ID: "android-tools-reviewer" + GH_AW_ACTION_FAILURE_ISSUE_EXPIRES_HOURS: "168" GH_AW_ENGINE_ID: "copilot" GH_AW_SECRET_VERIFICATION_RESULT: ${{ needs.activation.outputs.secret_verification_result }} GH_AW_CHECKOUT_PR_SUCCESS: ${{ needs.agent.outputs.checkout_pr_success }} + GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens || '' }} + GH_AW_AI_CREDITS_RATE_LIMIT_ERROR: ${{ needs.agent.outputs.ai_credits_rate_limit_error || 'false' }} + GH_AW_UNKNOWN_MODEL_AI_CREDITS: ${{ needs.agent.outputs.unknown_model_ai_credits || 'false' }} + GH_AW_AIC: ${{ needs.agent.outputs.aic }} + GH_AW_THREAT_DETECTION_AIC: ${{ needs.detection.outputs.aic }} + GH_AW_MAX_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_MAX_AI_CREDITS || '1000' }} GH_AW_INFERENCE_ACCESS_ERROR: ${{ needs.agent.outputs.inference_access_error }} GH_AW_MCP_POLICY_ERROR: ${{ needs.agent.outputs.mcp_policy_error }} GH_AW_AGENTIC_ENGINE_TIMEOUT: ${{ needs.agent.outputs.agentic_engine_timeout }} GH_AW_MODEL_NOT_SUPPORTED_ERROR: ${{ needs.agent.outputs.model_not_supported_error }} + GH_AW_ENGINE_API_HOSTS: "api.enterprise.githubcopilot.com,api.githubcopilot.com,api.business.githubcopilot.com,api.individual.githubcopilot.com" GH_AW_LOCKDOWN_CHECK_FAILED: ${{ needs.activation.outputs.lockdown_check_failed }} GH_AW_STALE_LOCK_FILE_FAILED: ${{ needs.activation.outputs.stale_lock_file_failed }} + GH_AW_DAILY_EFFECTIVE_WORKFLOW_EXCEEDED: ${{ needs.activation.outputs.daily_effective_workflow_exceeded }} + GH_AW_DAILY_EFFECTIVE_WORKFLOW_TOTAL_EFFECTIVE_TOKENS: ${{ needs.activation.outputs.daily_effective_workflow_total_effective_tokens }} + GH_AW_DAILY_EFFECTIVE_WORKFLOW_THRESHOLD: ${{ needs.activation.outputs.daily_effective_workflow_threshold }} GH_AW_GROUP_REPORTS: "false" GH_AW_FAILURE_REPORT_AS_ISSUE: "true" + GH_AW_MISSING_TOOL_REPORT_AS_FAILURE: "true" + GH_AW_MISSING_DATA_REPORT_AS_FAILURE: "true" GH_AW_TIMEOUT_MINUTES: "20" with: github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} @@ -1013,7 +1247,7 @@ jobs: await main(); - name: Update reaction comment with completion status id: conclusion - uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} GH_AW_COMMENT_ID: ${{ needs.activation.outputs.comment_id }} @@ -1021,6 +1255,7 @@ jobs: GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} GH_AW_WORKFLOW_NAME: "Android Tools PR Reviewer" GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }} + GH_AW_SAFE_OUTPUTS_RESULT: ${{ needs.safe_outputs.result }} GH_AW_DETECTION_CONCLUSION: ${{ needs.detection.outputs.detection_conclusion }} GH_AW_DETECTION_REASON: ${{ needs.detection.outputs.detection_reason }} with: @@ -1041,17 +1276,25 @@ jobs: permissions: contents: read outputs: + aic: ${{ steps.parse_detection_token_usage.outputs.aic }} detection_conclusion: ${{ steps.detection_conclusion.outputs.conclusion }} detection_reason: ${{ steps.detection_conclusion.outputs.reason }} detection_success: ${{ steps.detection_conclusion.outputs.success }} steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@ba90f2186d7ad780ec640f364005fa24e797b360 # v0.68.3 + uses: github/gh-aw-actions/setup@5c2fe865bb4dc46e1450f6ee0d0541d759aea73a # v0.79.6 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} trace-id: ${{ needs.activation.outputs.setup-trace-id }} + parent-span-id: ${{ needs.activation.outputs.setup-parent-span-id || needs.activation.outputs.setup-span-id }} + env: + GH_AW_SETUP_WORKFLOW_NAME: "Android Tools PR Reviewer" + GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/android-tools-reviewer.lock.yml@${{ github.ref }} + GH_AW_INFO_VERSION: "1.0.60" + GH_AW_INFO_AWF_VERSION: "v0.27.2" + GH_AW_INFO_ENGINE_ID: "copilot" - name: Download agent output artifact id: download-agent-output continue-on-error: true @@ -1068,7 +1311,7 @@ jobs: echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT" - name: Checkout repository for patch context if: needs.agent.outputs.has_patch == 'true' - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: persist-credentials: false # --- Threat Detection --- @@ -1077,7 +1320,7 @@ jobs: rm -rf /tmp/gh-aw/sandbox/firewall/logs rm -rf /tmp/gh-aw/sandbox/firewall/audit - name: Download container images - run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.25.20 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.20 ghcr.io/github/gh-aw-firewall/squid:0.25.20 + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.2@sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6 ghcr.io/github/gh-aw-firewall/api-proxy:0.27.2@sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4 ghcr.io/github/gh-aw-firewall/squid:0.27.2@sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591 - name: Check if detection needed id: detection_guard if: always() @@ -1092,17 +1335,21 @@ jobs: echo "run_detection=false" >> "$GITHUB_OUTPUT" echo "Detection skipped: no agent outputs or patches to analyze" fi - - name: Clear MCP configuration for detection + - name: Clear MCP Config for detection if: always() && steps.detection_guard.outputs.run_detection == 'true' run: | - rm -f /tmp/gh-aw/mcp-config/mcp-servers.json + rm -f "${RUNNER_TEMP}/gh-aw/mcp-config/mcp-servers.json" rm -f /home/runner/.copilot/mcp-config.json rm -f "$GITHUB_WORKSPACE/.gemini/settings.json" - name: Prepare threat detection files if: always() && steps.detection_guard.outputs.run_detection == 'true' run: | mkdir -p /tmp/gh-aw/threat-detection/aw-prompts + rm -f /tmp/gh-aw/agent_usage.json cp /tmp/gh-aw/aw-prompts/prompt.txt /tmp/gh-aw/threat-detection/aw-prompts/prompt.txt 2>/dev/null || true + if [ ! -s /tmp/gh-aw/threat-detection/aw-prompts/prompt.txt ]; then + echo "::warning::ERR_VALIDATION: Missing or empty detection context prompt at /tmp/gh-aw/threat-detection/aw-prompts/prompt.txt. Ensure the agent artifact includes /tmp/gh-aw/aw-prompts/prompt.txt. Detection will continue with fallback workflow context." + fi cp /tmp/gh-aw/agent_output.json /tmp/gh-aw/threat-detection/agent_output.json 2>/dev/null || true for f in /tmp/gh-aw/aw-*.patch; do [ -f "$f" ] && cp "$f" /tmp/gh-aw/threat-detection/ 2>/dev/null || true @@ -1114,7 +1361,7 @@ jobs: ls -la /tmp/gh-aw/threat-detection/ 2>/dev/null || true - name: Setup threat detection if: always() && steps.detection_guard.outputs.run_detection == 'true' - uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: WORKFLOW_NAME: "Android Tools PR Reviewer" WORKFLOW_DESCRIPTION: "No description provided" @@ -1130,33 +1377,69 @@ jobs: run: | mkdir -p /tmp/gh-aw/threat-detection touch /tmp/gh-aw/threat-detection/detection.log + - name: Setup Node.js + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: '24' + package-manager-cache: false - name: Install GitHub Copilot CLI - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.21 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.60 env: GH_HOST: github.com - name: Install AWF binary - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.20 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.2 - name: Execute GitHub Copilot CLI if: always() && steps.detection_guard.outputs.run_detection == 'true' + continue-on-error: true id: detection_agentic_execution # Copilot CLI tool arguments (sorted): timeout-minutes: 20 run: | set -o pipefail + printf '%s' "$(date +%s%3N)" > /tmp/gh-aw/agent_cli_start_ms.txt + trap 'rm -f /home/runner/.copilot/settings.json' EXIT + mkdir -p /home/runner/.copilot + printf '%s' '{"builtInAgents":{"rubberDuck":false}}' > /home/runner/.copilot/settings.json touch /tmp/gh-aw/agent-step-summary.md + GH_AW_NODE_BIN=$(command -v node 2>/dev/null || true) + export GH_AW_NODE_BIN + export COPILOT_API_KEY="$COPILOT_DUMMY_BYOK" (umask 177 && touch /tmp/gh-aw/threat-detection/detection.log) + GH_AW_MAX_AI_CREDITS="${{ vars.GH_AW_DEFAULT_DETECTION_MAX_AI_CREDITS || '400' }}" + printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.2/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"github.com\",\"host.docker.internal\",\"registry.npmjs.org\",\"telemetry.enterprise.githubcopilot.com\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS}},\"container\":{\"imageTag\":\"0.27.2,squid=sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591,agent=sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6,api-proxy=sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4,cli-proxy=sha256:02f3ec08f32dc26c5427920c6a2e2f3036238fce44802f2f11ef49ed8621b5d0\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json" + GH_AW_MODEL_MULTIPLIERS_PATH="/tmp/gh-aw/model_multipliers.json" node "${RUNNER_TEMP}/gh-aw/actions/merge_awf_model_multipliers.cjs" + cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json + export GH_AW_MODELS_JSON_PATH="/tmp/gh-aw/models.json" + GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="" + if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then + GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="--docker-host-path-prefix /tmp/gh-aw" + fi + GH_AW_TOOL_CACHE_MOUNT="" + GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:-/opt/hostedtoolcache}" + if [ -d "$GH_AW_TOOL_CACHE" ]; then + if [[ "$GH_AW_TOOL_CACHE" != /opt/* ]]; then + GH_AW_TOOL_CACHE_MOUNT="$GH_AW_TOOL_CACHE:$GH_AW_TOOL_CACHE:ro" + fi + elif [ -d "/home/runner/work/_tool" ]; then + GH_AW_TOOL_CACHE_MOUNT="/home/runner/work/_tool:/home/runner/work/_tool:ro" + fi # shellcheck disable=SC1003 - sudo -E awf --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" --env-all --exclude-env COPILOT_GITHUB_TOKEN --allow-domains api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,github.com,host.docker.internal,telemetry.enterprise.githubcopilot.com --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --image-tag 0.25.20 --skip-pull --enable-api-proxy \ - -- /bin/bash -c 'node ${RUNNER_TEMP}/gh-aw/actions/copilot_driver.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --add-dir "${GITHUB_WORKSPACE}" --prompt "$(cat /tmp/gh-aw/aw-prompts/prompt.txt)"' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log + sudo -E awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS} --env-all --exclude-env COPILOT_GITHUB_TOKEN --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --skip-pull \ + -- /bin/bash -c 'set +o histexpand; GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:-/opt/hostedtoolcache}"; export PATH="$(find "$GH_AW_TOOL_CACHE" /opt/hostedtoolcache /home/runner/work/_tool -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; GH_AW_NPM_GLOBAL_ROOT="$(npm root -g 2>/dev/null || true)"; if [ -n "$GH_AW_NPM_GLOBAL_ROOT" ]; then export NODE_PATH="${GH_AW_NPM_GLOBAL_ROOT}${NODE_PATH:+:${NODE_PATH}}"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log env: + AWF_REFLECT_ENABLED: 1 COPILOT_AGENT_RUNNER_TYPE: STANDALONE + COPILOT_DUMMY_BYOK: dummy-byok-key-for-offline-mode COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} COPILOT_MODEL: claude-opus-4.6 + GH_AW_MAX_TURNS: ${{ vars.GH_AW_DEFAULT_MAX_TURNS || '' }} GH_AW_PHASE: detection GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt - GH_AW_VERSION: v0.68.3 + GH_AW_TIMEOUT_MINUTES: 20 + GH_AW_VERSION: v0.79.6 GITHUB_API_URL: ${{ github.api_url }} GITHUB_AW: true + GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows GITHUB_HEAD_REF: ${{ github.head_ref }} GITHUB_REF_NAME: ${{ github.ref_name }} GITHUB_SERVER_URL: ${{ github.server_url }} @@ -1166,7 +1449,21 @@ jobs: GIT_AUTHOR_NAME: github-actions[bot] GIT_COMMITTER_EMAIL: github-actions[bot]@users.noreply.github.com GIT_COMMITTER_NAME: github-actions[bot] + RUNNER_TEMP: ${{ runner.temp }} XDG_CONFIG_HOME: /home/runner + - name: Parse threat detection token usage for step summary + id: parse_detection_token_usage + if: always() + continue-on-error: true + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_TOKEN_USAGE_SUMMARY_TITLE: Threat Detection Token Usage + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_token_usage.cjs'); + await main(); - name: Upload threat detection log if: always() && steps.detection_guard.outputs.run_detection == 'true' uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 @@ -1177,34 +1474,61 @@ jobs: - name: Parse and conclude threat detection id: detection_conclusion if: always() - uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + continue-on-error: true + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: RUN_DETECTION: ${{ steps.detection_guard.outputs.run_detection }} + DETECTION_AGENTIC_EXECUTION_OUTCOME: ${{ steps.detection_agentic_execution.outcome }} GH_AW_DETECTION_CONTINUE_ON_ERROR: "true" with: script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_threat_detection_results.cjs'); - await main(); + try { + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_threat_detection_results.cjs'); + await main(); + } catch (loadErr) { + const continueOnError = process.env.GH_AW_DETECTION_CONTINUE_ON_ERROR !== 'false'; + const detectionExecutionFailed = process.env.DETECTION_AGENTIC_EXECUTION_OUTCOME === 'failure'; + const msg = 'ERR_SYSTEM: \u274C Unexpected error loading threat detection module: ' + (loadErr && loadErr.message ? loadErr.message : String(loadErr)); + core.error(msg); + core.setOutput('reason', 'parse_error'); + if (continueOnError && !detectionExecutionFailed) { + core.warning('\u26A0\uFE0F ' + msg); + core.setOutput('conclusion', 'warning'); + core.setOutput('success', 'false'); + } else { + core.setOutput('conclusion', 'failure'); + core.setOutput('success', 'false'); + core.setFailed(msg); + } + } pre_activation: - if: "github.event_name == 'issue_comment' && (startsWith(github.event.comment.body, '/review ') || startsWith(github.event.comment.body, '/review\n') || github.event.comment.body == '/review') && github.event.issue.pull_request != null || !(github.event_name == 'issue_comment')" + if: "(github.event_name != 'issue_comment' && github.event_name != 'pull_request_review_comment' || contains(fromJSON('[\"OWNER\",\"MEMBER\",\"COLLABORATOR\"]'), github.event.comment.author_association)) && (github.event_name == 'issue_comment' && (startsWith(github.event.comment.body, '/review ') || startsWith(github.event.comment.body, '/review\n') || github.event.comment.body == '/review') && github.event.issue.pull_request != null || !(github.event_name == 'issue_comment'))" runs-on: ubuntu-slim outputs: activated: ${{ steps.check_membership.outputs.is_team_member == 'true' && steps.check_command_position.outputs.command_position_ok == 'true' }} matched_command: ${{ steps.check_command_position.outputs.matched_command }} + setup-parent-span-id: ${{ steps.setup.outputs.parent-span-id || steps.setup.outputs.span-id }} + setup-span-id: ${{ steps.setup.outputs.span-id }} setup-trace-id: ${{ steps.setup.outputs.trace-id }} steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@ba90f2186d7ad780ec640f364005fa24e797b360 # v0.68.3 + uses: github/gh-aw-actions/setup@5c2fe865bb4dc46e1450f6ee0d0541d759aea73a # v0.79.6 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} + env: + GH_AW_SETUP_WORKFLOW_NAME: "Android Tools PR Reviewer" + GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/android-tools-reviewer.lock.yml@${{ github.ref }} + GH_AW_INFO_VERSION: "1.0.60" + GH_AW_INFO_AWF_VERSION: "v0.27.2" + GH_AW_INFO_ENGINE_ID: "copilot" - name: Check team membership for command workflow id: check_membership - uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: GH_AW_REQUIRED_ROLES: "admin,maintainer,write" with: @@ -1216,7 +1540,7 @@ jobs: await main(); - name: Check command position id: check_command_position - uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: GH_AW_COMMANDS: "[\"review\"]" with: @@ -1236,16 +1560,23 @@ jobs: permissions: contents: read pull-requests: write - timeout-minutes: 15 + timeout-minutes: 45 env: + GH_AW_AGENT_AIC: ${{ needs.agent.outputs.aic }} + GH_AW_AIC: ${{ needs.agent.outputs.aic }} + GH_AW_AMBIENT_CONTEXT: ${{ needs.agent.outputs.ambient_context }} GH_AW_CALLER_WORKFLOW_ID: "${{ github.repository }}/android-tools-reviewer" + GH_AW_COMMANDS: "[\"review\"]" GH_AW_DETECTION_CONCLUSION: ${{ needs.detection.outputs.detection_conclusion }} GH_AW_DETECTION_REASON: ${{ needs.detection.outputs.detection_reason }} GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens }} GH_AW_ENGINE_ID: "copilot" GH_AW_ENGINE_MODEL: "claude-opus-4.6" + GH_AW_ENGINE_VERSION: "1.0.60" + GH_AW_THREAT_DETECTION_AIC: ${{ needs.detection.outputs.aic }} GH_AW_WORKFLOW_ID: "android-tools-reviewer" GH_AW_WORKFLOW_NAME: "Android Tools PR Reviewer" + GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/android-tools-reviewer.md" outputs: code_push_failure_count: ${{ steps.process_safe_outputs.outputs.code_push_failure_count }} code_push_failure_errors: ${{ steps.process_safe_outputs.outputs.code_push_failure_errors }} @@ -1256,11 +1587,18 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@ba90f2186d7ad780ec640f364005fa24e797b360 # v0.68.3 + uses: github/gh-aw-actions/setup@5c2fe865bb4dc46e1450f6ee0d0541d759aea73a # v0.79.6 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} trace-id: ${{ needs.activation.outputs.setup-trace-id }} + parent-span-id: ${{ needs.activation.outputs.setup-parent-span-id || needs.activation.outputs.setup-span-id }} + env: + GH_AW_SETUP_WORKFLOW_NAME: "Android Tools PR Reviewer" + GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/android-tools-reviewer.lock.yml@${{ github.ref }} + GH_AW_INFO_VERSION: "1.0.60" + GH_AW_INFO_AWF_VERSION: "v0.27.2" + GH_AW_INFO_ENGINE_ID: "copilot" - name: Download agent output artifact id: download-agent-output continue-on-error: true @@ -1278,6 +1616,7 @@ jobs: - name: Configure GH_HOST for enterprise compatibility id: ghes-host-config shell: bash + # zizmor: ignore[github-env] - GITHUB_SERVER_URL is set by GitHub Actions, not user input. run: | # Derive GH_HOST from GITHUB_SERVER_URL so the gh CLI targets the correct # GitHub instance (GHES/GHEC). On github.com this is a harmless no-op. @@ -1286,10 +1625,11 @@ jobs: echo "GH_HOST=${GH_HOST}" >> "$GITHUB_ENV" - name: Process Safe Outputs id: process_safe_outputs - uses: actions/github-script@373c709c69115d41ff229c7e5df9f8788daa9553 # v9 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} - GH_AW_ALLOWED_DOMAINS: "*.githubusercontent.com,*.vsblob.vsassets.io,aka.ms,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.nuget.org,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,azuresearch-usnc.nuget.org,azuresearch-ussc.nuget.org,builds.dotnet.microsoft.com,ci.dot.net,codeload.github.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,dc.services.visualstudio.com,dist.nuget.org,docs.github.com,dot.net,dotnet.microsoft.com,dotnetcli.blob.core.windows.net,github-cloud.githubusercontent.com,github-cloud.s3.amazonaws.com,github.blog,github.com,github.githubassets.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,lfs.github.com,microsoft.com,nuget.org,nuget.pkg.github.com,nugetregistryv2prod.blob.core.windows.net,objects.githubusercontent.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,oneocsp.microsoft.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,pkgs.dev.azure.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com,www.microsoft.com" + GH_AW_COMMENT_ID: ${{ needs.activation.outputs.comment_id }} + GH_AW_ALLOWED_DOMAINS: "*.githubusercontent.com,*.vsblob.vsassets.io,aka.ms,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.nuget.org,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,azuresearch-usnc.nuget.org,azuresearch-ussc.nuget.org,builds.dotnet.microsoft.com,ci.dot.net,codeload.github.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,dc.services.visualstudio.com,dist.nuget.org,docs.github.com,dot.net,dotnet.microsoft.com,dotnetcli.blob.core.windows.net,github-cloud.githubusercontent.com,github-cloud.s3.amazonaws.com,github.blog,github.com,github.githubassets.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,lfs.github.com,microsoft.com,nuget.org,nuget.pkg.github.com,nugetregistryv2prod.blob.core.windows.net,objects.githubusercontent.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,oneocsp.microsoft.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,patch-diff.githubusercontent.com,pkgs.dev.azure.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com,www.microsoft.com" GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_API_URL: ${{ github.api_url }} GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"create_pull_request_review_comment\":{\"max\":50,\"side\":\"RIGHT\"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"},\"report_incomplete\":{},\"submit_pull_request_review\":{\"allowed_events\":[\"COMMENT\",\"REQUEST_CHANGES\"],\"max\":1}}" From c01b7b062f0d6883db06f2624963f631edf2e3ce Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Mon, 15 Jun 2026 04:15:33 -0500 Subject: [PATCH 301/308] [ci] Update agentic workflows to claude-opus-4.8 (#389) The `claude-opus-4.6` model is retired/unsupported and produces this error in `gh-aw` workflow runs: > Error: model 'claude-opus-4.6' is retired or unsupported. Did you mean 'claude-opus-4.8'? Example failing run: https://github.com/dotnet/android/actions/runs/27439024224/job/81108439501 This is a follow-up fix to dotnet/android#11646, which made the same change in the `dotnet/android` repo. Updated `.github/workflows/android-tools-reviewer.md` to use `claude-opus-4.8` and recompiled the agentic workflow via `gh aw compile` so the `.lock.yml` is regenerated. Verified no `claude-opus-4.6` references remain in the repo. --- .../workflows/android-tools-reviewer.lock.yml | 22 +++++++++---------- .../workflows/android-tools-reviewer.md | 3 ++- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/external/xamarin-android-tools/.github/workflows/android-tools-reviewer.lock.yml b/external/xamarin-android-tools/.github/workflows/android-tools-reviewer.lock.yml index 516e3374e0d..aec2671d9bf 100644 --- a/external/xamarin-android-tools/.github/workflows/android-tools-reviewer.lock.yml +++ b/external/xamarin-android-tools/.github/workflows/android-tools-reviewer.lock.yml @@ -1,4 +1,4 @@ -# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"10f45ab5acbfd8f4152d6d465324cc535ddca73e66b888311f9198f9d09b2193","body_hash":"1b21a58fc25436cc68541bee79e0d82f2b44fb168383670638bc2cfc46f89483","compiler_version":"v0.79.6","strict":true,"agent_id":"copilot","agent_model":"claude-opus-4.6","engine_versions":{"copilot":"1.0.60"}} +# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"53e09e14c143bc03b90bcc9e309a72984f90170d8bd5d90479b7fd5e8716b5ed","body_hash":"1b21a58fc25436cc68541bee79e0d82f2b44fb168383670638bc2cfc46f89483","compiler_version":"v0.79.6","strict":true,"agent_id":"copilot","agent_model":"claude-opus-4.8","engine_versions":{"copilot":"1.0.60"}} # gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"df4cb1c069e1874edd31b4311f1884172cec0e10","version":"v6.0.3"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"5c2fe865bb4dc46e1450f6ee0d0541d759aea73a","version":"v0.79.6"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.2","digest":"sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.2@sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.2","digest":"sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.2@sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.2","digest":"sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.2@sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.25","digest":"sha256:c10331ad17668ef89f38f5e356678788a40b0cd5fef96e8f92e1d9c1de47cbaa","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.25@sha256:c10331ad17668ef89f38f5e356678788a40b0cd5fef96e8f92e1d9c1de47cbaa"},{"image":"ghcr.io/github/github-mcp-server:v1.1.2","digest":"sha256:30197479d8036c7811892bc07e06f9a05c9ef3cdd79bc59f256d50647f95788c","pinned_image":"ghcr.io/github/github-mcp-server:v1.1.2@sha256:30197479d8036c7811892bc07e06f9a05c9ef3cdd79bc59f256d50647f95788c"}]} # ___ _ _ # / _ \ | | (_) @@ -85,7 +85,6 @@ jobs: engine_id: ${{ steps.generate_aw_info.outputs.engine_id }} lockdown_check_failed: ${{ steps.generate_aw_info.outputs.lockdown_check_failed == 'true' }} model: ${{ steps.generate_aw_info.outputs.model }} - secret_verification_result: ${{ steps.validate-secret.outputs.verification_result }} setup-parent-span-id: ${{ steps.setup.outputs.parent-span-id || steps.setup.outputs.span-id }} setup-span-id: ${{ steps.setup.outputs.span-id }} setup-trace-id: ${{ steps.setup.outputs.trace-id }} @@ -114,7 +113,7 @@ jobs: env: GH_AW_INFO_ENGINE_ID: "copilot" GH_AW_INFO_ENGINE_NAME: "GitHub Copilot CLI" - GH_AW_INFO_MODEL: "claude-opus-4.6" + GH_AW_INFO_MODEL: "claude-opus-4.8" GH_AW_INFO_VERSION: "1.0.60" GH_AW_INFO_AGENT_VERSION: "1.0.60" GH_AW_INFO_CLI_VERSION: "v0.79.6" @@ -166,11 +165,6 @@ jobs: setupGlobals(core, github, context, exec, io, getOctokit); const { main } = require('${{ runner.temp }}/gh-aw/actions/add_reaction.cjs'); await main(); - - name: Validate COPILOT_GITHUB_TOKEN secret - id: validate-secret - run: bash "${RUNNER_TEMP}/gh-aw/actions/validate_multi_secret.sh" COPILOT_GITHUB_TOKEN 'GitHub Copilot CLI' https://github.github.com/gh-aw/reference/engines/#github-copilot-default - env: - COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} - name: Checkout .github and .agents folders uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: @@ -393,6 +387,7 @@ jobs: needs: activation if: needs.activation.outputs.daily_effective_workflow_exceeded != 'true' runs-on: ubuntu-latest + environment: copilot-pr-reviewer permissions: contents: read pull-requests: read @@ -863,7 +858,7 @@ jobs: COPILOT_AGENT_RUNNER_TYPE: STANDALONE COPILOT_DUMMY_BYOK: dummy-byok-key-for-offline-mode COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} - COPILOT_MODEL: claude-opus-4.6 + COPILOT_MODEL: claude-opus-4.8 GH_AW_MAX_TURNS: ${{ vars.GH_AW_DEFAULT_MAX_TURNS || '' }} GH_AW_MCP_CONFIG: /home/runner/.copilot/mcp-config.json GH_AW_PHASE: agent @@ -1058,6 +1053,7 @@ jobs: always() && (needs.agent.result != 'skipped' || needs.activation.outputs.lockdown_check_failed == 'true' || needs.activation.outputs.stale_lock_file_failed == 'true' || needs.activation.outputs.daily_effective_workflow_exceeded == 'true') runs-on: ubuntu-slim + environment: copilot-pr-reviewer permissions: contents: read pull-requests: write @@ -1215,7 +1211,6 @@ jobs: GH_AW_WORKFLOW_ID: "android-tools-reviewer" GH_AW_ACTION_FAILURE_ISSUE_EXPIRES_HOURS: "168" GH_AW_ENGINE_ID: "copilot" - GH_AW_SECRET_VERIFICATION_RESULT: ${{ needs.activation.outputs.secret_verification_result }} GH_AW_CHECKOUT_PR_SUCCESS: ${{ needs.agent.outputs.checkout_pr_success }} GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens || '' }} GH_AW_AI_CREDITS_RATE_LIMIT_ERROR: ${{ needs.agent.outputs.ai_credits_rate_limit_error || 'false' }} @@ -1273,6 +1268,7 @@ jobs: if: > always() && needs.agent.result != 'skipped' && (needs.agent.outputs.output_types != '' || needs.agent.outputs.has_patch == 'true') runs-on: ubuntu-latest + environment: copilot-pr-reviewer permissions: contents: read outputs: @@ -1431,7 +1427,7 @@ jobs: COPILOT_AGENT_RUNNER_TYPE: STANDALONE COPILOT_DUMMY_BYOK: dummy-byok-key-for-offline-mode COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} - COPILOT_MODEL: claude-opus-4.6 + COPILOT_MODEL: claude-opus-4.8 GH_AW_MAX_TURNS: ${{ vars.GH_AW_DEFAULT_MAX_TURNS || '' }} GH_AW_PHASE: detection GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt @@ -1507,6 +1503,7 @@ jobs: pre_activation: if: "(github.event_name != 'issue_comment' && github.event_name != 'pull_request_review_comment' || contains(fromJSON('[\"OWNER\",\"MEMBER\",\"COLLABORATOR\"]'), github.event.comment.author_association)) && (github.event_name == 'issue_comment' && (startsWith(github.event.comment.body, '/review ') || startsWith(github.event.comment.body, '/review\n') || github.event.comment.body == '/review') && github.event.issue.pull_request != null || !(github.event_name == 'issue_comment'))" runs-on: ubuntu-slim + environment: copilot-pr-reviewer outputs: activated: ${{ steps.check_membership.outputs.is_team_member == 'true' && steps.check_command_position.outputs.command_position_ok == 'true' }} matched_command: ${{ steps.check_command_position.outputs.matched_command }} @@ -1557,6 +1554,7 @@ jobs: - detection if: (!cancelled()) && needs.agent.result != 'skipped' && needs.detection.result == 'success' runs-on: ubuntu-slim + environment: copilot-pr-reviewer permissions: contents: read pull-requests: write @@ -1571,7 +1569,7 @@ jobs: GH_AW_DETECTION_REASON: ${{ needs.detection.outputs.detection_reason }} GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens }} GH_AW_ENGINE_ID: "copilot" - GH_AW_ENGINE_MODEL: "claude-opus-4.6" + GH_AW_ENGINE_MODEL: "claude-opus-4.8" GH_AW_ENGINE_VERSION: "1.0.60" GH_AW_THREAT_DETECTION_AIC: ${{ needs.detection.outputs.aic }} GH_AW_WORKFLOW_ID: "android-tools-reviewer" diff --git a/external/xamarin-android-tools/.github/workflows/android-tools-reviewer.md b/external/xamarin-android-tools/.github/workflows/android-tools-reviewer.md index b6e6545f32d..c75c2895eca 100644 --- a/external/xamarin-android-tools/.github/workflows/android-tools-reviewer.md +++ b/external/xamarin-android-tools/.github/workflows/android-tools-reviewer.md @@ -4,12 +4,13 @@ on: name: review events: [pull_request_comment] roles: [admin, maintainer, write] +environment: copilot-pr-reviewer permissions: contents: read pull-requests: read engine: id: copilot - model: claude-opus-4.6 + model: claude-opus-4.8 network: allowed: - defaults From 5f9b94b4efdf456d8935655f11d739d5076f4d65 Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Wed, 17 Jun 2026 09:35:03 -0500 Subject: [PATCH 302/308] Bump gh-aw to v0.79.8 and disable AIC guardrails on reviewer workflow (#390) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## What changed ### 1. Bump gh-aw CLI to v0.79.8 Regenerated lock files with the latest gh-aw CLI: - `.github/aw/actions-lock.json` - `.github/workflows/agentics-maintenance.yml` - `.github/workflows/android-tools-reviewer.lock.yml` The pinned `github/gh-aw-actions/setup` action moves from `v0.79.6` (`5c2fe86`) to `v0.79.8` (`c0338fe`). ### 2. Disable AIC guardrails on the reviewer workflow Added two lines to `.github/workflows/android-tools-reviewer.md`: ```yaml max-daily-ai-credits: -1 max-ai-credits: -1 ``` `-1` means unlimited / guardrail disabled. The system defaults (`5000`/day, `1000`/run) were tripping the `/review` workflow with `Daily workflow AIC guardrail exceeded`. After recompiling, the lock file shows `GH_AW_MAX_AI_CREDITS: "-1"` for the agent job and the `Check daily workflow token guardrail` plumbing drops out. The threat-detection job's separate `400` AIC default is left alone. ## Diff stat ``` .github/aw/actions-lock.json | 12 +-- .github/workflows/agentics-maintenance.yml | 43 +++++------ .github/workflows/android-tools-reviewer.lock.yml | 89 ++++++++--------------- .github/workflows/android-tools-reviewer.md | 2 + 4 files changed, 59 insertions(+), 87 deletions(-) ``` ## Out of scope Mirrors dotnet/java-interop#1471 + #1473. `COPILOT_GITHUB_TOKEN`, `ANDROID_TEAM_PAT`, `GH_AW_GITHUB_TOKEN`, and `GH_AW_GITHUB_MCP_SERVER_TOKEN` are untouched. Copilot CLI inference still uses the `COPILOT_GITHUB_TOKEN` PAT. The [`copilot-requests: write` opt-in](https://github.blog/changelog/2026-06-11-agentic-workflows-no-longer-need-a-personal-access-token/) for org-billed Copilot CLI inference is **not** adopted in this PR — the `dotnet` org has not enabled the required "Allow use of Copilot CLI billed to the organization" Copilot policy yet (dotnet/java-interop saw HTTP 403 in production after merging the equivalent change there). Can be revisited once that policy is enabled. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../.github/aw/actions-lock.json | 12 +-- .../workflows/agentics-maintenance.yml | 43 ++++----- .../workflows/android-tools-reviewer.lock.yml | 89 ++++++------------- .../workflows/android-tools-reviewer.md | 2 + 4 files changed, 59 insertions(+), 87 deletions(-) diff --git a/external/xamarin-android-tools/.github/aw/actions-lock.json b/external/xamarin-android-tools/.github/aw/actions-lock.json index 34cc08a154f..84e3231b543 100644 --- a/external/xamarin-android-tools/.github/aw/actions-lock.json +++ b/external/xamarin-android-tools/.github/aw/actions-lock.json @@ -5,15 +5,15 @@ "version": "v9.0.0", "sha": "3a2844b7e9c422d3c10d287c895573f7108da1b3" }, - "github/gh-aw-actions/setup-cli@v0.79.6": { + "github/gh-aw-actions/setup-cli@v0.79.8": { "repo": "github/gh-aw-actions/setup-cli", - "version": "v0.79.6", - "sha": "5c2fe865bb4dc46e1450f6ee0d0541d759aea73a" + "version": "v0.79.8", + "sha": "c0338fef4749d08c21f8f975fb0e37efa17dda47" }, - "github/gh-aw-actions/setup@v0.79.6": { + "github/gh-aw-actions/setup@v0.79.8": { "repo": "github/gh-aw-actions/setup", - "version": "v0.79.6", - "sha": "5c2fe865bb4dc46e1450f6ee0d0541d759aea73a" + "version": "v0.79.8", + "sha": "c0338fef4749d08c21f8f975fb0e37efa17dda47" } } } diff --git a/external/xamarin-android-tools/.github/workflows/agentics-maintenance.yml b/external/xamarin-android-tools/.github/workflows/agentics-maintenance.yml index 6bd015f59ae..dfd2eaf277c 100644 --- a/external/xamarin-android-tools/.github/workflows/agentics-maintenance.yml +++ b/external/xamarin-android-tools/.github/workflows/agentics-maintenance.yml @@ -1,3 +1,5 @@ +# This file was automatically generated by pkg/workflow/maintenance_workflow.go (v0.79.8). DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md +# # ___ _ _ # / _ \ | | (_) # | |_| | __ _ ___ _ __ | |_ _ ___ @@ -12,7 +14,6 @@ # \ /\ / (_) | | | | ( | | | | (_) \ V V /\__ \ # \/ \/ \___/|_| |_|\_\|_| |_|\___/ \_/\_/ |___/ # -# This file was automatically generated by pkg/workflow/maintenance_workflow.go (v0.79.6). DO NOT EDIT. # # To regenerate this workflow, run: # gh aw compile @@ -93,7 +94,7 @@ jobs: pull-requests: write steps: - name: Setup Scripts - uses: github/gh-aw-actions/setup@5c2fe865bb4dc46e1450f6ee0d0541d759aea73a # v0.79.6 + uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 with: destination: ${{ runner.temp }}/gh-aw/actions @@ -131,7 +132,7 @@ jobs: actions: write steps: - name: Setup Scripts - uses: github/gh-aw-actions/setup@5c2fe865bb4dc46e1450f6ee0d0541d759aea73a # v0.79.6 + uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 with: destination: ${{ runner.temp }}/gh-aw/actions @@ -160,7 +161,7 @@ jobs: persist-credentials: false - name: Setup Scripts - uses: github/gh-aw-actions/setup@5c2fe865bb4dc46e1450f6ee0d0541d759aea73a # v0.79.6 + uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 with: destination: ${{ runner.temp }}/gh-aw/actions @@ -175,9 +176,9 @@ jobs: await main(); - name: Install gh-aw - uses: github/gh-aw-actions/setup-cli@5c2fe865bb4dc46e1450f6ee0d0541d759aea73a # v0.79.6 + uses: github/gh-aw-actions/setup-cli@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 with: - version: v0.79.6 + version: v0.79.8 - name: Run operation uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -205,7 +206,7 @@ jobs: pull-requests: write steps: - name: Setup Scripts - uses: github/gh-aw-actions/setup@5c2fe865bb4dc46e1450f6ee0d0541d759aea73a # v0.79.6 + uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 with: destination: ${{ runner.temp }}/gh-aw/actions @@ -251,7 +252,7 @@ jobs: persist-credentials: false - name: Setup Scripts - uses: github/gh-aw-actions/setup@5c2fe865bb4dc46e1450f6ee0d0541d759aea73a # v0.79.6 + uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 with: destination: ${{ runner.temp }}/gh-aw/actions @@ -295,7 +296,7 @@ jobs: persist-credentials: false - name: Setup Scripts - uses: github/gh-aw-actions/setup@5c2fe865bb4dc46e1450f6ee0d0541d759aea73a # v0.79.6 + uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 with: destination: ${{ runner.temp }}/gh-aw/actions @@ -310,9 +311,9 @@ jobs: await main(); - name: Install gh-aw - uses: github/gh-aw-actions/setup-cli@5c2fe865bb4dc46e1450f6ee0d0541d759aea73a # v0.79.6 + uses: github/gh-aw-actions/setup-cli@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 with: - version: v0.79.6 + version: v0.79.8 - name: Create missing labels uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -341,7 +342,7 @@ jobs: persist-credentials: false - name: Setup Scripts - uses: github/gh-aw-actions/setup@5c2fe865bb4dc46e1450f6ee0d0541d759aea73a # v0.79.6 + uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 with: destination: ${{ runner.temp }}/gh-aw/actions @@ -356,9 +357,9 @@ jobs: await main(); - name: Install gh-aw - uses: github/gh-aw-actions/setup-cli@5c2fe865bb4dc46e1450f6ee0d0541d759aea73a # v0.79.6 + uses: github/gh-aw-actions/setup-cli@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 with: - version: v0.79.6 + version: v0.79.8 - name: Restore activity report logs cache id: activity_report_logs_cache @@ -446,7 +447,7 @@ jobs: persist-credentials: false - name: Setup Scripts - uses: github/gh-aw-actions/setup@5c2fe865bb4dc46e1450f6ee0d0541d759aea73a # v0.79.6 + uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 with: destination: ${{ runner.temp }}/gh-aw/actions @@ -461,9 +462,9 @@ jobs: await main(); - name: Install gh-aw - uses: github/gh-aw-actions/setup-cli@5c2fe865bb4dc46e1450f6ee0d0541d759aea73a # v0.79.6 + uses: github/gh-aw-actions/setup-cli@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 with: - version: v0.79.6 + version: v0.79.8 - name: Restore forecast report logs cache id: forecast_report_logs_cache @@ -538,7 +539,7 @@ jobs: issues: write steps: - name: Setup Scripts - uses: github/gh-aw-actions/setup@5c2fe865bb4dc46e1450f6ee0d0541d759aea73a # v0.79.6 + uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 with: destination: ${{ runner.temp }}/gh-aw/actions @@ -575,7 +576,7 @@ jobs: persist-credentials: false - name: Setup Scripts - uses: github/gh-aw-actions/setup@5c2fe865bb4dc46e1450f6ee0d0541d759aea73a # v0.79.6 + uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 with: destination: ${{ runner.temp }}/gh-aw/actions @@ -590,9 +591,9 @@ jobs: await main(); - name: Install gh-aw - uses: github/gh-aw-actions/setup-cli@5c2fe865bb4dc46e1450f6ee0d0541d759aea73a # v0.79.6 + uses: github/gh-aw-actions/setup-cli@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 with: - version: v0.79.6 + version: v0.79.8 - name: Validate workflows and file issue on findings uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 diff --git a/external/xamarin-android-tools/.github/workflows/android-tools-reviewer.lock.yml b/external/xamarin-android-tools/.github/workflows/android-tools-reviewer.lock.yml index aec2671d9bf..20cc98fcc58 100644 --- a/external/xamarin-android-tools/.github/workflows/android-tools-reviewer.lock.yml +++ b/external/xamarin-android-tools/.github/workflows/android-tools-reviewer.lock.yml @@ -1,5 +1,7 @@ -# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"53e09e14c143bc03b90bcc9e309a72984f90170d8bd5d90479b7fd5e8716b5ed","body_hash":"1b21a58fc25436cc68541bee79e0d82f2b44fb168383670638bc2cfc46f89483","compiler_version":"v0.79.6","strict":true,"agent_id":"copilot","agent_model":"claude-opus-4.8","engine_versions":{"copilot":"1.0.60"}} -# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"df4cb1c069e1874edd31b4311f1884172cec0e10","version":"v6.0.3"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"5c2fe865bb4dc46e1450f6ee0d0541d759aea73a","version":"v0.79.6"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.2","digest":"sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.2@sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.2","digest":"sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.2@sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.2","digest":"sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.2@sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.25","digest":"sha256:c10331ad17668ef89f38f5e356678788a40b0cd5fef96e8f92e1d9c1de47cbaa","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.25@sha256:c10331ad17668ef89f38f5e356678788a40b0cd5fef96e8f92e1d9c1de47cbaa"},{"image":"ghcr.io/github/github-mcp-server:v1.1.2","digest":"sha256:30197479d8036c7811892bc07e06f9a05c9ef3cdd79bc59f256d50647f95788c","pinned_image":"ghcr.io/github/github-mcp-server:v1.1.2@sha256:30197479d8036c7811892bc07e06f9a05c9ef3cdd79bc59f256d50647f95788c"}]} +# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"76c4e99bd1069b228569845faf61ee13e303c2806f57fbcbd2cc4a1c93f09a20","body_hash":"1b21a58fc25436cc68541bee79e0d82f2b44fb168383670638bc2cfc46f89483","compiler_version":"v0.79.8","strict":true,"agent_id":"copilot","agent_model":"claude-opus-4.8","engine_versions":{"copilot":"1.0.60"}} +# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"df4cb1c069e1874edd31b4311f1884172cec0e10","version":"v6.0.3"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"c0338fef4749d08c21f8f975fb0e37efa17dda47","version":"v0.79.8"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.2","digest":"sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.2@sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.2","digest":"sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.2@sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.2","digest":"sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.2@sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.25","digest":"sha256:c10331ad17668ef89f38f5e356678788a40b0cd5fef96e8f92e1d9c1de47cbaa","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.25@sha256:c10331ad17668ef89f38f5e356678788a40b0cd5fef96e8f92e1d9c1de47cbaa"},{"image":"ghcr.io/github/github-mcp-server:v1.1.2","digest":"sha256:30197479d8036c7811892bc07e06f9a05c9ef3cdd79bc59f256d50647f95788c","pinned_image":"ghcr.io/github/github-mcp-server:v1.1.2@sha256:30197479d8036c7811892bc07e06f9a05c9ef3cdd79bc59f256d50647f95788c"}]} +# This file was automatically generated by gh-aw (v0.79.8). DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md +# # ___ _ _ # / _ \ | | (_) # | |_| | __ _ ___ _ __ | |_ _ ___ @@ -14,7 +16,6 @@ # \ /\ / (_) | | | | ( | | | | (_) \ V V /\__ \ # \/ \/ \___/|_| |_|\_\|_| |_|\___/ \_/\_/ |___/ # -# This file was automatically generated by gh-aw (v0.79.6). DO NOT EDIT. # # To update this file, edit the corresponding .md file and run: # gh aw compile @@ -35,7 +36,7 @@ # - actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 # - actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 # - actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 -# - github/gh-aw-actions/setup@5c2fe865bb4dc46e1450f6ee0d0541d759aea73a # v0.79.6 +# - github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 # # Container images used: # - ghcr.io/github/gh-aw-firewall/agent:0.27.2@sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6 @@ -72,16 +73,11 @@ jobs: contents: read issues: write pull-requests: write - env: - GH_AW_MAX_DAILY_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_MAX_DAILY_AI_CREDITS || '5000' }} outputs: body: ${{ steps.sanitized.outputs.body }} comment_id: ${{ steps.add-comment.outputs.comment-id }} comment_repo: ${{ steps.add-comment.outputs.comment-repo }} comment_url: ${{ steps.add-comment.outputs.comment-url }} - daily_effective_workflow_exceeded: ${{ steps.daily-effective-workflow-guardrail.outputs.daily_effective_workflow_exceeded == 'true' }} - daily_effective_workflow_threshold: ${{ steps.daily-effective-workflow-guardrail.outputs.daily_effective_workflow_threshold || '' }} - daily_effective_workflow_total_effective_tokens: ${{ steps.daily-effective-workflow-guardrail.outputs.daily_effective_workflow_total_effective_tokens || '' }} engine_id: ${{ steps.generate_aw_info.outputs.engine_id }} lockdown_check_failed: ${{ steps.generate_aw_info.outputs.lockdown_check_failed == 'true' }} model: ${{ steps.generate_aw_info.outputs.model }} @@ -95,13 +91,12 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@5c2fe865bb4dc46e1450f6ee0d0541d759aea73a # v0.79.6 + uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} trace-id: ${{ needs.pre_activation.outputs.setup-trace-id }} parent-span-id: ${{ needs.pre_activation.outputs.setup-parent-span-id || needs.pre_activation.outputs.setup-span-id }} - safe-output-artifact-client: ${{ env.GH_AW_MAX_DAILY_AI_CREDITS != '' }} env: GH_AW_SETUP_WORKFLOW_NAME: "Android Tools PR Reviewer" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/android-tools-reviewer.lock.yml@${{ github.ref }} @@ -116,7 +111,7 @@ jobs: GH_AW_INFO_MODEL: "claude-opus-4.8" GH_AW_INFO_VERSION: "1.0.60" GH_AW_INFO_AGENT_VERSION: "1.0.60" - GH_AW_INFO_CLI_VERSION: "v0.79.6" + GH_AW_INFO_CLI_VERSION: "v0.79.8" GH_AW_INFO_WORKFLOW_NAME: "Android Tools PR Reviewer" GH_AW_INFO_EXPERIMENTAL: "false" GH_AW_INFO_SUPPORTS_TOOLS_ALLOWLIST: "true" @@ -134,24 +129,6 @@ jobs: setupGlobals(core, github, context, exec, io, getOctokit); const { main } = require('${{ runner.temp }}/gh-aw/actions/generate_aw_info.cjs'); await main(core, context); - - name: Check daily workflow token guardrail - id: daily-effective-workflow-guardrail - if: ${{ env.GH_AW_MAX_DAILY_AI_CREDITS != '' }} - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - env: - GH_AW_WORKFLOW_NAME: "Android Tools PR Reviewer" - GH_AW_WORKFLOW_ID: "android-tools-reviewer" - GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} - GH_AW_WORKFLOW_DISPATCH_AW_CONTEXT: ${{ github.event.inputs.aw_context || '' }} - GH_AW_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - GH_AW_MAX_DAILY_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_MAX_DAILY_AI_CREDITS || '5000' }} - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/check_daily_aic_workflow_guardrail.cjs'); - await main(); - name: Add eyes reaction for immediate feedback id: react if: github.event_name == 'issues' || github.event_name == 'issue_comment' || github.event_name == 'pull_request_review_comment' || github.event_name == 'discussion' || github.event_name == 'discussion_comment' || github.event_name == 'pull_request' && github.event.pull_request.head.repo.id == github.repository_id @@ -202,7 +179,7 @@ jobs: - name: Check compile-agentic version uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: - GH_AW_COMPILED_VERSION: "v0.79.6" + GH_AW_COMPILED_VERSION: "v0.79.8" with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); @@ -371,7 +348,6 @@ jobs: include-hidden-files: true path: | /tmp/gh-aw/aw_info.json - /tmp/gh-aw/model_multipliers.json /tmp/gh-aw/models.json /tmp/gh-aw/aw-prompts/prompt.txt /tmp/gh-aw/aw-prompts/prompt-template.txt @@ -385,7 +361,6 @@ jobs: agent: needs: activation - if: needs.activation.outputs.daily_effective_workflow_exceeded != 'true' runs-on: ubuntu-latest environment: copilot-pr-reviewer permissions: @@ -419,7 +394,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@5c2fe865bb4dc46e1450f6ee0d0541d759aea73a # v0.79.6 + uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -749,7 +724,7 @@ jobs: DOCKER_SOCK_GID=$(stat -c '%g' "$DOCKER_SOCK_PATH" 2>/dev/null || echo '0') export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host --add-host host.docker.internal:127.0.0.1 --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v '"${DOCKER_SOCK_PATH}"':/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DOCKER_HOST=unix:///var/run/docker.sock -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/github/gh-aw-mcpg:v0.3.25' - mkdir -p /home/runner/.copilot + mkdir -p "$HOME/.copilot" GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node) cat << GH_AW_MCP_CONFIG_5df4e2192112624c_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" { @@ -824,17 +799,17 @@ jobs: run: | set -o pipefail printf '%s' "$(date +%s%3N)" > /tmp/gh-aw/agent_cli_start_ms.txt - trap 'rm -f /home/runner/.copilot/settings.json' EXIT - mkdir -p /home/runner/.copilot - printf '%s' '{"builtInAgents":{"rubberDuck":false}}' > /home/runner/.copilot/settings.json + trap 'rm -f "$HOME/.copilot/settings.json"' EXIT + mkdir -p "$HOME/.copilot" + printf '%s' '{"builtInAgents":{"rubberDuck":false}}' > "$HOME/.copilot/settings.json" + export XDG_CONFIG_HOME="$HOME" + export GH_AW_MCP_CONFIG="$HOME/.copilot/mcp-config.json" touch /tmp/gh-aw/agent-step-summary.md GH_AW_NODE_BIN=$(command -v node 2>/dev/null || true) export GH_AW_NODE_BIN export COPILOT_API_KEY="$COPILOT_DUMMY_BYOK" (umask 177 && touch /tmp/gh-aw/agent-stdio.log) - GH_AW_MAX_AI_CREDITS="${{ vars.GH_AW_DEFAULT_MAX_AI_CREDITS || '1000' }}" - printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.2/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"*.githubusercontent.com\",\"*.vsblob.vsassets.io\",\"aka.ms\",\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"api.nuget.org\",\"api.snapcraft.io\",\"archive.ubuntu.com\",\"azure.archive.ubuntu.com\",\"azuresearch-usnc.nuget.org\",\"azuresearch-ussc.nuget.org\",\"builds.dotnet.microsoft.com\",\"ci.dot.net\",\"codeload.github.com\",\"crl.geotrust.com\",\"crl.globalsign.com\",\"crl.identrust.com\",\"crl.sectigo.com\",\"crl.thawte.com\",\"crl.usertrust.com\",\"crl.verisign.com\",\"crl3.digicert.com\",\"crl4.digicert.com\",\"crls.ssl.com\",\"dc.services.visualstudio.com\",\"dist.nuget.org\",\"docs.github.com\",\"dot.net\",\"dotnet.microsoft.com\",\"dotnetcli.blob.core.windows.net\",\"github-cloud.githubusercontent.com\",\"github-cloud.s3.amazonaws.com\",\"github.blog\",\"github.com\",\"github.githubassets.com\",\"host.docker.internal\",\"json-schema.org\",\"json.schemastore.org\",\"keyserver.ubuntu.com\",\"lfs.github.com\",\"microsoft.com\",\"nuget.org\",\"nuget.pkg.github.com\",\"nugetregistryv2prod.blob.core.windows.net\",\"objects.githubusercontent.com\",\"ocsp.digicert.com\",\"ocsp.geotrust.com\",\"ocsp.globalsign.com\",\"ocsp.identrust.com\",\"ocsp.sectigo.com\",\"ocsp.ssl.com\",\"ocsp.thawte.com\",\"ocsp.usertrust.com\",\"ocsp.verisign.com\",\"oneocsp.microsoft.com\",\"packagecloud.io\",\"packages.cloud.google.com\",\"packages.microsoft.com\",\"patch-diff.githubusercontent.com\",\"pkgs.dev.azure.com\",\"ppa.launchpad.net\",\"raw.githubusercontent.com\",\"registry.npmjs.org\",\"s.symcb.com\",\"s.symcd.com\",\"security.ubuntu.com\",\"telemetry.enterprise.githubcopilot.com\",\"ts-crl.ws.symantec.com\",\"ts-ocsp.ws.symantec.com\",\"www.googleapis.com\",\"www.microsoft.com\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"models\":{\"agent\":[\"sonnet-6x\",\"gpt-5.4\",\"gpt-5.3\",\"gemini-pro\",\"any\"],\"antigravity\":[\"copilot/antigravity*\",\"google/antigravity*\",\"gemini/antigravity*\"],\"any\":[\"copilot/*\",\"anthropic/*\",\"openai/*\",\"google/*\",\"gemini/*\"],\"claude\":[\"agent\"],\"codex\":[\"agent\"],\"coding\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\",\"gpt-5-codex\"],\"computer-use\":[\"copilot/*computer-use*\",\"google/*computer-use*\",\"gemini/*computer-use*\",\"openai/*computer-use*\"],\"copilot\":[\"agent\"],\"deep-research\":[\"copilot/deep-research*\",\"copilot/o3-deep-research*\",\"copilot/o4-mini-deep-research*\",\"google/deep-research*\",\"gemini/deep-research*\",\"openai/o3-deep-research*\",\"openai/o4-mini-deep-research*\"],\"gemini\":[\"agent\"],\"gemini-3-flash\":[\"copilot/gemini-3*flash*\",\"google/gemini-3*flash*\",\"gemini/gemini-3*flash*\"],\"gemini-3-pro\":[\"copilot/gemini-3*pro*\",\"google/gemini-3*pro*\",\"google/nano-banana*\",\"gemini/gemini-3*pro*\"],\"gemini-3.1-flash\":[\"copilot/gemini-3.1*flash*\",\"google/gemini-3.1*flash*\",\"gemini/gemini-3.1*flash*\"],\"gemini-3.1-pro\":[\"copilot/gemini-3.1*pro*\",\"google/gemini-3.1*pro*\",\"gemini/gemini-3.1*pro*\"],\"gemini-3.5-flash\":[\"copilot/gemini-3.5*flash*\",\"google/gemini-3.5*flash*\",\"gemini/gemini-3.5*flash*\"],\"gemini-flash\":[\"copilot/gemini-*flash*\",\"google/gemini-*flash*\",\"gemini/gemini-*flash*\"],\"gemini-flash-lite\":[\"copilot/gemini-*flash*lite*\",\"google/gemini-*flash*lite*\",\"gemini/gemini-*flash*lite*\"],\"gemini-pro\":[\"copilot/gemini-*pro*\",\"google/gemini-*pro*\",\"gemini/gemini-*pro*\"],\"gemma\":[\"copilot/gemma*\",\"google/gemma*\",\"gemini/gemma*\"],\"gpt-5\":[\"copilot/gpt-5*\",\"openai/gpt-5*\"],\"gpt-5-codex\":[\"copilot/gpt-5*codex*\",\"openai/gpt-5*codex*\"],\"gpt-5-mini\":[\"copilot/gpt-5*mini*\",\"openai/gpt-5*mini*\"],\"gpt-5-nano\":[\"copilot/gpt-5*nano*\",\"openai/gpt-5*nano*\"],\"gpt-5-pro\":[\"copilot/gpt-5*pro*\",\"openai/gpt-5*pro*\"],\"gpt-5.2\":[\"copilot/gpt-5.2*\",\"openai/gpt-5.2*\"],\"gpt-5.3\":[\"copilot/gpt-5.3*\",\"openai/gpt-5.3*\"],\"gpt-5.4\":[\"copilot/gpt-5.4*\",\"openai/gpt-5.4*\"],\"gpt-5.5\":[\"copilot/gpt-5.5*\",\"openai/gpt-5.5*\"],\"haiku\":[\"copilot/*haiku*\",\"anthropic/*haiku*\"],\"large\":[\"sonnet\",\"gpt-5-pro\",\"gpt-5\",\"gemini-pro\"],\"mai-code\":[\"copilot/MAI-Code*\",\"copilot/mai-code*\",\"openai/MAI-Code*\"],\"mini\":[\"haiku\",\"gpt-5-mini\",\"gpt-5-nano\",\"gemini-flash-lite\"],\"nano-banana\":[\"copilot/nano-banana*\",\"google/nano-banana*\",\"gemini/nano-banana*\"],\"opus\":[\"copilot/*opus*\",\"anthropic/*opus*\"],\"opusplan\":[\"opus?effort=high\"],\"reasoning\":[\"copilot/o1*\",\"copilot/o3*\",\"copilot/o4*\",\"openai/o1*\",\"openai/o3*\",\"openai/o4*\"],\"robotics\":[\"copilot/*robotics*\",\"google/*robotics*\",\"gemini/*robotics*\"],\"small\":[\"mini\"],\"small-agent\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash\"],\"sonnet\":[\"copilot/*sonnet*\",\"anthropic/*sonnet*\"],\"sonnet-6x\":[\"copilot/*sonnet-4.5*\",\"copilot/*sonnet-4.6*\",\"copilot/*sonnet-4-5-*\",\"anthropic/*sonnet-4-5-*\",\"copilot/*sonnet-4-6*\",\"anthropic/*sonnet-4-6*\"],\"summarization\":[\"haiku\",\"gpt-5-mini\",\"gemini-flash-lite\",\"mini\"],\"vision\":[\"copilot/gemini-*image*\",\"gemini/gemini-*image*\",\"copilot/gemini-*flash*\",\"gemini/gemini-*flash*\"]}},\"container\":{\"imageTag\":\"0.27.2,squid=sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591,agent=sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6,api-proxy=sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4,cli-proxy=sha256:02f3ec08f32dc26c5427920c6a2e2f3036238fce44802f2f11ef49ed8621b5d0\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json" - GH_AW_MODEL_MULTIPLIERS_PATH="/tmp/gh-aw/model_multipliers.json" node "${RUNNER_TEMP}/gh-aw/actions/merge_awf_model_multipliers.cjs" + printf '%s\n' '{"$schema":"https://github.com/github/gh-aw-firewall/releases/download/v0.27.2/awf-config.schema.json","network":{"allowDomains":["*.githubusercontent.com","*.vsblob.vsassets.io","aka.ms","api.business.githubcopilot.com","api.enterprise.githubcopilot.com","api.github.com","api.githubcopilot.com","api.individual.githubcopilot.com","api.nuget.org","api.snapcraft.io","archive.ubuntu.com","azure.archive.ubuntu.com","azuresearch-usnc.nuget.org","azuresearch-ussc.nuget.org","builds.dotnet.microsoft.com","ci.dot.net","codeload.github.com","crl.geotrust.com","crl.globalsign.com","crl.identrust.com","crl.sectigo.com","crl.thawte.com","crl.usertrust.com","crl.verisign.com","crl3.digicert.com","crl4.digicert.com","crls.ssl.com","dc.services.visualstudio.com","dist.nuget.org","docs.github.com","dot.net","dotnet.microsoft.com","dotnetcli.blob.core.windows.net","github-cloud.githubusercontent.com","github-cloud.s3.amazonaws.com","github.blog","github.com","github.githubassets.com","host.docker.internal","json-schema.org","json.schemastore.org","keyserver.ubuntu.com","lfs.github.com","microsoft.com","nuget.org","nuget.pkg.github.com","nugetregistryv2prod.blob.core.windows.net","objects.githubusercontent.com","ocsp.digicert.com","ocsp.geotrust.com","ocsp.globalsign.com","ocsp.identrust.com","ocsp.sectigo.com","ocsp.ssl.com","ocsp.thawte.com","ocsp.usertrust.com","ocsp.verisign.com","oneocsp.microsoft.com","packagecloud.io","packages.cloud.google.com","packages.microsoft.com","patch-diff.githubusercontent.com","pkgs.dev.azure.com","ppa.launchpad.net","raw.githubusercontent.com","registry.npmjs.org","s.symcb.com","s.symcd.com","security.ubuntu.com","telemetry.enterprise.githubcopilot.com","ts-crl.ws.symantec.com","ts-ocsp.ws.symantec.com","www.googleapis.com","www.microsoft.com"]},"apiProxy":{"enabled":true,"maxRuns":500,"models":{"agent":["sonnet-6x","gpt-5.4","gpt-5.3","gemini-pro","any"],"antigravity":["copilot/antigravity*","google/antigravity*","gemini/antigravity*"],"any":["copilot/*","anthropic/*","openai/*","google/*","gemini/*"],"claude":["agent"],"codex":["agent"],"coding":["copilot/gpt-5*codex*","openai/gpt-5*codex*","gpt-5-codex"],"computer-use":["copilot/*computer-use*","google/*computer-use*","gemini/*computer-use*","openai/*computer-use*"],"copilot":["agent"],"deep-research":["copilot/deep-research*","copilot/o3-deep-research*","copilot/o4-mini-deep-research*","google/deep-research*","gemini/deep-research*","openai/o3-deep-research*","openai/o4-mini-deep-research*"],"gemini":["agent"],"gemini-3-flash":["copilot/gemini-3*flash*","google/gemini-3*flash*","gemini/gemini-3*flash*"],"gemini-3-pro":["copilot/gemini-3*pro*","google/gemini-3*pro*","google/nano-banana*","gemini/gemini-3*pro*"],"gemini-3.1-flash":["copilot/gemini-3.1*flash*","google/gemini-3.1*flash*","gemini/gemini-3.1*flash*"],"gemini-3.1-pro":["copilot/gemini-3.1*pro*","google/gemini-3.1*pro*","gemini/gemini-3.1*pro*"],"gemini-3.5-flash":["copilot/gemini-3.5*flash*","google/gemini-3.5*flash*","gemini/gemini-3.5*flash*"],"gemini-flash":["copilot/gemini-*flash*","google/gemini-*flash*","gemini/gemini-*flash*"],"gemini-flash-lite":["copilot/gemini-*flash*lite*","google/gemini-*flash*lite*","gemini/gemini-*flash*lite*"],"gemini-pro":["copilot/gemini-*pro*","google/gemini-*pro*","gemini/gemini-*pro*"],"gemma":["copilot/gemma*","google/gemma*","gemini/gemma*"],"gpt-5":["copilot/gpt-5*","openai/gpt-5*"],"gpt-5-codex":["copilot/gpt-5*codex*","openai/gpt-5*codex*"],"gpt-5-mini":["copilot/gpt-5*mini*","openai/gpt-5*mini*"],"gpt-5-nano":["copilot/gpt-5*nano*","openai/gpt-5*nano*"],"gpt-5-pro":["copilot/gpt-5*pro*","openai/gpt-5*pro*"],"gpt-5.2":["copilot/gpt-5.2*","openai/gpt-5.2*"],"gpt-5.3":["copilot/gpt-5.3*","openai/gpt-5.3*"],"gpt-5.4":["copilot/gpt-5.4*","openai/gpt-5.4*"],"gpt-5.5":["copilot/gpt-5.5*","openai/gpt-5.5*"],"haiku":["copilot/*haiku*","anthropic/*haiku*"],"large":["sonnet","gpt-5-pro","gpt-5","gemini-pro"],"mai-code":["copilot/MAI-Code*","copilot/mai-code*","openai/MAI-Code*"],"mini":["haiku","gpt-5-mini","gpt-5-nano","gemini-flash-lite"],"nano-banana":["copilot/nano-banana*","google/nano-banana*","gemini/nano-banana*"],"opus":["copilot/*opus*","anthropic/*opus*"],"opusplan":["opus?effort=high"],"reasoning":["copilot/o1*","copilot/o3*","copilot/o4*","openai/o1*","openai/o3*","openai/o4*"],"robotics":["copilot/*robotics*","google/*robotics*","gemini/*robotics*"],"small":["mini"],"small-agent":["haiku","gpt-5-mini","gemini-flash"],"sonnet":["copilot/*sonnet*","anthropic/*sonnet*"],"sonnet-6x":["copilot/*sonnet-4.5*","copilot/*sonnet-4.6*","copilot/*sonnet-4-5-*","anthropic/*sonnet-4-5-*","copilot/*sonnet-4-6*","anthropic/*sonnet-4-6*"],"summarization":["haiku","gpt-5-mini","gemini-flash-lite","mini"],"vision":["copilot/gemini-*image*","gemini/gemini-*image*","copilot/gemini-*flash*","gemini/gemini-*flash*"]}},"container":{"imageTag":"0.27.2,squid=sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591,agent=sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6,api-proxy=sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4,cli-proxy=sha256:02f3ec08f32dc26c5427920c6a2e2f3036238fce44802f2f11ef49ed8621b5d0"}}' > "${RUNNER_TEMP}/gh-aw/awf-config.json" cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json export GH_AW_MODELS_JSON_PATH="/tmp/gh-aw/models.json" GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="" @@ -860,12 +835,11 @@ jobs: COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} COPILOT_MODEL: claude-opus-4.8 GH_AW_MAX_TURNS: ${{ vars.GH_AW_DEFAULT_MAX_TURNS || '' }} - GH_AW_MCP_CONFIG: /home/runner/.copilot/mcp-config.json GH_AW_PHASE: agent GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} GH_AW_TIMEOUT_MINUTES: 20 - GH_AW_VERSION: v0.79.6 + GH_AW_VERSION: v0.79.8 GITHUB_API_URL: ${{ github.api_url }} GITHUB_AW: true GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows @@ -880,7 +854,6 @@ jobs: GIT_COMMITTER_EMAIL: github-actions[bot]@users.noreply.github.com GIT_COMMITTER_NAME: github-actions[bot] RUNNER_TEMP: ${{ runner.temp }} - XDG_CONFIG_HOME: /home/runner - name: Detect agent errors if: always() id: detect-agent-errors @@ -1051,7 +1024,7 @@ jobs: - safe_outputs if: > always() && (needs.agent.result != 'skipped' || needs.activation.outputs.lockdown_check_failed == 'true' || - needs.activation.outputs.stale_lock_file_failed == 'true' || needs.activation.outputs.daily_effective_workflow_exceeded == 'true') + needs.activation.outputs.stale_lock_file_failed == 'true') runs-on: ubuntu-slim environment: copilot-pr-reviewer permissions: @@ -1069,7 +1042,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@5c2fe865bb4dc46e1450f6ee0d0541d759aea73a # v0.79.6 + uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -1217,7 +1190,7 @@ jobs: GH_AW_UNKNOWN_MODEL_AI_CREDITS: ${{ needs.agent.outputs.unknown_model_ai_credits || 'false' }} GH_AW_AIC: ${{ needs.agent.outputs.aic }} GH_AW_THREAT_DETECTION_AIC: ${{ needs.detection.outputs.aic }} - GH_AW_MAX_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_MAX_AI_CREDITS || '1000' }} + GH_AW_MAX_AI_CREDITS: "-1" GH_AW_INFERENCE_ACCESS_ERROR: ${{ needs.agent.outputs.inference_access_error }} GH_AW_MCP_POLICY_ERROR: ${{ needs.agent.outputs.mcp_policy_error }} GH_AW_AGENTIC_ENGINE_TIMEOUT: ${{ needs.agent.outputs.agentic_engine_timeout }} @@ -1225,9 +1198,6 @@ jobs: GH_AW_ENGINE_API_HOSTS: "api.enterprise.githubcopilot.com,api.githubcopilot.com,api.business.githubcopilot.com,api.individual.githubcopilot.com" GH_AW_LOCKDOWN_CHECK_FAILED: ${{ needs.activation.outputs.lockdown_check_failed }} GH_AW_STALE_LOCK_FILE_FAILED: ${{ needs.activation.outputs.stale_lock_file_failed }} - GH_AW_DAILY_EFFECTIVE_WORKFLOW_EXCEEDED: ${{ needs.activation.outputs.daily_effective_workflow_exceeded }} - GH_AW_DAILY_EFFECTIVE_WORKFLOW_TOTAL_EFFECTIVE_TOKENS: ${{ needs.activation.outputs.daily_effective_workflow_total_effective_tokens }} - GH_AW_DAILY_EFFECTIVE_WORKFLOW_THRESHOLD: ${{ needs.activation.outputs.daily_effective_workflow_threshold }} GH_AW_GROUP_REPORTS: "false" GH_AW_FAILURE_REPORT_AS_ISSUE: "true" GH_AW_MISSING_TOOL_REPORT_AS_FAILURE: "true" @@ -1279,7 +1249,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@5c2fe865bb4dc46e1450f6ee0d0541d759aea73a # v0.79.6 + uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -1335,7 +1305,7 @@ jobs: if: always() && steps.detection_guard.outputs.run_detection == 'true' run: | rm -f "${RUNNER_TEMP}/gh-aw/mcp-config/mcp-servers.json" - rm -f /home/runner/.copilot/mcp-config.json + rm -f "$HOME/.copilot/mcp-config.json" rm -f "$GITHUB_WORKSPACE/.gemini/settings.json" - name: Prepare threat detection files if: always() && steps.detection_guard.outputs.run_detection == 'true' @@ -1393,9 +1363,10 @@ jobs: run: | set -o pipefail printf '%s' "$(date +%s%3N)" > /tmp/gh-aw/agent_cli_start_ms.txt - trap 'rm -f /home/runner/.copilot/settings.json' EXIT - mkdir -p /home/runner/.copilot - printf '%s' '{"builtInAgents":{"rubberDuck":false}}' > /home/runner/.copilot/settings.json + trap 'rm -f "$HOME/.copilot/settings.json"' EXIT + mkdir -p "$HOME/.copilot" + printf '%s' '{"builtInAgents":{"rubberDuck":false}}' > "$HOME/.copilot/settings.json" + export XDG_CONFIG_HOME="$HOME" touch /tmp/gh-aw/agent-step-summary.md GH_AW_NODE_BIN=$(command -v node 2>/dev/null || true) export GH_AW_NODE_BIN @@ -1403,7 +1374,6 @@ jobs: (umask 177 && touch /tmp/gh-aw/threat-detection/detection.log) GH_AW_MAX_AI_CREDITS="${{ vars.GH_AW_DEFAULT_DETECTION_MAX_AI_CREDITS || '400' }}" printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.2/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"github.com\",\"host.docker.internal\",\"registry.npmjs.org\",\"telemetry.enterprise.githubcopilot.com\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS}},\"container\":{\"imageTag\":\"0.27.2,squid=sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591,agent=sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6,api-proxy=sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4,cli-proxy=sha256:02f3ec08f32dc26c5427920c6a2e2f3036238fce44802f2f11ef49ed8621b5d0\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json" - GH_AW_MODEL_MULTIPLIERS_PATH="/tmp/gh-aw/model_multipliers.json" node "${RUNNER_TEMP}/gh-aw/actions/merge_awf_model_multipliers.cjs" cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json export GH_AW_MODELS_JSON_PATH="/tmp/gh-aw/models.json" GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="" @@ -1432,7 +1402,7 @@ jobs: GH_AW_PHASE: detection GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt GH_AW_TIMEOUT_MINUTES: 20 - GH_AW_VERSION: v0.79.6 + GH_AW_VERSION: v0.79.8 GITHUB_API_URL: ${{ github.api_url }} GITHUB_AW: true GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows @@ -1446,7 +1416,6 @@ jobs: GIT_COMMITTER_EMAIL: github-actions[bot]@users.noreply.github.com GIT_COMMITTER_NAME: github-actions[bot] RUNNER_TEMP: ${{ runner.temp }} - XDG_CONFIG_HOME: /home/runner - name: Parse threat detection token usage for step summary id: parse_detection_token_usage if: always() @@ -1513,7 +1482,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@5c2fe865bb4dc46e1450f6ee0d0541d759aea73a # v0.79.6 + uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -1585,7 +1554,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@5c2fe865bb4dc46e1450f6ee0d0541d759aea73a # v0.79.6 + uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} diff --git a/external/xamarin-android-tools/.github/workflows/android-tools-reviewer.md b/external/xamarin-android-tools/.github/workflows/android-tools-reviewer.md index c75c2895eca..758171bc10a 100644 --- a/external/xamarin-android-tools/.github/workflows/android-tools-reviewer.md +++ b/external/xamarin-android-tools/.github/workflows/android-tools-reviewer.md @@ -11,6 +11,8 @@ permissions: engine: id: copilot model: claude-opus-4.8 +max-daily-ai-credits: -1 +max-ai-credits: -1 network: allowed: - defaults From fca3a7705f234fedde0bac66d53d84815e0e936a Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Wed, 17 Jun 2026 14:03:39 -0500 Subject: [PATCH 303/308] Scope gh-aw GitHub tokens to suppress magic fallback secret names (#391) Add explicit `github-token: ${{ secrets.GITHUB_TOKEN }}` under both `tools.github` and `safe-outputs` in the gh-aw frontmatter. This eliminates `GH_AW_GITHUB_TOKEN` and `GH_AW_GITHUB_MCP_SERVER_TOKEN` from the compiled lock file's `Secrets used:` block, which a repo secret-audit was flagging by name reference. Matches the change merged in dotnet/java-interop#1475 and dotnet/android (same gh-aw compiler version v0.79.8). Recompiled with `gh aw compile` (0 errors, 0 warnings). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../workflows/android-tools-reviewer.lock.yml | 32 ++++++++----------- .../workflows/android-tools-reviewer.md | 2 ++ 2 files changed, 16 insertions(+), 18 deletions(-) diff --git a/external/xamarin-android-tools/.github/workflows/android-tools-reviewer.lock.yml b/external/xamarin-android-tools/.github/workflows/android-tools-reviewer.lock.yml index 20cc98fcc58..8af8af9327a 100644 --- a/external/xamarin-android-tools/.github/workflows/android-tools-reviewer.lock.yml +++ b/external/xamarin-android-tools/.github/workflows/android-tools-reviewer.lock.yml @@ -1,5 +1,5 @@ -# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"76c4e99bd1069b228569845faf61ee13e303c2806f57fbcbd2cc4a1c93f09a20","body_hash":"1b21a58fc25436cc68541bee79e0d82f2b44fb168383670638bc2cfc46f89483","compiler_version":"v0.79.8","strict":true,"agent_id":"copilot","agent_model":"claude-opus-4.8","engine_versions":{"copilot":"1.0.60"}} -# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"df4cb1c069e1874edd31b4311f1884172cec0e10","version":"v6.0.3"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"c0338fef4749d08c21f8f975fb0e37efa17dda47","version":"v0.79.8"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.2","digest":"sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.2@sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.2","digest":"sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.2@sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.2","digest":"sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.2@sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.25","digest":"sha256:c10331ad17668ef89f38f5e356678788a40b0cd5fef96e8f92e1d9c1de47cbaa","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.25@sha256:c10331ad17668ef89f38f5e356678788a40b0cd5fef96e8f92e1d9c1de47cbaa"},{"image":"ghcr.io/github/github-mcp-server:v1.1.2","digest":"sha256:30197479d8036c7811892bc07e06f9a05c9ef3cdd79bc59f256d50647f95788c","pinned_image":"ghcr.io/github/github-mcp-server:v1.1.2@sha256:30197479d8036c7811892bc07e06f9a05c9ef3cdd79bc59f256d50647f95788c"}]} +# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"c3396efe8513752ad6979d99d949c90fba2958ee9e30eac19a6a02762b1313ea","body_hash":"1b21a58fc25436cc68541bee79e0d82f2b44fb168383670638bc2cfc46f89483","compiler_version":"v0.79.8","strict":true,"agent_id":"copilot","agent_model":"claude-opus-4.8","engine_versions":{"copilot":"1.0.60"}} +# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"df4cb1c069e1874edd31b4311f1884172cec0e10","version":"v6.0.3"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"c0338fef4749d08c21f8f975fb0e37efa17dda47","version":"v0.79.8"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.2","digest":"sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.2@sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.2","digest":"sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.2@sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.2","digest":"sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.2@sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.25","digest":"sha256:c10331ad17668ef89f38f5e356678788a40b0cd5fef96e8f92e1d9c1de47cbaa","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.25@sha256:c10331ad17668ef89f38f5e356678788a40b0cd5fef96e8f92e1d9c1de47cbaa"},{"image":"ghcr.io/github/github-mcp-server:v1.1.2","digest":"sha256:30197479d8036c7811892bc07e06f9a05c9ef3cdd79bc59f256d50647f95788c","pinned_image":"ghcr.io/github/github-mcp-server:v1.1.2@sha256:30197479d8036c7811892bc07e06f9a05c9ef3cdd79bc59f256d50647f95788c"}]} # This file was automatically generated by gh-aw (v0.79.8). DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md # # ___ _ _ @@ -26,8 +26,6 @@ # # Secrets used: # - COPILOT_GITHUB_TOKEN -# - GH_AW_GITHUB_MCP_SERVER_TOKEN -# - GH_AW_GITHUB_TOKEN # - GITHUB_TOKEN # # Custom actions used: @@ -443,9 +441,9 @@ jobs: github.event.pull_request || github.event.issue.pull_request || github.event_name == 'workflow_dispatch' && fromJSON(github.event.inputs.aw_context || '{}').item_type == 'pull_request' uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: - GH_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: - github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + github-token: ${{ secrets.GITHUB_TOKEN }} script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); setupGlobals(core, github, context, exec, io, getOctokit); @@ -696,7 +694,7 @@ jobs: GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-start.outputs.api_key }} GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-start.outputs.port }} - GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | set -eo pipefail mkdir -p "${RUNNER_TEMP}/gh-aw/mcp-config" @@ -844,7 +842,7 @@ jobs: GITHUB_AW: true GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows GITHUB_HEAD_REF: ${{ github.head_ref }} - GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_REF_NAME: ${{ github.ref_name }} GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_STEP_SUMMARY: /tmp/gh-aw/agent-step-summary.md @@ -895,10 +893,8 @@ jobs: const { main } = require('${{ runner.temp }}/gh-aw/actions/redact_secrets.cjs'); await main(); env: - GH_AW_SECRET_NAMES: 'COPILOT_GITHUB_TOKEN,GH_AW_GITHUB_MCP_SERVER_TOKEN,GH_AW_GITHUB_TOKEN,GITHUB_TOKEN' + GH_AW_SECRET_NAMES: 'COPILOT_GITHUB_TOKEN,GITHUB_TOKEN' SECRET_COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} - SECRET_GH_AW_GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }} - SECRET_GH_AW_GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }} SECRET_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Append agent step summary if: always() @@ -1118,7 +1114,7 @@ jobs: GH_AW_AMBIENT_CONTEXT: ${{ needs.agent.outputs.ambient_context }} GH_AW_WORKFLOW_ID: "android-tools-reviewer" with: - github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + github-token: ${{ secrets.GITHUB_TOKEN }} script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); setupGlobals(core, github, context, exec, io, getOctokit); @@ -1135,7 +1131,7 @@ jobs: GH_AW_DETECTION_CONCLUSION: ${{ needs.detection.outputs.detection_conclusion }} GH_AW_DETECTION_REASON: ${{ needs.detection.outputs.detection_reason }} with: - github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + github-token: ${{ secrets.GITHUB_TOKEN }} script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); setupGlobals(core, github, context, exec, io, getOctokit); @@ -1150,7 +1146,7 @@ jobs: GH_AW_WORKFLOW_NAME: "Android Tools PR Reviewer" GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/android-tools-reviewer.md" with: - github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + github-token: ${{ secrets.GITHUB_TOKEN }} script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); setupGlobals(core, github, context, exec, io, getOctokit); @@ -1165,7 +1161,7 @@ jobs: GH_AW_WORKFLOW_NAME: "Android Tools PR Reviewer" GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/android-tools-reviewer.md" with: - github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + github-token: ${{ secrets.GITHUB_TOKEN }} script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); setupGlobals(core, github, context, exec, io, getOctokit); @@ -1204,7 +1200,7 @@ jobs: GH_AW_MISSING_DATA_REPORT_AS_FAILURE: "true" GH_AW_TIMEOUT_MINUTES: "20" with: - github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + github-token: ${{ secrets.GITHUB_TOKEN }} script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); setupGlobals(core, github, context, exec, io, getOctokit); @@ -1224,7 +1220,7 @@ jobs: GH_AW_DETECTION_CONCLUSION: ${{ needs.detection.outputs.detection_conclusion }} GH_AW_DETECTION_REASON: ${{ needs.detection.outputs.detection_reason }} with: - github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + github-token: ${{ secrets.GITHUB_TOKEN }} script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); setupGlobals(core, github, context, exec, io, getOctokit); @@ -1601,7 +1597,7 @@ jobs: GITHUB_API_URL: ${{ github.api_url }} GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"create_pull_request_review_comment\":{\"max\":50,\"side\":\"RIGHT\"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"},\"report_incomplete\":{},\"submit_pull_request_review\":{\"allowed_events\":[\"COMMENT\",\"REQUEST_CHANGES\"],\"max\":1}}" with: - github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + github-token: ${{ secrets.GITHUB_TOKEN }} script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); setupGlobals(core, github, context, exec, io, getOctokit); diff --git a/external/xamarin-android-tools/.github/workflows/android-tools-reviewer.md b/external/xamarin-android-tools/.github/workflows/android-tools-reviewer.md index 758171bc10a..87c281c328f 100644 --- a/external/xamarin-android-tools/.github/workflows/android-tools-reviewer.md +++ b/external/xamarin-android-tools/.github/workflows/android-tools-reviewer.md @@ -22,11 +22,13 @@ network: - "microsoft.com" tools: github: + github-token: ${{ secrets.GITHUB_TOKEN }} toolsets: [pull_requests, repos] # Allow reading PR content from external/first-time contributors. # The /review command is gated to maintainers, so only trusted users can trigger it. min-integrity: none safe-outputs: + github-token: ${{ secrets.GITHUB_TOKEN }} create-pull-request-review-comment: max: 50 submit-pull-request-review: From dcc326dc61e57a7fb0eb9356409bda2298e0e384 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 25 Jun 2026 16:16:36 -0500 Subject: [PATCH 304/308] Bump GitInfo from 3.6.0 to 3.6.1 (#392) --- updated-dependencies: - dependency-name: GitInfo dependency-version: 3.6.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- external/xamarin-android-tools/Directory.Build.targets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/xamarin-android-tools/Directory.Build.targets b/external/xamarin-android-tools/Directory.Build.targets index a7e63167bba..e1373281c8e 100644 --- a/external/xamarin-android-tools/Directory.Build.targets +++ b/external/xamarin-android-tools/Directory.Build.targets @@ -1,6 +1,6 @@ - + false From 84ca33f10ade4328dc8d88f0439643828dd5f502 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 25 Jun 2026 16:17:20 -0500 Subject: [PATCH 305/308] Bump Microsoft.NET.Test.Sdk from 18.6.0 to 18.7.0 (#393) --- updated-dependencies: - dependency-name: Microsoft.NET.Test.Sdk dependency-version: 18.7.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- external/xamarin-android-tools/Directory.Build.targets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/xamarin-android-tools/Directory.Build.targets b/external/xamarin-android-tools/Directory.Build.targets index e1373281c8e..71eb37507a4 100644 --- a/external/xamarin-android-tools/Directory.Build.targets +++ b/external/xamarin-android-tools/Directory.Build.targets @@ -22,7 +22,7 @@ - + From f765eee05a9e652e94cde150af519c8675ee6941 Mon Sep 17 00:00:00 2001 From: Vitek Karas <10670590+vitek-karas@users.noreply.github.com> Date: Mon, 29 Jun 2026 19:51:16 +0200 Subject: [PATCH 306/308] [gh-aw] move AI reviewer to PAT pool (#394) ## Summary - migrate the Android Tools GH-AW reviewer workflow to the Copilot PAT pool model - add the shared PAT pool import and README under `.github/workflows/shared/` - add `validate-pat-pool.yml` so the PAT pool can be checked independently - recompile the generated lock workflow and update `.github/aw/actions-lock.json` ## Validation - `gh aw compile .github/workflows/android-tools-reviewer.md --schedule-seed dotnet/android-tools --approve` - `dotnet build Xamarin.Android.Tools.sln` - `dotnet test tests\Microsoft.Android.Build.BaseTasks-Tests\Microsoft.Android.Build.BaseTasks-Tests.csproj` - `dotnet test tests\Xamarin.Android.Tools.AndroidSdk-Tests\Xamarin.Android.Tools.AndroidSdk-Tests.csproj` still has the pre-existing `JdkDirectory_JavaHome("JI_JAVA_HOME")` failure in both the feature worktree and the clean `main` checkout because `JAVA_HOME` is set in the local environment ## Security review note I reviewed the newly introduced secret references, action revisions, and generated workflow manifest changes from the PAT pool migration. ### Secrets Added restricted secrets: - `COPILOT_PAT_0` - `COPILOT_PAT_1` - `COPILOT_PAT_2` - `COPILOT_PAT_3` - `COPILOT_PAT_4` - `COPILOT_PAT_5` - `COPILOT_PAT_6` - `COPILOT_PAT_7` - `COPILOT_PAT_8` - `COPILOT_PAT_9` These are only used to select a PAT slot number inside the isolated `pat_pool` job and then resolve the selected slot through a `case(...)` expression in `engine.env`, matching the documented pattern in [dotnet/vitals PAT pool guidance](https://github.com/dotnet/vitals/blob/main/.github/workflows/shared/pat_pool.README.md) and the example in [dotnet/xharness#1626](https://github.com/dotnet/xharness/pull/1626). ### Actions and generated runtime updates - `github/gh-aw-actions/setup` updated from `v0.79.8` to `v0.80.9` (`8c7d04ebf1ece56cd381446125da3e0f6896294a`) - generated workflow containers updated to the compiler-selected set from `gh aw` v0.80.9: - `ghcr.io/github/gh-aw-firewall/agent:0.27.7` - `ghcr.io/github/gh-aw-firewall/api-proxy:0.27.7` - `ghcr.io/github/gh-aw-firewall/squid:0.27.7` - `ghcr.io/github/gh-aw-mcpg:v0.3.27` - `ghcr.io/github/gh-aw-node` - `ghcr.io/github/github-mcp-server:v1.4.0` I reviewed these as safe because they are generated by the official `gh aw` compiler during recompilation of the existing workflow, align with the PAT pool migration shape used in [dotnet/runtime#129840](https://github.com/dotnet/runtime/pull/129840), and do not introduce any custom third-party action beyond the standard GitHub / gh-aw generated set already used by this workflow. ### Redirect changes No redirect changes were introduced. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../.github/aw/actions-lock.json | 11 +- .../workflows/android-tools-reviewer.lock.yml | 431 +++++++++++------- .../workflows/android-tools-reviewer.md | 30 +- .../workflows/shared/pat_pool.README.md | 205 +++++++++ .../.github/workflows/shared/pat_pool.md | 119 +++++ .../.github/workflows/validate-pat-pool.yml | 206 +++++++++ 6 files changed, 837 insertions(+), 165 deletions(-) create mode 100644 external/xamarin-android-tools/.github/workflows/shared/pat_pool.README.md create mode 100644 external/xamarin-android-tools/.github/workflows/shared/pat_pool.md create mode 100644 external/xamarin-android-tools/.github/workflows/validate-pat-pool.yml diff --git a/external/xamarin-android-tools/.github/aw/actions-lock.json b/external/xamarin-android-tools/.github/aw/actions-lock.json index 84e3231b543..64a0a0a93a1 100644 --- a/external/xamarin-android-tools/.github/aw/actions-lock.json +++ b/external/xamarin-android-tools/.github/aw/actions-lock.json @@ -5,15 +5,10 @@ "version": "v9.0.0", "sha": "3a2844b7e9c422d3c10d287c895573f7108da1b3" }, - "github/gh-aw-actions/setup-cli@v0.79.8": { - "repo": "github/gh-aw-actions/setup-cli", - "version": "v0.79.8", - "sha": "c0338fef4749d08c21f8f975fb0e37efa17dda47" - }, - "github/gh-aw-actions/setup@v0.79.8": { + "github/gh-aw-actions/setup@v0.80.9": { "repo": "github/gh-aw-actions/setup", - "version": "v0.79.8", - "sha": "c0338fef4749d08c21f8f975fb0e37efa17dda47" + "version": "v0.80.9", + "sha": "8c7d04ebf1ece56cd381446125da3e0f6896294a" } } } diff --git a/external/xamarin-android-tools/.github/workflows/android-tools-reviewer.lock.yml b/external/xamarin-android-tools/.github/workflows/android-tools-reviewer.lock.yml index 8af8af9327a..36be6842a7c 100644 --- a/external/xamarin-android-tools/.github/workflows/android-tools-reviewer.lock.yml +++ b/external/xamarin-android-tools/.github/workflows/android-tools-reviewer.lock.yml @@ -1,6 +1,6 @@ -# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"c3396efe8513752ad6979d99d949c90fba2958ee9e30eac19a6a02762b1313ea","body_hash":"1b21a58fc25436cc68541bee79e0d82f2b44fb168383670638bc2cfc46f89483","compiler_version":"v0.79.8","strict":true,"agent_id":"copilot","agent_model":"claude-opus-4.8","engine_versions":{"copilot":"1.0.60"}} -# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"df4cb1c069e1874edd31b4311f1884172cec0e10","version":"v6.0.3"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"c0338fef4749d08c21f8f975fb0e37efa17dda47","version":"v0.79.8"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.2","digest":"sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.2@sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.2","digest":"sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.2@sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.2","digest":"sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.2@sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.25","digest":"sha256:c10331ad17668ef89f38f5e356678788a40b0cd5fef96e8f92e1d9c1de47cbaa","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.25@sha256:c10331ad17668ef89f38f5e356678788a40b0cd5fef96e8f92e1d9c1de47cbaa"},{"image":"ghcr.io/github/github-mcp-server:v1.1.2","digest":"sha256:30197479d8036c7811892bc07e06f9a05c9ef3cdd79bc59f256d50647f95788c","pinned_image":"ghcr.io/github/github-mcp-server:v1.1.2@sha256:30197479d8036c7811892bc07e06f9a05c9ef3cdd79bc59f256d50647f95788c"}]} -# This file was automatically generated by gh-aw (v0.79.8). DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md +# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"e0faed06be2be28082e53de5c31e62440e1c2f42b88ba18d39b5bf90562cbb5a","body_hash":"1b21a58fc25436cc68541bee79e0d82f2b44fb168383670638bc2cfc46f89483","compiler_version":"v0.80.9","strict":true,"agent_id":"copilot","agent_model":"claude-opus-4.8","engine_versions":{"copilot":"1.0.63"}} +# gh-aw-manifest: {"version":1,"secrets":["COPILOT_PAT_0","COPILOT_PAT_1","COPILOT_PAT_2","COPILOT_PAT_3","COPILOT_PAT_4","COPILOT_PAT_5","COPILOT_PAT_6","COPILOT_PAT_7","COPILOT_PAT_8","COPILOT_PAT_9","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0","version":"v7.0.0"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"8c7d04ebf1ece56cd381446125da3e0f6896294a","version":"v0.80.9"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.7","digest":"sha256:aae231e4635c8999d039c132f1602d3df850fe9b84a00aa2b5ac981179b5661c","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.7@sha256:aae231e4635c8999d039c132f1602d3df850fe9b84a00aa2b5ac981179b5661c"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.7","digest":"sha256:009caf2e3d88fa77b64e9a03a95a228fc58db0f1701c6d324b29ba5a3c7c79b6","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.7@sha256:009caf2e3d88fa77b64e9a03a95a228fc58db0f1701c6d324b29ba5a3c7c79b6"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.7","digest":"sha256:deb1d4e19de62d51cee0508057a596a19315c3423ada4d675cad136dc8037c96","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.7@sha256:deb1d4e19de62d51cee0508057a596a19315c3423ada4d675cad136dc8037c96"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.27","digest":"sha256:fe984bddde4ec05d756d9043edb0a32912e6b7b72f6a121b1082f29221421cc7","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.27@sha256:fe984bddde4ec05d756d9043edb0a32912e6b7b72f6a121b1082f29221421cc7"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b","pinned_image":"ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b"},{"image":"ghcr.io/github/github-mcp-server:v1.4.0","digest":"sha256:2afb26356481d1a350e14544a6e160f7f7ec1561a1ea309b823665abf0309036","pinned_image":"ghcr.io/github/github-mcp-server:v1.4.0@sha256:2afb26356481d1a350e14544a6e160f7f7ec1561a1ea309b823665abf0309036"}]} +# This file was automatically generated by gh-aw (v0.80.9). DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md # # ___ _ _ # / _ \ | | (_) @@ -24,24 +24,38 @@ # For more information: https://github.github.com/gh-aw/introduction/overview/ # # +# Resolved workflow manifest: +# Imports: +# - shared/pat_pool.md +# # Secrets used: -# - COPILOT_GITHUB_TOKEN +# - COPILOT_PAT_0 +# - COPILOT_PAT_1 +# - COPILOT_PAT_2 +# - COPILOT_PAT_3 +# - COPILOT_PAT_4 +# - COPILOT_PAT_5 +# - COPILOT_PAT_6 +# - COPILOT_PAT_7 +# - COPILOT_PAT_8 +# - COPILOT_PAT_9 # - GITHUB_TOKEN # # Custom actions used: -# - actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 +# - actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 # - actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 # - actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 # - actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 # - actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 -# - github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 +# - github/gh-aw-actions/setup@8c7d04ebf1ece56cd381446125da3e0f6896294a # v0.80.9 # # Container images used: -# - ghcr.io/github/gh-aw-firewall/agent:0.27.2@sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6 -# - ghcr.io/github/gh-aw-firewall/api-proxy:0.27.2@sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4 -# - ghcr.io/github/gh-aw-firewall/squid:0.27.2@sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591 -# - ghcr.io/github/gh-aw-mcpg:v0.3.25@sha256:c10331ad17668ef89f38f5e356678788a40b0cd5fef96e8f92e1d9c1de47cbaa -# - ghcr.io/github/github-mcp-server:v1.1.2@sha256:30197479d8036c7811892bc07e06f9a05c9ef3cdd79bc59f256d50647f95788c +# - ghcr.io/github/gh-aw-firewall/agent:0.27.7@sha256:aae231e4635c8999d039c132f1602d3df850fe9b84a00aa2b5ac981179b5661c +# - ghcr.io/github/gh-aw-firewall/api-proxy:0.27.7@sha256:009caf2e3d88fa77b64e9a03a95a228fc58db0f1701c6d324b29ba5a3c7c79b6 +# - ghcr.io/github/gh-aw-firewall/squid:0.27.7@sha256:deb1d4e19de62d51cee0508057a596a19315c3423ada4d675cad136dc8037c96 +# - ghcr.io/github/gh-aw-mcpg:v0.3.27@sha256:fe984bddde4ec05d756d9043edb0a32912e6b7b72f6a121b1082f29221421cc7 +# - ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b +# - ghcr.io/github/github-mcp-server:v1.4.0@sha256:2afb26356481d1a350e14544a6e160f7f7ec1561a1ea309b823665abf0309036 name: "Android Tools PR Reviewer" on: @@ -63,7 +77,9 @@ run-name: "Android Tools PR Reviewer" jobs: activation: - needs: pre_activation + needs: + - pat_pool + - pre_activation if: "needs.pre_activation.outputs.activated == 'true' && (github.event_name == 'issue_comment' && (startsWith(github.event.comment.body, '/review ') || startsWith(github.event.comment.body, '/review\n') || github.event.comment.body == '/review') && github.event.issue.pull_request != null || !(github.event_name == 'issue_comment'))" runs-on: ubuntu-slim permissions: @@ -89,7 +105,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 + uses: github/gh-aw-actions/setup@8c7d04ebf1ece56cd381446125da3e0f6896294a # v0.80.9 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -98,8 +114,8 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Android Tools PR Reviewer" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/android-tools-reviewer.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.60" - GH_AW_INFO_AWF_VERSION: "v0.27.2" + GH_AW_INFO_VERSION: "1.0.63" + GH_AW_INFO_AWF_VERSION: "v0.27.7" GH_AW_INFO_ENGINE_ID: "copilot" - name: Generate agentic run info id: generate_aw_info @@ -107,16 +123,16 @@ jobs: GH_AW_INFO_ENGINE_ID: "copilot" GH_AW_INFO_ENGINE_NAME: "GitHub Copilot CLI" GH_AW_INFO_MODEL: "claude-opus-4.8" - GH_AW_INFO_VERSION: "1.0.60" - GH_AW_INFO_AGENT_VERSION: "1.0.60" - GH_AW_INFO_CLI_VERSION: "v0.79.8" + GH_AW_INFO_VERSION: "1.0.63" + GH_AW_INFO_AGENT_VERSION: "1.0.63" + GH_AW_INFO_CLI_VERSION: "v0.80.9" GH_AW_INFO_WORKFLOW_NAME: "Android Tools PR Reviewer" GH_AW_INFO_EXPERIMENTAL: "false" GH_AW_INFO_SUPPORTS_TOOLS_ALLOWLIST: "true" GH_AW_INFO_STAGED: "false" GH_AW_INFO_ALLOWED_DOMAINS: '["defaults","dotnet","github","aka.ms","microsoft.com"]' GH_AW_INFO_FIREWALL_ENABLED: "true" - GH_AW_INFO_AWF_VERSION: "v0.27.2" + GH_AW_INFO_AWF_VERSION: "v0.27.7" GH_AW_INFO_AWMG_VERSION: "" GH_AW_INFO_FIREWALL_TYPE: "squid" GH_AW_COMPILED_STRICT: "true" @@ -141,7 +157,7 @@ jobs: const { main } = require('${{ runner.temp }}/gh-aw/actions/add_reaction.cjs'); await main(); - name: Checkout .github and .agents folders - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: persist-credentials: false sparse-checkout: | @@ -177,7 +193,7 @@ jobs: - name: Check compile-agentic version uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 env: - GH_AW_COMPILED_VERSION: "v0.79.8" + GH_AW_COMPILED_VERSION: "v0.80.9" with: script: | const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); @@ -224,20 +240,20 @@ jobs: run: | bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh" { - cat << 'GH_AW_PROMPT_6dabf3c3eafb9cae_EOF' + cat << 'GH_AW_PROMPT_6509d4c84633d1dc_EOF' - GH_AW_PROMPT_6dabf3c3eafb9cae_EOF + GH_AW_PROMPT_6509d4c84633d1dc_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/xpia.md" cat "${RUNNER_TEMP}/gh-aw/prompts/temp_folder_prompt.md" cat "${RUNNER_TEMP}/gh-aw/prompts/markdown.md" cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_prompt.md" - cat << 'GH_AW_PROMPT_6dabf3c3eafb9cae_EOF' + cat << 'GH_AW_PROMPT_6509d4c84633d1dc_EOF' Tools: create_pull_request_review_comment(max:50), submit_pull_request_review, missing_tool, missing_data, noop - GH_AW_PROMPT_6dabf3c3eafb9cae_EOF + GH_AW_PROMPT_6509d4c84633d1dc_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md" - cat << 'GH_AW_PROMPT_6dabf3c3eafb9cae_EOF' + cat << 'GH_AW_PROMPT_6509d4c84633d1dc_EOF' The following GitHub context information is available for this workflow: {{#if github.actor}} @@ -266,15 +282,15 @@ jobs: {{/if}} - GH_AW_PROMPT_6dabf3c3eafb9cae_EOF + GH_AW_PROMPT_6509d4c84633d1dc_EOF cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md" if [ "$GITHUB_EVENT_NAME" = "issue_comment" ] && [ -n "$GH_AW_IS_PR_COMMENT" ] || [ "$GITHUB_EVENT_NAME" = "pull_request_review_comment" ] || [ "$GITHUB_EVENT_NAME" = "pull_request_review" ]; then cat "${RUNNER_TEMP}/gh-aw/prompts/pr_context_prompt.md" fi - cat << 'GH_AW_PROMPT_6dabf3c3eafb9cae_EOF' + cat << 'GH_AW_PROMPT_6509d4c84633d1dc_EOF' {{#runtime-import .github/workflows/android-tools-reviewer.md}} - GH_AW_PROMPT_6dabf3c3eafb9cae_EOF + GH_AW_PROMPT_6509d4c84633d1dc_EOF } > "$GH_AW_PROMPT" - name: Interpolate variables and render templates uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 @@ -358,9 +374,11 @@ jobs: retention-days: 1 agent: - needs: activation + needs: + - activation + - pat_pool runs-on: ubuntu-latest - environment: copilot-pr-reviewer + environment: copilot-pat-pool permissions: contents: read pull-requests: read @@ -392,7 +410,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 + uses: github/gh-aw-actions/setup@8c7d04ebf1ece56cd381446125da3e0f6896294a # v0.80.9 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -401,8 +419,8 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Android Tools PR Reviewer" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/android-tools-reviewer.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.60" - GH_AW_INFO_AWF_VERSION: "v0.27.2" + GH_AW_INFO_VERSION: "1.0.63" + GH_AW_INFO_AWF_VERSION: "v0.27.7" GH_AW_INFO_ENGINE_ID: "copilot" - name: Set runtime paths id: set-runtime-paths @@ -413,7 +431,7 @@ jobs: echo "GH_AW_SAFE_OUTPUTS_TOOLS_PATH=${RUNNER_TEMP}/gh-aw/safeoutputs/tools.json" } >> "$GITHUB_OUTPUT" - name: Checkout repository - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: persist-credentials: false - name: Create gh-aw temp directory @@ -424,17 +442,10 @@ jobs: GH_TOKEN: ${{ github.token }} - name: Configure Git credentials env: - REPO_NAME: ${{ github.repository }} - SERVER_URL: ${{ github.server_url }} + GITHUB_REPOSITORY: ${{ github.repository }} + GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_TOKEN: ${{ github.token }} - run: | - git config --global user.email "github-actions[bot]@users.noreply.github.com" - git config --global user.name "github-actions[bot]" - git config --global am.keepcr true - # Re-authenticate git with GitHub token - SERVER_URL_STRIPPED="${SERVER_URL#https://}" - git remote set-url origin "https://x-access-token:${GITHUB_TOKEN}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" - echo "Git configured with standard GitHub Actions identity" + run: bash "${RUNNER_TEMP}/gh-aw/actions/configure_git_credentials.sh" - name: Checkout PR branch id: checkout-pr if: | @@ -450,11 +461,11 @@ jobs: const { main } = require('${{ runner.temp }}/gh-aw/actions/checkout_pr_branch.cjs'); await main(); - name: Install GitHub Copilot CLI - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.60 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.63 env: GH_HOST: github.com - name: Install AWF binary - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.2 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.7 - name: Parse integrity filter lists id: parse-guard-vars env: @@ -483,15 +494,15 @@ jobs: GH_AW_SKILL_DIR: ".github/skills" run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_inline_skills.sh" - name: Download container images - run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.2@sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6 ghcr.io/github/gh-aw-firewall/api-proxy:0.27.2@sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4 ghcr.io/github/gh-aw-firewall/squid:0.27.2@sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591 ghcr.io/github/gh-aw-mcpg:v0.3.25@sha256:c10331ad17668ef89f38f5e356678788a40b0cd5fef96e8f92e1d9c1de47cbaa ghcr.io/github/github-mcp-server:v1.1.2@sha256:30197479d8036c7811892bc07e06f9a05c9ef3cdd79bc59f256d50647f95788c + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.7@sha256:aae231e4635c8999d039c132f1602d3df850fe9b84a00aa2b5ac981179b5661c ghcr.io/github/gh-aw-firewall/api-proxy:0.27.7@sha256:009caf2e3d88fa77b64e9a03a95a228fc58db0f1701c6d324b29ba5a3c7c79b6 ghcr.io/github/gh-aw-firewall/squid:0.27.7@sha256:deb1d4e19de62d51cee0508057a596a19315c3423ada4d675cad136dc8037c96 ghcr.io/github/gh-aw-mcpg:v0.3.27@sha256:fe984bddde4ec05d756d9043edb0a32912e6b7b72f6a121b1082f29221421cc7 ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b ghcr.io/github/github-mcp-server:v1.4.0@sha256:2afb26356481d1a350e14544a6e160f7f7ec1561a1ea309b823665abf0309036 - name: Generate Safe Outputs Config run: | mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" mkdir -p /tmp/gh-aw/safeoutputs mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs - cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_161badeedaa0bf19_EOF' + cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_0e2b419eba97b7e4_EOF' {"create_pull_request_review_comment":{"max":50,"side":"RIGHT"},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"report_incomplete":{},"submit_pull_request_review":{"allowed_events":["COMMENT","REQUEST_CHANGES"],"max":1}} - GH_AW_SAFE_OUTPUTS_CONFIG_161badeedaa0bf19_EOF + GH_AW_SAFE_OUTPUTS_CONFIG_0e2b419eba97b7e4_EOF - name: Generate Safe Outputs Tools env: GH_AW_TOOLS_META_JSON: | @@ -632,7 +643,7 @@ jobs: ] }, "pull_request_number": { - "optionalPositiveInteger": true + "issueOrPRNumber": true }, "repo": { "type": "string", @@ -648,53 +659,14 @@ jobs: setupGlobals(core, github, context, exec, io, getOctokit); const { main } = require('${{ runner.temp }}/gh-aw/actions/generate_safe_outputs_tools.cjs'); await main(); - - name: Generate Safe Outputs MCP Server Config - id: safe-outputs-config - run: | - # Generate a secure random API key (360 bits of entropy, 40+ chars) - # Mask immediately to prevent timing vulnerabilities - API_KEY=$(openssl rand -base64 45 | tr -d '/+=') - echo "::add-mask::${API_KEY}" - - PORT=3001 - - # Set outputs for next steps - { - echo "safe_outputs_api_key=${API_KEY}" - echo "safe_outputs_port=${PORT}" - } >> "$GITHUB_OUTPUT" - - echo "Safe Outputs MCP server will run on port ${PORT}" - - - name: Start Safe Outputs MCP HTTP Server - id: safe-outputs-start - env: - DEBUG: '*' - GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} - GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-config.outputs.safe_outputs_port }} - GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-config.outputs.safe_outputs_api_key }} - GH_AW_SAFE_OUTPUTS_TOOLS_PATH: ${{ runner.temp }}/gh-aw/safeoutputs/tools.json - GH_AW_SAFE_OUTPUTS_CONFIG_PATH: ${{ runner.temp }}/gh-aw/safeoutputs/config.json - GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs - run: | - # Environment variables are set above to prevent template injection - export DEBUG - export GH_AW_SAFE_OUTPUTS - export GH_AW_SAFE_OUTPUTS_PORT - export GH_AW_SAFE_OUTPUTS_API_KEY - export GH_AW_SAFE_OUTPUTS_TOOLS_PATH - export GH_AW_SAFE_OUTPUTS_CONFIG_PATH - export GH_AW_MCP_LOG_DIR - - bash "${RUNNER_TEMP}/gh-aw/actions/start_safe_outputs_server.sh" - - name: Start MCP Gateway id: start-mcp-gateway env: GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} - GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-start.outputs.api_key }} - GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-start.outputs.port }} + GH_AW_SAFE_OUTPUTS_CONFIG_PATH: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS_CONFIG_PATH }} + GH_AW_SAFE_OUTPUTS_TOOLS_PATH: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS_TOOLS_PATH }} GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | set -eo pipefail mkdir -p "${RUNNER_TEMP}/gh-aw/mcp-config" @@ -720,16 +692,16 @@ jobs: * ) DOCKER_SOCK_PATH=/var/run/docker.sock ;; esac DOCKER_SOCK_GID=$(stat -c '%g' "$DOCKER_SOCK_PATH" 2>/dev/null || echo '0') - export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host --add-host host.docker.internal:127.0.0.1 --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v '"${DOCKER_SOCK_PATH}"':/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DOCKER_HOST=unix:///var/run/docker.sock -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/github/gh-aw-mcpg:v0.3.25' + export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host --name awmg-mcpg --add-host host.docker.internal:127.0.0.1 --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v '"${DOCKER_SOCK_PATH}"':/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DOCKER_HOST=unix:///var/run/docker.sock -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e RUNNER_TEMP -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw -v '"${RUNNER_TEMP}"'/gh-aw/safeoutputs:'"${RUNNER_TEMP}"'/gh-aw/safeoutputs:rw ghcr.io/github/gh-aw-mcpg:v0.3.27' mkdir -p "$HOME/.copilot" GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node) - cat << GH_AW_MCP_CONFIG_5df4e2192112624c_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" + cat << GH_AW_MCP_CONFIG_4efb80bd6f5d5d9b_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" { "mcpServers": { "github": { "type": "stdio", - "container": "ghcr.io/github/github-mcp-server:v1.1.2", + "container": "ghcr.io/github/github-mcp-server:v1.4.0", "env": { "GITHUB_HOST": "\${GITHUB_SERVER_URL}", "GITHUB_PERSONAL_ACCESS_TOKEN": "\${GITHUB_MCP_SERVER_TOKEN}", @@ -747,10 +719,26 @@ jobs: } }, "safeoutputs": { - "type": "http", - "url": "http://host.docker.internal:$GH_AW_SAFE_OUTPUTS_PORT", - "headers": { - "Authorization": "\${GH_AW_SAFE_OUTPUTS_API_KEY}" + "type": "stdio", + "container": "ghcr.io/github/gh-aw-node", + "mounts": ["\${GITHUB_WORKSPACE}:\${GITHUB_WORKSPACE}:rw", "${RUNNER_TEMP}/gh-aw/safeoutputs:${RUNNER_TEMP}/gh-aw/safeoutputs:rw", "/tmp/gh-aw:/tmp/gh-aw:rw"], + "args": ["-w", "\${GITHUB_WORKSPACE}"], + "entrypoint": "sh", + "entrypointArgs": ["-c", "sh ${RUNNER_TEMP}/gh-aw/safeoutputs/start_safe_outputs_mcp.sh"], + "env": { + "DEBUG": "*", + "DEFAULT_BRANCH": "\${DEFAULT_BRANCH}", + "GH_AW_ASSETS_ALLOWED_EXTS": "\${GH_AW_ASSETS_ALLOWED_EXTS}", + "GH_AW_ASSETS_BRANCH": "\${GH_AW_ASSETS_BRANCH}", + "GH_AW_ASSETS_MAX_SIZE_KB": "\${GH_AW_ASSETS_MAX_SIZE_KB}", + "GH_AW_MCP_LOG_DIR": "\${GH_AW_MCP_LOG_DIR}", + "GH_AW_SAFE_OUTPUTS": "\${GH_AW_SAFE_OUTPUTS}", + "GH_AW_SAFE_OUTPUTS_CONFIG_PATH": "\${GH_AW_SAFE_OUTPUTS_CONFIG_PATH}", + "GH_AW_SAFE_OUTPUTS_TOOLS_PATH": "\${GH_AW_SAFE_OUTPUTS_TOOLS_PATH}", + "GITHUB_REPOSITORY": "\${GITHUB_REPOSITORY}", + "GITHUB_TOKEN": "\${GITHUB_TOKEN}", + "GITHUB_WORKSPACE": "\${GITHUB_WORKSPACE}", + "RUNNER_TEMP": "\${RUNNER_TEMP}" }, "guard-policies": { "write-sink": { @@ -768,7 +756,7 @@ jobs: "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" } } - GH_AW_MCP_CONFIG_5df4e2192112624c_EOF + GH_AW_MCP_CONFIG_4efb80bd6f5d5d9b_EOF - name: Mount MCP servers as CLIs id: mount-mcp-clis continue-on-error: true @@ -807,37 +795,65 @@ jobs: export GH_AW_NODE_BIN export COPILOT_API_KEY="$COPILOT_DUMMY_BYOK" (umask 177 && touch /tmp/gh-aw/agent-stdio.log) - printf '%s\n' '{"$schema":"https://github.com/github/gh-aw-firewall/releases/download/v0.27.2/awf-config.schema.json","network":{"allowDomains":["*.githubusercontent.com","*.vsblob.vsassets.io","aka.ms","api.business.githubcopilot.com","api.enterprise.githubcopilot.com","api.github.com","api.githubcopilot.com","api.individual.githubcopilot.com","api.nuget.org","api.snapcraft.io","archive.ubuntu.com","azure.archive.ubuntu.com","azuresearch-usnc.nuget.org","azuresearch-ussc.nuget.org","builds.dotnet.microsoft.com","ci.dot.net","codeload.github.com","crl.geotrust.com","crl.globalsign.com","crl.identrust.com","crl.sectigo.com","crl.thawte.com","crl.usertrust.com","crl.verisign.com","crl3.digicert.com","crl4.digicert.com","crls.ssl.com","dc.services.visualstudio.com","dist.nuget.org","docs.github.com","dot.net","dotnet.microsoft.com","dotnetcli.blob.core.windows.net","github-cloud.githubusercontent.com","github-cloud.s3.amazonaws.com","github.blog","github.com","github.githubassets.com","host.docker.internal","json-schema.org","json.schemastore.org","keyserver.ubuntu.com","lfs.github.com","microsoft.com","nuget.org","nuget.pkg.github.com","nugetregistryv2prod.blob.core.windows.net","objects.githubusercontent.com","ocsp.digicert.com","ocsp.geotrust.com","ocsp.globalsign.com","ocsp.identrust.com","ocsp.sectigo.com","ocsp.ssl.com","ocsp.thawte.com","ocsp.usertrust.com","ocsp.verisign.com","oneocsp.microsoft.com","packagecloud.io","packages.cloud.google.com","packages.microsoft.com","patch-diff.githubusercontent.com","pkgs.dev.azure.com","ppa.launchpad.net","raw.githubusercontent.com","registry.npmjs.org","s.symcb.com","s.symcd.com","security.ubuntu.com","telemetry.enterprise.githubcopilot.com","ts-crl.ws.symantec.com","ts-ocsp.ws.symantec.com","www.googleapis.com","www.microsoft.com"]},"apiProxy":{"enabled":true,"maxRuns":500,"models":{"agent":["sonnet-6x","gpt-5.4","gpt-5.3","gemini-pro","any"],"antigravity":["copilot/antigravity*","google/antigravity*","gemini/antigravity*"],"any":["copilot/*","anthropic/*","openai/*","google/*","gemini/*"],"claude":["agent"],"codex":["agent"],"coding":["copilot/gpt-5*codex*","openai/gpt-5*codex*","gpt-5-codex"],"computer-use":["copilot/*computer-use*","google/*computer-use*","gemini/*computer-use*","openai/*computer-use*"],"copilot":["agent"],"deep-research":["copilot/deep-research*","copilot/o3-deep-research*","copilot/o4-mini-deep-research*","google/deep-research*","gemini/deep-research*","openai/o3-deep-research*","openai/o4-mini-deep-research*"],"gemini":["agent"],"gemini-3-flash":["copilot/gemini-3*flash*","google/gemini-3*flash*","gemini/gemini-3*flash*"],"gemini-3-pro":["copilot/gemini-3*pro*","google/gemini-3*pro*","google/nano-banana*","gemini/gemini-3*pro*"],"gemini-3.1-flash":["copilot/gemini-3.1*flash*","google/gemini-3.1*flash*","gemini/gemini-3.1*flash*"],"gemini-3.1-pro":["copilot/gemini-3.1*pro*","google/gemini-3.1*pro*","gemini/gemini-3.1*pro*"],"gemini-3.5-flash":["copilot/gemini-3.5*flash*","google/gemini-3.5*flash*","gemini/gemini-3.5*flash*"],"gemini-flash":["copilot/gemini-*flash*","google/gemini-*flash*","gemini/gemini-*flash*"],"gemini-flash-lite":["copilot/gemini-*flash*lite*","google/gemini-*flash*lite*","gemini/gemini-*flash*lite*"],"gemini-pro":["copilot/gemini-*pro*","google/gemini-*pro*","gemini/gemini-*pro*"],"gemma":["copilot/gemma*","google/gemma*","gemini/gemma*"],"gpt-5":["copilot/gpt-5*","openai/gpt-5*"],"gpt-5-codex":["copilot/gpt-5*codex*","openai/gpt-5*codex*"],"gpt-5-mini":["copilot/gpt-5*mini*","openai/gpt-5*mini*"],"gpt-5-nano":["copilot/gpt-5*nano*","openai/gpt-5*nano*"],"gpt-5-pro":["copilot/gpt-5*pro*","openai/gpt-5*pro*"],"gpt-5.2":["copilot/gpt-5.2*","openai/gpt-5.2*"],"gpt-5.3":["copilot/gpt-5.3*","openai/gpt-5.3*"],"gpt-5.4":["copilot/gpt-5.4*","openai/gpt-5.4*"],"gpt-5.5":["copilot/gpt-5.5*","openai/gpt-5.5*"],"haiku":["copilot/*haiku*","anthropic/*haiku*"],"large":["sonnet","gpt-5-pro","gpt-5","gemini-pro"],"mai-code":["copilot/MAI-Code*","copilot/mai-code*","openai/MAI-Code*"],"mini":["haiku","gpt-5-mini","gpt-5-nano","gemini-flash-lite"],"nano-banana":["copilot/nano-banana*","google/nano-banana*","gemini/nano-banana*"],"opus":["copilot/*opus*","anthropic/*opus*"],"opusplan":["opus?effort=high"],"reasoning":["copilot/o1*","copilot/o3*","copilot/o4*","openai/o1*","openai/o3*","openai/o4*"],"robotics":["copilot/*robotics*","google/*robotics*","gemini/*robotics*"],"small":["mini"],"small-agent":["haiku","gpt-5-mini","gemini-flash"],"sonnet":["copilot/*sonnet*","anthropic/*sonnet*"],"sonnet-6x":["copilot/*sonnet-4.5*","copilot/*sonnet-4.6*","copilot/*sonnet-4-5-*","anthropic/*sonnet-4-5-*","copilot/*sonnet-4-6*","anthropic/*sonnet-4-6*"],"summarization":["haiku","gpt-5-mini","gemini-flash-lite","mini"],"vision":["copilot/gemini-*image*","gemini/gemini-*image*","copilot/gemini-*flash*","gemini/gemini-*flash*"]}},"container":{"imageTag":"0.27.2,squid=sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591,agent=sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6,api-proxy=sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4,cli-proxy=sha256:02f3ec08f32dc26c5427920c6a2e2f3036238fce44802f2f11ef49ed8621b5d0"}}' > "${RUNNER_TEMP}/gh-aw/awf-config.json" + printf '%s\n' '{"$schema":"https://github.com/github/gh-aw-firewall/releases/download/v0.27.7/awf-config.schema.json","network":{"allowDomains":["*.githubusercontent.com","*.vsblob.vsassets.io","aka.ms","api.business.githubcopilot.com","api.enterprise.githubcopilot.com","api.github.com","api.githubcopilot.com","api.individual.githubcopilot.com","api.nuget.org","api.snapcraft.io","archive.ubuntu.com","azure.archive.ubuntu.com","azuresearch-usnc.nuget.org","azuresearch-ussc.nuget.org","builds.dotnet.microsoft.com","ci.dot.net","codeload.github.com","crl.geotrust.com","crl.globalsign.com","crl.identrust.com","crl.sectigo.com","crl.thawte.com","crl.usertrust.com","crl.verisign.com","crl3.digicert.com","crl4.digicert.com","crls.ssl.com","dc.services.visualstudio.com","dist.nuget.org","docs.github.com","dot.net","dotnet.microsoft.com","dotnetcli.blob.core.windows.net","github-cloud.githubusercontent.com","github-cloud.s3.amazonaws.com","github.blog","github.com","github.githubassets.com","host.docker.internal","json-schema.org","json.schemastore.org","keyserver.ubuntu.com","lfs.github.com","microsoft.com","nuget.org","nuget.pkg.github.com","nugetregistryv2prod.blob.core.windows.net","objects.githubusercontent.com","ocsp.digicert.com","ocsp.geotrust.com","ocsp.globalsign.com","ocsp.identrust.com","ocsp.sectigo.com","ocsp.ssl.com","ocsp.thawte.com","ocsp.usertrust.com","ocsp.verisign.com","oneocsp.microsoft.com","packagecloud.io","packages.cloud.google.com","packages.microsoft.com","patch-diff.githubusercontent.com","pkgs.dev.azure.com","ppa.launchpad.net","raw.githubusercontent.com","registry.npmjs.org","s.symcb.com","s.symcd.com","security.ubuntu.com","telemetry.enterprise.githubcopilot.com","ts-crl.ws.symantec.com","ts-ocsp.ws.symantec.com","www.googleapis.com","www.microsoft.com"]},"apiProxy":{"enabled":true,"maxRuns":500,"maxCacheMisses":5,"models":{"agent":["sonnet-6x","gpt-5.5","gpt-5.4","gpt-5.3","gemini-pro","any"],"antigravity":["copilot/antigravity*","google/antigravity*","gemini/antigravity*"],"any":["copilot/*","anthropic/*","openai/*","google/*","gemini/*"],"claude":["agent"],"codex":["agent"],"coding":["copilot/gpt-5*codex*","openai/gpt-5*codex*","gpt-5-codex"],"computer-use":["copilot/*computer-use*","google/*computer-use*","gemini/*computer-use*","openai/*computer-use*"],"copilot":["agent"],"deep-research":["copilot/deep-research*","copilot/o3-deep-research*","copilot/o4-mini-deep-research*","google/deep-research*","gemini/deep-research*","openai/o3-deep-research*","openai/o4-mini-deep-research*"],"gemini":["agent"],"gemini-3-flash":["copilot/gemini-3*flash*","google/gemini-3*flash*","gemini/gemini-3*flash*"],"gemini-3-pro":["copilot/gemini-3*pro*","google/gemini-3*pro*","google/nano-banana*","gemini/gemini-3*pro*"],"gemini-3.1-flash":["copilot/gemini-3.1*flash*","google/gemini-3.1*flash*","gemini/gemini-3.1*flash*"],"gemini-3.1-pro":["copilot/gemini-3.1*pro*","google/gemini-3.1*pro*","gemini/gemini-3.1*pro*"],"gemini-3.5-flash":["copilot/gemini-3.5*flash*","google/gemini-3.5*flash*","gemini/gemini-3.5*flash*"],"gemini-flash":["copilot/gemini-*flash*","google/gemini-*flash*","gemini/gemini-*flash*"],"gemini-flash-lite":["copilot/gemini-*flash*lite*","google/gemini-*flash*lite*","gemini/gemini-*flash*lite*"],"gemini-pro":["copilot/gemini-*pro*","google/gemini-*pro*","gemini/gemini-*pro*"],"gemma":["copilot/gemma*","google/gemma*","gemini/gemma*"],"gpt-5":["copilot/gpt-5*","openai/gpt-5*"],"gpt-5-codex":["copilot/gpt-5*codex*","openai/gpt-5*codex*"],"gpt-5-mini":["copilot/gpt-5*mini*","openai/gpt-5*mini*"],"gpt-5-nano":["copilot/gpt-5*nano*","openai/gpt-5*nano*"],"gpt-5-pro":["copilot/gpt-5*pro*","openai/gpt-5*pro*"],"gpt-5.1":["copilot/gpt-5.1*","openai/gpt-5.1*"],"gpt-5.2":["copilot/gpt-5.2*","openai/gpt-5.2*"],"gpt-5.3":["copilot/gpt-5.3*","openai/gpt-5.3*"],"gpt-5.4":["copilot/gpt-5.4*","openai/gpt-5.4*"],"gpt-5.5":["copilot/gpt-5.5*","openai/gpt-5.5*"],"haiku":["copilot/*haiku*","anthropic/*haiku*"],"image-generation":["copilot/gpt-image*","openai/gpt-image*","openai/chatgpt-image*","copilot/gemini-*image*","google/gemini-*image*","gemini/gemini-*image*","google/imagen*"],"large":["sonnet","gpt-5-pro","gpt-5","gemini-pro"],"mai-code":["copilot/MAI-Code*","copilot/mai-code*","openai/MAI-Code*"],"mini":["haiku","gpt-5-mini","gpt-5-nano","gemini-flash-lite"],"nano-banana":["copilot/nano-banana*","google/nano-banana*","gemini/nano-banana*"],"opus":["copilot/*opus*","anthropic/*opus*"],"opusplan":["opus?effort=high"],"reasoning":["copilot/o1*","copilot/o3*","copilot/o4*","openai/o1*","openai/o3*","openai/o4*"],"robotics":["copilot/*robotics*","google/*robotics*","gemini/*robotics*"],"small":["mini"],"small-agent":["haiku","gpt-5-mini","gemini-flash"],"sonnet":["copilot/*sonnet*","anthropic/*sonnet*"],"sonnet-6x":["copilot/*sonnet-4.5*","copilot/*sonnet-4.6*","copilot/*sonnet-4-5-*","anthropic/*sonnet-4-5-*","copilot/*sonnet-4-6*","anthropic/*sonnet-4-6*"],"summarization":["haiku","gpt-5-mini","gemini-flash-lite","mini"],"vision":["copilot/gemini-*image*","google/gemini-*image*","gemini/gemini-*image*","copilot/gemini-*flash*","google/gemini-*flash*","gemini/gemini-*flash*"]}},"container":{"imageTag":"0.27.7,squid=sha256:deb1d4e19de62d51cee0508057a596a19315c3423ada4d675cad136dc8037c96,agent=sha256:aae231e4635c8999d039c132f1602d3df850fe9b84a00aa2b5ac981179b5661c,api-proxy=sha256:009caf2e3d88fa77b64e9a03a95a228fc58db0f1701c6d324b29ba5a3c7c79b6,cli-proxy=sha256:4757f198a3fa20f88bdbe70be7ae1a05f127d9c0a9e96a5d6460ef40c08fc83d"}}' > "${RUNNER_TEMP}/gh-aw/awf-config.json" cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json export GH_AW_MODELS_JSON_PATH="/tmp/gh-aw/models.json" + GH_AW_DOCKER_HOST="" + if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then + GH_AW_DOCKER_HOST="${DOCKER_HOST}" + fi GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="" if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="--docker-host-path-prefix /tmp/gh-aw" + python3 - <<'PY' + import json,os,subprocess as sp + from pathlib import Path + try: + p=Path(os.environ["RUNNER_TEMP"])/"gh-aw"/"awf-config.json" + c=json.loads(p.read_text()) + c["chroot"]={"binariesSourcePath":"/tmp/gh-aw","identity":{"user":sp.check_output(["id","-un"],text=True).strip(),"uid":int(sp.check_output(["id","-u"],text=True)),"gid":int(sp.check_output(["id","-g"],text=True)),"home":"/tmp/gh-aw/home"}} + out=json.dumps(c,separators=(",",":"),ensure_ascii=False)+"\n" + p.write_text(out) + Path("/tmp/gh-aw/awf-config.json").write_text(out) + except Exception as e: + raise SystemExit(f"chroot config patch failed: {e}") from e + PY fi GH_AW_TOOL_CACHE_MOUNT="" - GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:-/opt/hostedtoolcache}" + GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}" if [ -d "$GH_AW_TOOL_CACHE" ]; then if [[ "$GH_AW_TOOL_CACHE" != /opt/* ]]; then GH_AW_TOOL_CACHE_MOUNT="$GH_AW_TOOL_CACHE:$GH_AW_TOOL_CACHE:ro" fi - elif [ -d "/home/runner/work/_tool" ]; then - GH_AW_TOOL_CACHE_MOUNT="/home/runner/work/_tool:/home/runner/work/_tool:ro" fi - # shellcheck disable=SC1003 - sudo -E awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS} --env-all --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --skip-pull \ - -- /bin/bash -c 'set +o histexpand; export PATH="${RUNNER_TEMP}/gh-aw/mcp-cli/bin:$PATH" && GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:-/opt/hostedtoolcache}"; export PATH="$(find "$GH_AW_TOOL_CACHE" /opt/hostedtoolcache /home/runner/work/_tool -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; GH_AW_NPM_GLOBAL_ROOT="$(npm root -g 2>/dev/null || true)"; if [ -n "$GH_AW_NPM_GLOBAL_ROOT" ]; then export NODE_PATH="${GH_AW_NPM_GLOBAL_ROOT}${NODE_PATH:+:${NODE_PATH}}"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log + # shellcheck disable=SC1003,SC2086 + sudo -E awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST:+--docker-host "$GH_AW_DOCKER_HOST"} ${GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS} --env-all --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --skip-pull \ + -- /bin/bash -c 'set +o histexpand; export PATH="${RUNNER_TEMP}/gh-aw/mcp-cli/bin:$PATH" && : "${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"; GH_AW_TOOL_CACHE="$RUNNER_TOOL_CACHE"; export PATH="$(find "$GH_AW_TOOL_CACHE" -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; GH_AW_NPM_GLOBAL_ROOT="$(npm root -g 2>/dev/null || true)"; if [ -n "$GH_AW_NPM_GLOBAL_ROOT" ]; then export NODE_PATH="${GH_AW_NPM_GLOBAL_ROOT}${NODE_PATH:+:${NODE_PATH}}"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log env: AWF_REFLECT_ENABLED: 1 COPILOT_AGENT_RUNNER_TYPE: STANDALONE COPILOT_DUMMY_BYOK: dummy-byok-key-for-offline-mode - COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + COPILOT_GITHUB_TOKEN: | + ${{ case( + needs.pat_pool.outputs.pat_number == '0', secrets.COPILOT_PAT_0, + needs.pat_pool.outputs.pat_number == '1', secrets.COPILOT_PAT_1, + needs.pat_pool.outputs.pat_number == '2', secrets.COPILOT_PAT_2, + needs.pat_pool.outputs.pat_number == '3', secrets.COPILOT_PAT_3, + needs.pat_pool.outputs.pat_number == '4', secrets.COPILOT_PAT_4, + needs.pat_pool.outputs.pat_number == '5', secrets.COPILOT_PAT_5, + needs.pat_pool.outputs.pat_number == '6', secrets.COPILOT_PAT_6, + needs.pat_pool.outputs.pat_number == '7', secrets.COPILOT_PAT_7, + needs.pat_pool.outputs.pat_number == '8', secrets.COPILOT_PAT_8, + needs.pat_pool.outputs.pat_number == '9', secrets.COPILOT_PAT_9, + 'NO COPILOT PAT AVAILABLE') + }} COPILOT_MODEL: claude-opus-4.8 GH_AW_MAX_TURNS: ${{ vars.GH_AW_DEFAULT_MAX_TURNS || '' }} GH_AW_PHASE: agent GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} GH_AW_TIMEOUT_MINUTES: 20 - GH_AW_VERSION: v0.79.8 + GH_AW_VERSION: v0.80.9 GITHUB_API_URL: ${{ github.api_url }} GITHUB_AW: true GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows @@ -852,6 +868,7 @@ jobs: GIT_COMMITTER_EMAIL: github-actions[bot]@users.noreply.github.com GIT_COMMITTER_NAME: github-actions[bot] RUNNER_TEMP: ${{ runner.temp }} + TRACEPARENT: ${{ env.GITHUB_AW_OTEL_TRACE_ID != '' && env.GITHUB_AW_OTEL_PARENT_SPAN_ID != '' && format('00-{0}-{1}-01', env.GITHUB_AW_OTEL_TRACE_ID, env.GITHUB_AW_OTEL_PARENT_SPAN_ID) || '' }} - name: Detect agent errors if: always() id: detect-agent-errors @@ -859,17 +876,10 @@ jobs: run: node "${RUNNER_TEMP}/gh-aw/actions/detect_agent_errors.cjs" - name: Configure Git credentials env: - REPO_NAME: ${{ github.repository }} - SERVER_URL: ${{ github.server_url }} + GITHUB_REPOSITORY: ${{ github.repository }} + GITHUB_SERVER_URL: ${{ github.server_url }} GITHUB_TOKEN: ${{ github.token }} - run: | - git config --global user.email "github-actions[bot]@users.noreply.github.com" - git config --global user.name "github-actions[bot]" - git config --global am.keepcr true - # Re-authenticate git with GitHub token - SERVER_URL_STRIPPED="${SERVER_URL#https://}" - git remote set-url origin "https://x-access-token:${GITHUB_TOKEN}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" - echo "Git configured with standard GitHub Actions identity" + run: bash "${RUNNER_TEMP}/gh-aw/actions/configure_git_credentials.sh" - name: Copy Copilot session state files to logs if: always() continue-on-error: true @@ -893,8 +903,17 @@ jobs: const { main } = require('${{ runner.temp }}/gh-aw/actions/redact_secrets.cjs'); await main(); env: - GH_AW_SECRET_NAMES: 'COPILOT_GITHUB_TOKEN,GITHUB_TOKEN' - SECRET_COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + GH_AW_SECRET_NAMES: 'COPILOT_PAT_0,COPILOT_PAT_1,COPILOT_PAT_2,COPILOT_PAT_3,COPILOT_PAT_4,COPILOT_PAT_5,COPILOT_PAT_6,COPILOT_PAT_7,COPILOT_PAT_8,COPILOT_PAT_9,GITHUB_TOKEN' + SECRET_COPILOT_PAT_0: ${{ secrets.COPILOT_PAT_0 }} + SECRET_COPILOT_PAT_1: ${{ secrets.COPILOT_PAT_1 }} + SECRET_COPILOT_PAT_2: ${{ secrets.COPILOT_PAT_2 }} + SECRET_COPILOT_PAT_3: ${{ secrets.COPILOT_PAT_3 }} + SECRET_COPILOT_PAT_4: ${{ secrets.COPILOT_PAT_4 }} + SECRET_COPILOT_PAT_5: ${{ secrets.COPILOT_PAT_5 }} + SECRET_COPILOT_PAT_6: ${{ secrets.COPILOT_PAT_6 }} + SECRET_COPILOT_PAT_7: ${{ secrets.COPILOT_PAT_7 }} + SECRET_COPILOT_PAT_8: ${{ secrets.COPILOT_PAT_8 }} + SECRET_COPILOT_PAT_9: ${{ secrets.COPILOT_PAT_9 }} SECRET_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Append agent step summary if: always() @@ -1017,12 +1036,13 @@ jobs: - activation - agent - detection + - pat_pool - safe_outputs if: > always() && (needs.agent.result != 'skipped' || needs.activation.outputs.lockdown_check_failed == 'true' || needs.activation.outputs.stale_lock_file_failed == 'true') runs-on: ubuntu-slim - environment: copilot-pr-reviewer + environment: copilot-pat-pool permissions: contents: read pull-requests: write @@ -1038,7 +1058,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 + uses: github/gh-aw-actions/setup@8c7d04ebf1ece56cd381446125da3e0f6896294a # v0.80.9 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -1047,8 +1067,8 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Android Tools PR Reviewer" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/android-tools-reviewer.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.60" - GH_AW_INFO_AWF_VERSION: "v0.27.2" + GH_AW_INFO_VERSION: "1.0.63" + GH_AW_INFO_AWF_VERSION: "v0.27.7" GH_AW_INFO_ENGINE_ID: "copilot" - name: Download agent output artifact id: download-agent-output @@ -1070,12 +1090,14 @@ jobs: run: | mkdir -p /tmp/gh-aw/usage/agent /tmp/gh-aw/usage/detection echo "Usage artifact source file status:" - for file in /tmp/gh-aw/aw-info.jsonl /tmp/gh-aw/agent_usage.jsonl /tmp/gh-aw/detection_usage.jsonl /tmp/gh-aw/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/threat-detection/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/threat-detection/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/threat-detection/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl; do + for file in /tmp/gh-aw/aw_info.json /tmp/gh-aw/aw-info.jsonl /tmp/gh-aw/agent_usage.jsonl /tmp/gh-aw/detection_usage.jsonl /tmp/gh-aw/github_rate_limits.jsonl /tmp/gh-aw/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/threat-detection/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/threat-detection/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/threat-detection/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl; do [ -f "$file" ] && echo "FOUND: $file" || echo "MISSING: $file" done + [ -f /tmp/gh-aw/aw_info.json ] && cp /tmp/gh-aw/aw_info.json /tmp/gh-aw/usage/aw_info.json || true [ -f /tmp/gh-aw/aw-info.jsonl ] && cp /tmp/gh-aw/aw-info.jsonl /tmp/gh-aw/usage/aw-info.jsonl || true [ -f /tmp/gh-aw/agent_usage.jsonl ] && cp /tmp/gh-aw/agent_usage.jsonl /tmp/gh-aw/usage/agent_usage.jsonl || true [ -f /tmp/gh-aw/detection_usage.jsonl ] && cp /tmp/gh-aw/detection_usage.jsonl /tmp/gh-aw/usage/detection_usage.jsonl || true + [ -f /tmp/gh-aw/github_rate_limits.jsonl ] && cp /tmp/gh-aw/github_rate_limits.jsonl /tmp/gh-aw/usage/github_rate_limits.jsonl || true [ -f /tmp/gh-aw/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl || true [ -f /tmp/gh-aw/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl || true [ -f /tmp/gh-aw/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl || true @@ -1084,6 +1106,8 @@ jobs: [ -f /tmp/gh-aw/threat-detection/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/threat-detection/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl || true [ -f /tmp/gh-aw/usage/agent/token_usage.jsonl ] || : > /tmp/gh-aw/usage/agent/token_usage.jsonl [ -f /tmp/gh-aw/usage/detection/token_usage.jsonl ] || : > /tmp/gh-aw/usage/detection/token_usage.jsonl + mkdir -p /tmp/gh-aw/usage/activity + node ${{ runner.temp }}/gh-aw/actions/generate_usage_activity_summary.cjs find /tmp/gh-aw/usage -type f -print | sort - name: Upload usage artifact if: always() @@ -1092,11 +1116,14 @@ jobs: with: name: usage path: | + /tmp/gh-aw/usage/aw_info.json /tmp/gh-aw/usage/aw-info.jsonl /tmp/gh-aw/usage/agent_usage.jsonl /tmp/gh-aw/usage/detection_usage.jsonl + /tmp/gh-aw/usage/github_rate_limits.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl + /tmp/gh-aw/usage/activity/summary.json if-no-files-found: ignore - name: Process no-op messages id: noop @@ -1234,7 +1261,7 @@ jobs: if: > always() && needs.agent.result != 'skipped' && (needs.agent.outputs.output_types != '' || needs.agent.outputs.has_patch == 'true') runs-on: ubuntu-latest - environment: copilot-pr-reviewer + environment: copilot-pat-pool permissions: contents: read outputs: @@ -1245,7 +1272,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 + uses: github/gh-aw-actions/setup@8c7d04ebf1ece56cd381446125da3e0f6896294a # v0.80.9 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -1254,8 +1281,8 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Android Tools PR Reviewer" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/android-tools-reviewer.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.60" - GH_AW_INFO_AWF_VERSION: "v0.27.2" + GH_AW_INFO_VERSION: "1.0.63" + GH_AW_INFO_AWF_VERSION: "v0.27.7" GH_AW_INFO_ENGINE_ID: "copilot" - name: Download agent output artifact id: download-agent-output @@ -1273,7 +1300,7 @@ jobs: echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT" - name: Checkout repository for patch context if: needs.agent.outputs.has_patch == 'true' - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: persist-credentials: false # --- Threat Detection --- @@ -1282,7 +1309,7 @@ jobs: rm -rf /tmp/gh-aw/sandbox/firewall/logs rm -rf /tmp/gh-aw/sandbox/firewall/audit - name: Download container images - run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.2@sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6 ghcr.io/github/gh-aw-firewall/api-proxy:0.27.2@sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4 ghcr.io/github/gh-aw-firewall/squid:0.27.2@sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591 + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.7@sha256:aae231e4635c8999d039c132f1602d3df850fe9b84a00aa2b5ac981179b5661c ghcr.io/github/gh-aw-firewall/api-proxy:0.27.7@sha256:009caf2e3d88fa77b64e9a03a95a228fc58db0f1701c6d324b29ba5a3c7c79b6 ghcr.io/github/gh-aw-firewall/squid:0.27.7@sha256:deb1d4e19de62d51cee0508057a596a19315c3423ada4d675cad136dc8037c96 - name: Check if detection needed id: detection_guard if: always() @@ -1345,11 +1372,11 @@ jobs: node-version: '24' package-manager-cache: false - name: Install GitHub Copilot CLI - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.60 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.63 env: GH_HOST: github.com - name: Install AWF binary - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.2 + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.7 - name: Execute GitHub Copilot CLI if: always() && steps.detection_guard.outputs.run_detection == 'true' continue-on-error: true @@ -1368,37 +1395,56 @@ jobs: export GH_AW_NODE_BIN export COPILOT_API_KEY="$COPILOT_DUMMY_BYOK" (umask 177 && touch /tmp/gh-aw/threat-detection/detection.log) - GH_AW_MAX_AI_CREDITS="${{ vars.GH_AW_DEFAULT_DETECTION_MAX_AI_CREDITS || '400' }}" - printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.2/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"github.com\",\"host.docker.internal\",\"registry.npmjs.org\",\"telemetry.enterprise.githubcopilot.com\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS}},\"container\":{\"imageTag\":\"0.27.2,squid=sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591,agent=sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6,api-proxy=sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4,cli-proxy=sha256:02f3ec08f32dc26c5427920c6a2e2f3036238fce44802f2f11ef49ed8621b5d0\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json" + GH_AW_MAX_AI_CREDITS="${GH_AW_MAX_AI_CREDITS:-400}" + printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.7/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"github.com\",\"host.docker.internal\",\"registry.npmjs.org\",\"telemetry.enterprise.githubcopilot.com\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5},\"container\":{\"imageTag\":\"0.27.7,squid=sha256:deb1d4e19de62d51cee0508057a596a19315c3423ada4d675cad136dc8037c96,agent=sha256:aae231e4635c8999d039c132f1602d3df850fe9b84a00aa2b5ac981179b5661c,api-proxy=sha256:009caf2e3d88fa77b64e9a03a95a228fc58db0f1701c6d324b29ba5a3c7c79b6,cli-proxy=sha256:4757f198a3fa20f88bdbe70be7ae1a05f127d9c0a9e96a5d6460ef40c08fc83d\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json" cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json export GH_AW_MODELS_JSON_PATH="/tmp/gh-aw/models.json" + GH_AW_DOCKER_HOST="" + if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then + GH_AW_DOCKER_HOST="${DOCKER_HOST}" + fi GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="" if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="--docker-host-path-prefix /tmp/gh-aw" + _GH_AW_CHROOT_JSON=$(jq -c --arg src /tmp/gh-aw --arg user "$(id -un)" --argjson uid "$(id -u)" --argjson gid "$(id -g)" --arg home /tmp/gh-aw/home '.chroot={"binariesSourcePath":$src,"identity":{"user":$user,"uid":$uid,"gid":$gid,"home":$home}}' "${RUNNER_TEMP}/gh-aw/awf-config.json") || { echo "chroot config patch failed" >&2; exit 1; } + printf '%s\n' "$_GH_AW_CHROOT_JSON" > "${RUNNER_TEMP}/gh-aw/awf-config.json" + printf '%s\n' "$_GH_AW_CHROOT_JSON" > "/tmp/gh-aw/awf-config.json" fi GH_AW_TOOL_CACHE_MOUNT="" - GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:-/opt/hostedtoolcache}" + GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}" if [ -d "$GH_AW_TOOL_CACHE" ]; then if [[ "$GH_AW_TOOL_CACHE" != /opt/* ]]; then GH_AW_TOOL_CACHE_MOUNT="$GH_AW_TOOL_CACHE:$GH_AW_TOOL_CACHE:ro" fi - elif [ -d "/home/runner/work/_tool" ]; then - GH_AW_TOOL_CACHE_MOUNT="/home/runner/work/_tool:/home/runner/work/_tool:ro" fi - # shellcheck disable=SC1003 - sudo -E awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS} --env-all --exclude-env COPILOT_GITHUB_TOKEN --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --skip-pull \ - -- /bin/bash -c 'set +o histexpand; GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:-/opt/hostedtoolcache}"; export PATH="$(find "$GH_AW_TOOL_CACHE" /opt/hostedtoolcache /home/runner/work/_tool -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; GH_AW_NPM_GLOBAL_ROOT="$(npm root -g 2>/dev/null || true)"; if [ -n "$GH_AW_NPM_GLOBAL_ROOT" ]; then export NODE_PATH="${GH_AW_NPM_GLOBAL_ROOT}${NODE_PATH:+:${NODE_PATH}}"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log + # shellcheck disable=SC1003,SC2086 + sudo -E awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST:+--docker-host "$GH_AW_DOCKER_HOST"} ${GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS} --env-all --exclude-env COPILOT_GITHUB_TOKEN --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --skip-pull \ + -- /bin/bash -c 'set +o histexpand; : "${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"; GH_AW_TOOL_CACHE="$RUNNER_TOOL_CACHE"; export PATH="$(find "$GH_AW_TOOL_CACHE" -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; GH_AW_NPM_GLOBAL_ROOT="$(npm root -g 2>/dev/null || true)"; if [ -n "$GH_AW_NPM_GLOBAL_ROOT" ]; then export NODE_PATH="${GH_AW_NPM_GLOBAL_ROOT}${NODE_PATH:+:${NODE_PATH}}"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log env: AWF_REFLECT_ENABLED: 1 COPILOT_AGENT_RUNNER_TYPE: STANDALONE COPILOT_DUMMY_BYOK: dummy-byok-key-for-offline-mode - COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + COPILOT_GITHUB_TOKEN: | + ${{ case( + needs.pat_pool.outputs.pat_number == '0', secrets.COPILOT_PAT_0, + needs.pat_pool.outputs.pat_number == '1', secrets.COPILOT_PAT_1, + needs.pat_pool.outputs.pat_number == '2', secrets.COPILOT_PAT_2, + needs.pat_pool.outputs.pat_number == '3', secrets.COPILOT_PAT_3, + needs.pat_pool.outputs.pat_number == '4', secrets.COPILOT_PAT_4, + needs.pat_pool.outputs.pat_number == '5', secrets.COPILOT_PAT_5, + needs.pat_pool.outputs.pat_number == '6', secrets.COPILOT_PAT_6, + needs.pat_pool.outputs.pat_number == '7', secrets.COPILOT_PAT_7, + needs.pat_pool.outputs.pat_number == '8', secrets.COPILOT_PAT_8, + needs.pat_pool.outputs.pat_number == '9', secrets.COPILOT_PAT_9, + 'NO COPILOT PAT AVAILABLE') + }} COPILOT_MODEL: claude-opus-4.8 + GH_AW_MAX_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_DETECTION_MAX_AI_CREDITS || '400' }} GH_AW_MAX_TURNS: ${{ vars.GH_AW_DEFAULT_MAX_TURNS || '' }} GH_AW_PHASE: detection GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt GH_AW_TIMEOUT_MINUTES: 20 - GH_AW_VERSION: v0.79.8 + GH_AW_VERSION: v0.80.9 GITHUB_API_URL: ${{ github.api_url }} GITHUB_AW: true GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows @@ -1412,6 +1458,7 @@ jobs: GIT_COMMITTER_EMAIL: github-actions[bot]@users.noreply.github.com GIT_COMMITTER_NAME: github-actions[bot] RUNNER_TEMP: ${{ runner.temp }} + TRACEPARENT: ${{ env.GITHUB_AW_OTEL_TRACE_ID != '' && env.GITHUB_AW_OTEL_PARENT_SPAN_ID != '' && format('00-{0}-{1}-01', env.GITHUB_AW_OTEL_TRACE_ID, env.GITHUB_AW_OTEL_PARENT_SPAN_ID) || '' }} - name: Parse threat detection token usage for step summary id: parse_detection_token_usage if: always() @@ -1465,10 +1512,83 @@ jobs: } } + pat_pool: + needs: pre_activation + runs-on: ubuntu-slim + environment: copilot-pat-pool + outputs: + pat_number: ${{ steps.select-pat-number.outputs.copilot_pat_number }} + steps: + - name: Configure GH_HOST for enterprise compatibility + id: ghes-host-config + shell: bash + run: | # zizmor: ignore[github-env] - GITHUB_SERVER_URL is set by GitHub Actions, not user input. + # Derive GH_HOST from GITHUB_SERVER_URL so the gh CLI targets the correct + # GitHub instance (GHES/GHEC). On github.com this is a harmless no-op. + GH_HOST="${GITHUB_SERVER_URL#https://}" + GH_HOST="${GH_HOST#http://}" + echo "GH_HOST=${GH_HOST}" >> "$GITHUB_ENV" + - name: Select Copilot token from pool + id: select-pat-number + run: | + # Collect pool entries with non-empty secrets from COPILOT_PAT_0..COPILOT_PAT_9. + PAT_NUMBERS=() + POOL_INDICATORS=(➖ ➖ ➖ ➖ ➖ ➖ ➖ ➖ ➖ ➖) + + for i in $(seq 0 9); do + var="COPILOT_PAT_${i}" + val="${!var}" + if [ -n "$val" ]; then + PAT_NUMBERS+=(${i}) + POOL_INDICATORS[${i}]="🟪" + fi + done + + # If none of the entries in the pool have values, emit a warning + # and do not set an output value. The consumer can fall back to + # using COPILOT_GITHUB_TOKEN. + if [ ${#PAT_NUMBERS[@]} -eq 0 ]; then + warning_message="::warning::None of the PAT pool entries had values " + warning_message+="(checked COPILOT_PAT_0 through COPILOT_PAT_9)" + echo "$warning_message" + exit 0 + fi + + # Select a random index using the seed if specified. + if [ -n "$RANDOM_SEED" ]; then + RANDOM=$RANDOM_SEED + fi + + PAT_INDEX=$(( RANDOM % ${#PAT_NUMBERS[@]} )) + PAT_NUMBER="${PAT_NUMBERS[$PAT_INDEX]}" + POOL_INDICATORS[${PAT_NUMBER}]="✅" + + echo "Pool size: ${#PAT_NUMBERS[@]}" + echo "Selected PAT number ${PAT_NUMBER} (index: ${PAT_INDEX})" + + echo "|0|1|2|3|4|5|6|7|8|9|" >> "$GITHUB_STEP_SUMMARY" + echo "|-|-|-|-|-|-|-|-|-|-|" >> "$GITHUB_STEP_SUMMARY" + (IFS='|'; printf '|%s' "${POOL_INDICATORS[@]}"; printf '|\n') >> "$GITHUB_STEP_SUMMARY" + + echo "copilot_pat_number=${PAT_NUMBER}" >> "$GITHUB_OUTPUT" + env: + COPILOT_PAT_0: ${{ secrets.COPILOT_PAT_0 }} + COPILOT_PAT_1: ${{ secrets.COPILOT_PAT_1 }} + COPILOT_PAT_2: ${{ secrets.COPILOT_PAT_2 }} + COPILOT_PAT_3: ${{ secrets.COPILOT_PAT_3 }} + COPILOT_PAT_4: ${{ secrets.COPILOT_PAT_4 }} + COPILOT_PAT_5: ${{ secrets.COPILOT_PAT_5 }} + COPILOT_PAT_6: ${{ secrets.COPILOT_PAT_6 }} + COPILOT_PAT_7: ${{ secrets.COPILOT_PAT_7 }} + COPILOT_PAT_8: ${{ secrets.COPILOT_PAT_8 }} + COPILOT_PAT_9: ${{ secrets.COPILOT_PAT_9 }} + RANDOM_SEED: ${{ github.aw.import-inputs.random_seed }} + shell: bash + pre_activation: if: "(github.event_name != 'issue_comment' && github.event_name != 'pull_request_review_comment' || contains(fromJSON('[\"OWNER\",\"MEMBER\",\"COLLABORATOR\"]'), github.event.comment.author_association)) && (github.event_name == 'issue_comment' && (startsWith(github.event.comment.body, '/review ') || startsWith(github.event.comment.body, '/review\n') || github.event.comment.body == '/review') && github.event.issue.pull_request != null || !(github.event_name == 'issue_comment'))" runs-on: ubuntu-slim - environment: copilot-pr-reviewer + environment: copilot-pat-pool outputs: activated: ${{ steps.check_membership.outputs.is_team_member == 'true' && steps.check_command_position.outputs.command_position_ok == 'true' }} matched_command: ${{ steps.check_command_position.outputs.matched_command }} @@ -1478,15 +1598,15 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 + uses: github/gh-aw-actions/setup@8c7d04ebf1ece56cd381446125da3e0f6896294a # v0.80.9 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} env: GH_AW_SETUP_WORKFLOW_NAME: "Android Tools PR Reviewer" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/android-tools-reviewer.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.60" - GH_AW_INFO_AWF_VERSION: "v0.27.2" + GH_AW_INFO_VERSION: "1.0.63" + GH_AW_INFO_AWF_VERSION: "v0.27.7" GH_AW_INFO_ENGINE_ID: "copilot" - name: Check team membership for command workflow id: check_membership @@ -1519,7 +1639,7 @@ jobs: - detection if: (!cancelled()) && needs.agent.result != 'skipped' && needs.detection.result == 'success' runs-on: ubuntu-slim - environment: copilot-pr-reviewer + environment: copilot-pat-pool permissions: contents: read pull-requests: write @@ -1535,7 +1655,7 @@ jobs: GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens }} GH_AW_ENGINE_ID: "copilot" GH_AW_ENGINE_MODEL: "claude-opus-4.8" - GH_AW_ENGINE_VERSION: "1.0.60" + GH_AW_ENGINE_VERSION: "1.0.63" GH_AW_THREAT_DETECTION_AIC: ${{ needs.detection.outputs.aic }} GH_AW_WORKFLOW_ID: "android-tools-reviewer" GH_AW_WORKFLOW_NAME: "Android Tools PR Reviewer" @@ -1550,7 +1670,7 @@ jobs: steps: - name: Setup Scripts id: setup - uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 + uses: github/gh-aw-actions/setup@8c7d04ebf1ece56cd381446125da3e0f6896294a # v0.80.9 with: destination: ${{ runner.temp }}/gh-aw/actions job-name: ${{ github.job }} @@ -1559,8 +1679,8 @@ jobs: env: GH_AW_SETUP_WORKFLOW_NAME: "Android Tools PR Reviewer" GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/android-tools-reviewer.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.60" - GH_AW_INFO_AWF_VERSION: "v0.27.2" + GH_AW_INFO_VERSION: "1.0.63" + GH_AW_INFO_AWF_VERSION: "v0.27.7" GH_AW_INFO_ENGINE_ID: "copilot" - name: Download agent output artifact id: download-agent-output @@ -1579,8 +1699,7 @@ jobs: - name: Configure GH_HOST for enterprise compatibility id: ghes-host-config shell: bash - # zizmor: ignore[github-env] - GITHUB_SERVER_URL is set by GitHub Actions, not user input. - run: | + run: | # zizmor: ignore[github-env] - GITHUB_SERVER_URL is set by GitHub Actions, not user input. # Derive GH_HOST from GITHUB_SERVER_URL so the gh CLI targets the correct # GitHub instance (GHES/GHEC). On github.com this is a harmless no-op. GH_HOST="${GITHUB_SERVER_URL#https://}" diff --git a/external/xamarin-android-tools/.github/workflows/android-tools-reviewer.md b/external/xamarin-android-tools/.github/workflows/android-tools-reviewer.md index 87c281c328f..9db4f6deb3e 100644 --- a/external/xamarin-android-tools/.github/workflows/android-tools-reviewer.md +++ b/external/xamarin-android-tools/.github/workflows/android-tools-reviewer.md @@ -4,13 +4,41 @@ on: name: review events: [pull_request_comment] roles: [admin, maintainer, write] -environment: copilot-pr-reviewer + +# ############################################################### +# Select a PAT from the pool and override COPILOT_GITHUB_TOKEN. +# Run agentic jobs in an isolated `copilot-pat-pool` environment. +# +# When org-level billing is available, this will be removed. +# See `shared/pat_pool.README.md` for more information. +# ############################################################### +imports: + - uses: shared/pat_pool.md + with: + environment: copilot-pat-pool + +environment: copilot-pat-pool permissions: contents: read pull-requests: read engine: id: copilot model: claude-opus-4.8 + env: + COPILOT_GITHUB_TOKEN: | + ${{ case( + needs.pat_pool.outputs.pat_number == '0', secrets.COPILOT_PAT_0, + needs.pat_pool.outputs.pat_number == '1', secrets.COPILOT_PAT_1, + needs.pat_pool.outputs.pat_number == '2', secrets.COPILOT_PAT_2, + needs.pat_pool.outputs.pat_number == '3', secrets.COPILOT_PAT_3, + needs.pat_pool.outputs.pat_number == '4', secrets.COPILOT_PAT_4, + needs.pat_pool.outputs.pat_number == '5', secrets.COPILOT_PAT_5, + needs.pat_pool.outputs.pat_number == '6', secrets.COPILOT_PAT_6, + needs.pat_pool.outputs.pat_number == '7', secrets.COPILOT_PAT_7, + needs.pat_pool.outputs.pat_number == '8', secrets.COPILOT_PAT_8, + needs.pat_pool.outputs.pat_number == '9', secrets.COPILOT_PAT_9, + 'NO COPILOT PAT AVAILABLE') + }} max-daily-ai-credits: -1 max-ai-credits: -1 network: diff --git a/external/xamarin-android-tools/.github/workflows/shared/pat_pool.README.md b/external/xamarin-android-tools/.github/workflows/shared/pat_pool.README.md new file mode 100644 index 00000000000..2f10fc62a2a --- /dev/null +++ b/external/xamarin-android-tools/.github/workflows/shared/pat_pool.README.md @@ -0,0 +1,205 @@ +# PAT Pool + +Selects a random Copilot PAT from a numbered pool of secrets. This addresses limitations that arise from having a single PAT shared across all agentic workflows, such as rate-limiting. + +**This is a stop-gap workaround.** As soon as organization/enterprise billing is available to the dotnet org, this approach will be removed from our workflows. + +## Repository Onboarding + +To use Agentic Workflows in a dotnet org repository: + +1. Follow the instructions for [Configuring Your Repository | Agentic Authoring | GitHub Agentic Workflows][configure-repo]. Use `gh aw` **v0.80.9 or newer**, which supports the agent job dependencies required for this implementation. +2. Copy the `pat_pool.md` and `pat_pool.README.md` files into the repository under `.github/workflows/shared`. +3. Merge those additions into the repository and then follow the instructions for the PAT Creation and Usage below. + +**Install or upgrade the `gh aw` CLI and check the version** + +```sh +gh extension install github/gh-aw --force +gh aw --version +``` + +### Environment + +Create an environment for the agentic workflows: + - _Configuring these settings requires repo admin permission_ + - https://github.com/dotnet/{repo}/settings/environments + - Recommended Name: **copilot-pat-pool** + - Recommended Deployment branches and tags: **Protected branches only** + +This environment is used for all agentic workflows, restricting agentic workflows to the repo's protected branches and preventing the workflows from accessing secrets defined for other environments. + +## PAT Management + +Team members provide PATs into the pool with secret names matching the pattern of `_<0-9>`, such as `COPILOT_PAT_0`. + +[Use this link to prefill the PAT creation form with the required settings][create-pat]: + +1. **Resource owner** is your **user account**, not an organization. +2. **Copilot Requests (Read)** must be the only permission granted. +3. **8-day expiration** must be used, which enforces a weekly renewal. +4. **Repository access** set to **Public repositories** only. + +The **Token Name** _does not_ need to match the secret name and is only visible to the owner of the PAT. It's recommended to use a token name indicating the PAT is used for dotnet org agentic workflows. The **Description** is also only used for your own reference. + +Team members providing PATs for workflows should set weekly recurring reminders to regenerate and update their PATs in the PAT pool. With an 8-day expiration, renewal can be done on the same day each week. + +## PAT Pool Secrets + +For a PAT pool that is specific to an environment, PATs can be added to repositories as **Environment Secrets** for the environment created above. _This requires repo admin permission_. + +* **Settings** > + * **Environments** > + * **copilot-pat-pool** (or other environment name) > + * **Add environment secret** (or edit your existing secret) + * Enter your secret name of `COPILOT_PAT_{0-9}` and paste in your PAT + +This can also be accomplished using the `gh` CLI, specifying the repo and environment arguments. + +```sh +# Register the PAT secret. This will prompt for you to paste the PAT. +gh secret set "_<0-9>" --repo / --env "copilot-pat-pool" +``` + +It's also helpful to record who owns each PAT within the pool. To capture which team member is associated with each PAT, a `_<0-9>_` "sidecar secret" can be added alongside the PAT secret to make the username for the PAT pool entry visible. This sidecar secret must have a non-empty value, but it's never consumed, so any value is sufficient. + +```sh +# Record a sidecar secret that presents who owns this PAT. +gh secret set "_<0-9>_" --body "" --repo / --env "copilot-pat-pool" +``` + +## Workflow Output Attribution + +Team members' PATs are _only_ used for the Copilot requests from within the agentic portion of the workflow. All outputs from the workflow use the `github-actions[bot]` account token. Issues, PRs, comments, and all other content generated by the workflow will be attributed to `github-actions[bot]`--not the team member's account or token. + +## Usage + +The [`pat_pool.md`](./pat_pool.md) workflow import defines a custom job with a `pat_number` output. Consuming workflows need two additions to their frontmatter to import this job and use the PAT number to override the `COPILOT_GITHUB_TOKEN` passed to the workflow's agent job. + +```yml +# ############################################################### +# Select a PAT from the pool and override COPILOT_GITHUB_TOKEN. +# Run agentic jobs in an isolated `copilot-pat-pool` environment. +# +# When org-level billing is available, this will be removed. +# See `shared/pat_pool.README.md` for more information. +# ############################################################### +imports: + - uses: shared/pat_pool.md + with: + environment: copilot-pat-pool + +environment: copilot-pat-pool + +engine: + id: copilot + env: + COPILOT_GITHUB_TOKEN: | + ${{ case( + needs.pat_pool.outputs.pat_number == '0', secrets.COPILOT_PAT_0, + needs.pat_pool.outputs.pat_number == '1', secrets.COPILOT_PAT_1, + needs.pat_pool.outputs.pat_number == '2', secrets.COPILOT_PAT_2, + needs.pat_pool.outputs.pat_number == '3', secrets.COPILOT_PAT_3, + needs.pat_pool.outputs.pat_number == '4', secrets.COPILOT_PAT_4, + needs.pat_pool.outputs.pat_number == '5', secrets.COPILOT_PAT_5, + needs.pat_pool.outputs.pat_number == '6', secrets.COPILOT_PAT_6, + needs.pat_pool.outputs.pat_number == '7', secrets.COPILOT_PAT_7, + needs.pat_pool.outputs.pat_number == '8', secrets.COPILOT_PAT_8, + needs.pat_pool.outputs.pat_number == '9', secrets.COPILOT_PAT_9, + 'NO COPILOT PAT AVAILABLE') + }} +``` + +The `COPILOT_GITHUB_TOKEN` expression can be collapsed onto a single line if desired. `gh-aw compile` automatically wires `pat_pool` into the activation and agent jobs' `needs:` graph because of the `needs.pat_pool.` references within the `engine.env` property. + +```sh +gh aw compile --schedule-seed / +``` + +### Specifying the environment + +The `environment` must be specified both to the `pat_pool.md` import and to the containing workflow to ensure both jobs access the PAT pool from the same environment. The `copilot-pat-pool` environment name is recommended as the isolated environment for agentic workflows that use the PAT pool. + +### Customizing the pool + +The import declares 10 optional inputs (`COPILOT_PAT_0` through `COPILOT_PAT_9`), each defaulting to `secrets.COPILOT_PAT_#` of the matching number. To point a workflow at a different pool of repository secrets, use the parameterized `uses`/`with` form when importing and pass the substitute secrets as the `COPILOT_PAT_#` inputs: + +```yml +imports: + - uses: shared/pat_pool.md + with: + COPILOT_PAT_0: ${{ secrets.MY_TEAM_PAT_0 }} + COPILOT_PAT_1: ${{ secrets.MY_TEAM_PAT_1 }} + # Unspecified inputs default to `secrets.COPILOT_PAT_#` lookups +``` + +The secrets passed via `with:` must match the secrets referenced in the consuming workflow's `case` expression that overrides `COPILOT_GITHUB_TOKEN`--both sides need to agree on which secret backs each `COPILOT_PAT_#` slot. Update the `case` expression accordingly: + +```yml +engine: + id: copilot + env: + COPILOT_GITHUB_TOKEN: ${{ case(needs.pat_pool.outputs.pat_number == '0', secrets.MY_TEAM_PAT_0, needs.pat_pool.outputs.pat_number == '1', secrets.MY_TEAM_PAT_1, ..., 'NO COPILOT PAT AVAILABLE') }} +``` + +This approach aligns with GitHub's documented guidance for [passing secrets][passing-secrets] between workflows, where the `pat_pool` job returns a PAT number and the `case` statement acts as a secret store to look the PAT secret up based on the selected number. + +## Design / Security + +There are several details of this implementation that keep our workflows and repositories safe. + +1. **Secrets adhere to existing trust boundaries.** The pool of PAT secrets is provided to a dedicated step within the `pat_pool` job. That job runs after `pre_activation` and contains only the trusted checkout and action steps--no untrusted context or input is within scope. The `select-pat-number` action only references the secret values to determine which are non-empty, filtering the secret numbers to those with values. +1. **The `pat_pool` job emits only a number, never a secret.** Its sole output, `pat_number`, is the 0-9 index of the selected PAT (or empty when the pool is empty). The actual secret materializes only later, in the activation job's `engine.env` mapping, where the `case()` expression resolves the number to the matching secret. This follows GitHub's guidance for [passing secrets][passing-secrets] between jobs or workflows, with the `case` statement acting as a very simple secret store. +1. **The `select-pat-number` action does not require any permissions.** It reads only the `COPILOT_PAT_#` environment variables passed to it and writes only to `GITHUB_OUTPUT`. The job that hosts it sets `permissions:` to the workflow defaults (no elevated scopes). +1. **The implementation uses supported Agentic Workflow extensibility hooks.** Defining a custom job inside an [imported workflow file][imports] is supported by `gh aw compile`. gh-aw automatically wires `pat_pool` into the activation job's `needs:` graph based on the `needs.pat_pool.outputs.pat_number` references in `engine.env`. The [secret override][secret-override] capability supplies the `COPILOT_GITHUB_TOKEN` value via `engine.env` rather than the default secret of the same name. + +Each of the references below contributed to the design and implementation to ensure a secure and reliable design. + +## Known Issues + +The `pat_pool` import integration requires that the workflow's compilation results in a `pre_activation` job. If nothing in your workflow definition produces a `pre_activation` job, a compilation error will be received. + +```text +✗ Failed workflows: + ✗ .md + +.github\workflows\.md:1:1: error: failed to generate YAML: failed to build and validate jobs: job 'pat_pool' depends on non-existent job 'pre_activation' +``` + +To work around this, add `on.permissions: {}` to your workflow, which forces a no-op `pre_activation` job to be generated. + +```yml +on: + permissions: {} +``` + +See: [Activation 'needs' does not incorporate jobs in engine.env expressions (github/gh-aw#30790)](https://github.com/github/gh-aw/issues/30790) + +## References + +- [Agentic Workflows CLI Extension][cli-setup] +- [Agentic Authoring][configure-repo] +- [Authentication][authentication] +- [Agentic Workflow Imports][imports] +- [Custom Steps][steps] +- [Custom Jobs][jobs] +- [Job Outputs][job-outputs] +- [Engine Configuration][engine] +- [Engine Environment Variables][engine-vars] +- [Update agentic engine token handling to use user-provided secrets (github/gh-aw#18017)][secret-override] +- [Case Function in Workflow Expressions][case-expression] +- [Passing a secret between jobs or workflows][passing-secrets] + +[cli-setup]: https://github.github.com/gh-aw/setup/cli/ +[configure-repo]: https://github.github.com/gh-aw/guides/agentic-authoring/#configuring-your-repository +[authentication]: https://github.github.com/gh-aw/reference/auth/ +[create-pat]: https://github.com/settings/personal-access-tokens/new?name=dotnet%20org%20agentic%20workflows&description=GitHub+Agentic+Workflows+-+Copilot+engine+authentication.++Used+for+dotnet+org+workflows.+MUST+be+configured+with+only+Copilot+Requests+permissions+and+user+account+as+resource+owner.+Weekly+expiration+and+required+renewal.&user_copilot_requests=read&expires_in=8 +[imports]: https://github.github.com/gh-aw/reference/imports/ +[steps]: https://github.github.com/gh-aw/reference/frontmatter/#custom-steps-steps +[jobs]: https://github.github.com/gh-aw/reference/frontmatter/#custom-jobs-jobs +[job-outputs]: https://github.github.com/gh-aw/reference/frontmatter/#job-outputs +[engine]: https://github.github.com/gh-aw/reference/frontmatter/#ai-engine-engine +[engine-vars]: https://github.github.com/gh-aw/reference/engines/#engine-environment-variables +[secret-override]: https://github.com/github/gh-aw/pull/18017 +[case-expression]: https://docs.github.com/actions/reference/workflows-and-actions/expressions#case +[passing-secrets]: https://docs.github.com/actions/reference/workflows-and-actions/workflow-commands#example-masking-and-passing-a-secret-between-jobs-or-workflows diff --git a/external/xamarin-android-tools/.github/workflows/shared/pat_pool.md b/external/xamarin-android-tools/.github/workflows/shared/pat_pool.md new file mode 100644 index 00000000000..7d9b3b974e6 --- /dev/null +++ b/external/xamarin-android-tools/.github/workflows/shared/pat_pool.md @@ -0,0 +1,119 @@ +--- +description: Agentic workflow import to integrate the Copilot PAT Pool + +jobs: + pat_pool: + environment: ${{ github.aw.import-inputs.environment }} + needs: [pre_activation] + runs-on: ubuntu-slim + outputs: + pat_number: ${{ steps.select-pat-number.outputs.copilot_pat_number }} + steps: + - id: select-pat-number + name: Select Copilot token from pool + env: + COPILOT_PAT_0: ${{ github.aw.import-inputs.COPILOT_PAT_0 }} + COPILOT_PAT_1: ${{ github.aw.import-inputs.COPILOT_PAT_1 }} + COPILOT_PAT_2: ${{ github.aw.import-inputs.COPILOT_PAT_2 }} + COPILOT_PAT_3: ${{ github.aw.import-inputs.COPILOT_PAT_3 }} + COPILOT_PAT_4: ${{ github.aw.import-inputs.COPILOT_PAT_4 }} + COPILOT_PAT_5: ${{ github.aw.import-inputs.COPILOT_PAT_5 }} + COPILOT_PAT_6: ${{ github.aw.import-inputs.COPILOT_PAT_6 }} + COPILOT_PAT_7: ${{ github.aw.import-inputs.COPILOT_PAT_7 }} + COPILOT_PAT_8: ${{ github.aw.import-inputs.COPILOT_PAT_8 }} + COPILOT_PAT_9: ${{ github.aw.import-inputs.COPILOT_PAT_9 }} + RANDOM_SEED: ${{ github.aw.import-inputs.random_seed }} + shell: bash + run: | + # Collect pool entries with non-empty secrets from COPILOT_PAT_0..COPILOT_PAT_9. + PAT_NUMBERS=() + POOL_INDICATORS=(➖ ➖ ➖ ➖ ➖ ➖ ➖ ➖ ➖ ➖) + + for i in $(seq 0 9); do + var="COPILOT_PAT_${i}" + val="${!var}" + if [ -n "$val" ]; then + PAT_NUMBERS+=(${i}) + POOL_INDICATORS[${i}]="🟪" + fi + done + + # If none of the entries in the pool have values, emit a warning + # and do not set an output value. The consumer can fall back to + # using COPILOT_GITHUB_TOKEN. + if [ ${#PAT_NUMBERS[@]} -eq 0 ]; then + warning_message="::warning::None of the PAT pool entries had values " + warning_message+="(checked COPILOT_PAT_0 through COPILOT_PAT_9)" + echo "$warning_message" + exit 0 + fi + + # Select a random index using the seed if specified. + if [ -n "$RANDOM_SEED" ]; then + RANDOM=$RANDOM_SEED + fi + + PAT_INDEX=$(( RANDOM % ${#PAT_NUMBERS[@]} )) + PAT_NUMBER="${PAT_NUMBERS[$PAT_INDEX]}" + POOL_INDICATORS[${PAT_NUMBER}]="✅" + + echo "Pool size: ${#PAT_NUMBERS[@]}" + echo "Selected PAT number ${PAT_NUMBER} (index: ${PAT_INDEX})" + + echo "|0|1|2|3|4|5|6|7|8|9|" >> "$GITHUB_STEP_SUMMARY" + echo "|-|-|-|-|-|-|-|-|-|-|" >> "$GITHUB_STEP_SUMMARY" + (IFS='|'; printf '|%s' "${POOL_INDICATORS[@]}"; printf '|\n') >> "$GITHUB_STEP_SUMMARY" + + echo "copilot_pat_number=${PAT_NUMBER}" >> "$GITHUB_OUTPUT" + +import-schema: + environment: + type: string + required: true + COPILOT_PAT_0: + type: string + required: false + default: ${{ secrets.COPILOT_PAT_0 }} + COPILOT_PAT_1: + type: string + required: false + default: ${{ secrets.COPILOT_PAT_1 }} + COPILOT_PAT_2: + type: string + required: false + default: ${{ secrets.COPILOT_PAT_2 }} + COPILOT_PAT_3: + type: string + required: false + default: ${{ secrets.COPILOT_PAT_3 }} + COPILOT_PAT_4: + type: string + required: false + default: ${{ secrets.COPILOT_PAT_4 }} + COPILOT_PAT_5: + type: string + required: false + default: ${{ secrets.COPILOT_PAT_5 }} + COPILOT_PAT_6: + type: string + required: false + default: ${{ secrets.COPILOT_PAT_6 }} + COPILOT_PAT_7: + type: string + required: false + default: ${{ secrets.COPILOT_PAT_7 }} + COPILOT_PAT_8: + type: string + required: false + default: ${{ secrets.COPILOT_PAT_8 }} + COPILOT_PAT_9: + type: string + required: false + default: ${{ secrets.COPILOT_PAT_9 }} + random_seed: + type: number + required: false + description: >- + A seed number to use for the random PAT number selection, + for deterministic selection if needed. +--- diff --git a/external/xamarin-android-tools/.github/workflows/validate-pat-pool.yml b/external/xamarin-android-tools/.github/workflows/validate-pat-pool.yml new file mode 100644 index 00000000000..5815082154d --- /dev/null +++ b/external/xamarin-android-tools/.github/workflows/validate-pat-pool.yml @@ -0,0 +1,206 @@ +name: Validate PAT Pool + +on: + schedule: + - cron: '17 2 * * *' # Daily at ~2:17 AM UTC (off-round to reduce contention) + workflow_dispatch: + +permissions: {} + +jobs: + validate: + environment: copilot-pat-pool + name: Validate Copilot PAT Pool + if: ${{ github.event_name == 'workflow_dispatch' || !github.event.repository.fork }} + runs-on: ubuntu-latest + env: + VALIDATE_PAT: | + if [ -z "$COPILOT_GITHUB_TOKEN" ]; then echo "status=empty" >> "$GITHUB_OUTPUT"; exit 0; fi + set +e; timeout 30 copilot --prompt "Say OK" --available-tools="" --silent --effort=low; rc=$?; set -e + if [ $rc -eq 0 ]; then echo "status=valid" >> "$GITHUB_OUTPUT" + elif [ $rc -eq 124 ]; then echo "status=unknown" >> "$GITHUB_OUTPUT" + else echo "status=invalid" >> "$GITHUB_OUTPUT"; fi + steps: + - name: Setup gh-aw scripts + uses: github/gh-aw-actions/setup@8c7d04ebf1ece56cd381446125da3e0f6896294a # v0.80.9 + with: + destination: ${{ runner.temp }}/gh-aw/actions + + - name: Install Copilot CLI + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.63 + + - name: Validate COPILOT_PAT_0 + id: pat0 + continue-on-error: true + env: + COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_PAT_0 }} + shell: bash + run: | + eval "$VALIDATE_PAT" + + - name: Validate COPILOT_PAT_1 + id: pat1 + continue-on-error: true + env: + COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_PAT_1 }} + shell: bash + run: | + eval "$VALIDATE_PAT" + + - name: Validate COPILOT_PAT_2 + id: pat2 + continue-on-error: true + env: + COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_PAT_2 }} + shell: bash + run: | + eval "$VALIDATE_PAT" + + - name: Validate COPILOT_PAT_3 + id: pat3 + continue-on-error: true + env: + COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_PAT_3 }} + shell: bash + run: | + eval "$VALIDATE_PAT" + + - name: Validate COPILOT_PAT_4 + id: pat4 + continue-on-error: true + env: + COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_PAT_4 }} + shell: bash + run: | + eval "$VALIDATE_PAT" + + - name: Validate COPILOT_PAT_5 + id: pat5 + continue-on-error: true + env: + COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_PAT_5 }} + shell: bash + run: | + eval "$VALIDATE_PAT" + + - name: Validate COPILOT_PAT_6 + id: pat6 + continue-on-error: true + env: + COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_PAT_6 }} + shell: bash + run: | + eval "$VALIDATE_PAT" + + - name: Validate COPILOT_PAT_7 + id: pat7 + continue-on-error: true + env: + COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_PAT_7 }} + shell: bash + run: | + eval "$VALIDATE_PAT" + + - name: Validate COPILOT_PAT_8 + id: pat8 + continue-on-error: true + env: + COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_PAT_8 }} + shell: bash + run: | + eval "$VALIDATE_PAT" + + - name: Validate COPILOT_PAT_9 + id: pat9 + continue-on-error: true + env: + COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_PAT_9 }} + shell: bash + run: | + eval "$VALIDATE_PAT" + + - name: Build summary + if: always() + env: + S0: ${{ steps.pat0.outputs.status }} + S1: ${{ steps.pat1.outputs.status }} + S2: ${{ steps.pat2.outputs.status }} + S3: ${{ steps.pat3.outputs.status }} + S4: ${{ steps.pat4.outputs.status }} + S5: ${{ steps.pat5.outputs.status }} + S6: ${{ steps.pat6.outputs.status }} + S7: ${{ steps.pat7.outputs.status }} + S8: ${{ steps.pat8.outputs.status }} + S9: ${{ steps.pat9.outputs.status }} + shell: bash + run: | + statuses=("$S0" "$S1" "$S2" "$S3" "$S4" "$S5" "$S6" "$S7" "$S8" "$S9") + + valid=0; empty=0; invalid=0; unknown=0 + for s in "${statuses[@]}"; do + case "$s" in + valid) valid=$((valid + 1)) ;; + empty) empty=$((empty + 1)) ;; + invalid) invalid=$((invalid + 1)) ;; + *) unknown=$((unknown + 1)) ;; + esac + done + + { + if [ $invalid -eq 0 ] && [ $unknown -eq 0 ] && [ $valid -gt 0 ]; then + echo "> [!NOTE]" + echo "> **PAT pool is valid** -- no action needed" + echo "" + fi + + if [ $invalid -eq 0 ] && [ $unknown -eq 0 ] && [ $valid -eq 0 ]; then + echo "> [!WARNING]" + echo "> **Empty PAT pool** -- agentic workflows will fall back to the default \`COPILOT_GITHUB_TOKEN\`." + echo "" + fi + + if [ $invalid -gt 0 ]; then + echo "> [!CAUTION]" + echo "> **Invalid PAT pool** -- agentic workflows selecting an invalid PAT will fail." + echo "" + fi + + if [ $unknown -gt 0 ]; then + echo "> [!WARNING]" + echo "> **PAT pool not verified** due to transient errors -- re-run the workflow to retry." + echo "" + fi + + echo "☑️ Valid: ${valid} • ⏹️ Empty: ${empty} • ❌ Invalid: ${invalid} • ❓ Unknown: ${unknown}" + echo "" + + echo "| PAT Secret | Status |" + echo "|:-----------|:-------|" + + for i in $(seq 0 9); do + case "${statuses[$i]}" in + valid) symbol="☑️ Valid" ;; + empty) symbol="⏹️ Empty" ;; + invalid) symbol="❌ Invalid" ;; + *) symbol="❓ Unknown" ;; + esac + echo "| \`COPILOT_PAT_${i}\` | ${symbol} |" + done + } >> "$GITHUB_STEP_SUMMARY" + + if [ $invalid -gt 0 ]; then + echo "::error::${invalid} PAT(s) in the pool are invalid and need to be removed or replaced" + exit 1 + fi + + if [ $unknown -gt 0 ]; then + echo "::error::${unknown} PAT(s) could not be verified due to transient errors -- re-run to retry" + exit 1 + fi + + if [ $valid -eq 0 ]; then + echo "::error::The PAT pool is empty -- no PATs are available" + exit 1 + fi + + echo "PAT pool validation passed: ${valid} valid PAT(s)" From d836afda530c1f5091a9360cdbc0649391e0b332 Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Mon, 6 Jul 2026 15:55:04 -0500 Subject: [PATCH 307/308] Remove external/xamarin-android-tools submodule in preparation for in-tree merge Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- external/xamarin-android-tools | 1 - 1 file changed, 1 deletion(-) delete mode 160000 external/xamarin-android-tools diff --git a/external/xamarin-android-tools b/external/xamarin-android-tools deleted file mode 160000 index 367724d6ba9..00000000000 --- a/external/xamarin-android-tools +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 367724d6ba93db20294681769de97b9cdecb58a0 From ac8faf7b9d479aa90cf422c41d335c60a551f7f0 Mon Sep 17 00:00:00 2001 From: Jonathan Peppers Date: Tue, 7 Jul 2026 08:28:42 -0500 Subject: [PATCH 308/308] Add Android Tools CI stage and merge follow-ups Follow-up adjustments after merging dotnet/android-tools in-tree under external/xamarin-android-tools/: - .gitmodules: drop the external/xamarin-android-tools submodule entry - build-tools/scripts/XAVersionInfo.targets: remove the now-dead _GetSubmodulesVersionInfo plumbing (no submodules contribute version info anymore; Java.Interop and xamarin-android-tools are both in-tree) - Xamarin.Android.Build.Tasks.targets: @XAMARIN_ANDROID_TOOLS_COMMIT@ is now the literal "in-tree" (mirrors @JAVA_INTEROP_COMMIT@) - Add stage-xamarin-android-tools-tests.yaml mirroring the upstream android-tools build + unit-test jobs, wired into both azure-pipelines.yaml (1ES) and azure-pipelines-public.yaml. In-tree, external/xamarin-android-tools.override.props imports dotnet/android's root Directory.Build.props (pinning DotNetTargetFrameworkVersion=11.0 and AndroidToolsDisableMultiTargeting=true), so the stage forces -p:AndroidToolsDisableMultiTargeting=false -p:DotNetTargetFrameworkVersion=10.0 to reproduce upstream's standalone netstandard2.0 + net10.0 test configuration (its ProcessUtils tests assert ProcessStartInfo.ArgumentList, which only exists on the NET5_0_OR_GREATER build) - Delete unwired infra under external/xamarin-android-tools/ (.github, .vscode, azure-pipelines.yaml, Localize/onelocbuild.yaml, docs/CODE_REVIEW_POSTMORTEM.md, *.code-workspace, duplicate SECURITY.md and CODE-OF-CONDUCT.md) - Merge unique android-tools reviewer guidance into the in-tree android-reviewer skill + copilot-instructions.md - Normalize line endings of the in-tree files per dotnet/android's .gitattributes (EOL-only; no content changes) Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .github/copilot-instructions.md | 11 + .github/skills/android-reviewer/SKILL.md | 4 +- .../references/csharp-rules.md | 11 + .../references/repo-conventions.md | 4 + .../references/security-rules.md | 1 + .../references/testing-rules.md | 1 + .gitmodules | 4 - .../automation/azure-pipelines-public.yaml | 14 + build-tools/automation/azure-pipelines.yaml | 11 + .../stage-xamarin-android-tools-tests.yaml | 135 ++ build-tools/scripts/XAVersionInfo.targets | 30 +- .../xamarin-android-tools/.github/CODEOWNERS | 12 - .../.github/agents/agentic-workflows.agent.md | 178 -- .../.github/aw/actions-lock.json | 14 - .../.github/copilot-instructions.md | 80 - .../.github/dependabot.yml | 6 - .../skills/android-tools-reviewer/SKILL.md | 114 -- .../references/ai-pitfalls.md | 23 - .../references/csharp-rules.md | 109 -- .../references/msbuild-rules.md | 31 - .../references/repo-conventions.md | 58 - .../references/security-rules.md | 21 - .../references/testing-rules.md | 12 - .../workflows/agentics-maintenance.yml | 608 ------ .../workflows/android-tools-reviewer.lock.yml | 1734 ----------------- .../workflows/android-tools-reviewer.md | 92 - .../.github/workflows/copilot-setup-steps.yml | 25 - .../workflows/shared/pat_pool.README.md | 205 -- .../.github/workflows/shared/pat_pool.md | 119 -- .../.github/workflows/validate-pat-pool.yml | 206 -- .../.vscode/extensions.json | 9 - .../xamarin-android-tools/.vscode/launch.json | 22 - .../.vscode/settings.json | 3 - .../xamarin-android-tools/.vscode/tasks.json | 47 - .../xamarin-android-tools/CODE-OF-CONDUCT.md | 6 - .../Localize/onelocbuild.yaml | 77 - external/xamarin-android-tools/SECURITY.md | 15 - .../Xamarin.Android.Tools.code-workspace | 9 - .../Xamarin.Android.Tools.sln | 118 +- .../azure-pipelines.yaml | 85 - .../docs/CODE_REVIEW_POSTMORTEM.md | 614 ------ .../Models/Sdk/SdkBootstrapPhase.cs | 42 +- .../Models/Sdk/SdkBootstrapProgress.cs | 14 +- .../Models/Sdk/SdkLicense.cs | 12 +- .../Models/Sdk/SdkManifestComponent.cs | 36 +- .../Models/Sdk/SdkPackage.cs | 16 +- .../Xamarin.Android.Tools.AndroidSdk.csproj | 150 +- .../tools/ls-jdks/App.cs | 80 +- .../tools/ls-jdks/ls-jdks.csproj | 30 +- .../Xamarin.Android.Build.Tasks.targets | 2 +- 50 files changed, 445 insertions(+), 4815 deletions(-) create mode 100644 build-tools/automation/yaml-templates/stage-xamarin-android-tools-tests.yaml delete mode 100644 external/xamarin-android-tools/.github/CODEOWNERS delete mode 100644 external/xamarin-android-tools/.github/agents/agentic-workflows.agent.md delete mode 100644 external/xamarin-android-tools/.github/aw/actions-lock.json delete mode 100644 external/xamarin-android-tools/.github/copilot-instructions.md delete mode 100644 external/xamarin-android-tools/.github/dependabot.yml delete mode 100644 external/xamarin-android-tools/.github/skills/android-tools-reviewer/SKILL.md delete mode 100644 external/xamarin-android-tools/.github/skills/android-tools-reviewer/references/ai-pitfalls.md delete mode 100644 external/xamarin-android-tools/.github/skills/android-tools-reviewer/references/csharp-rules.md delete mode 100644 external/xamarin-android-tools/.github/skills/android-tools-reviewer/references/msbuild-rules.md delete mode 100644 external/xamarin-android-tools/.github/skills/android-tools-reviewer/references/repo-conventions.md delete mode 100644 external/xamarin-android-tools/.github/skills/android-tools-reviewer/references/security-rules.md delete mode 100644 external/xamarin-android-tools/.github/skills/android-tools-reviewer/references/testing-rules.md delete mode 100644 external/xamarin-android-tools/.github/workflows/agentics-maintenance.yml delete mode 100644 external/xamarin-android-tools/.github/workflows/android-tools-reviewer.lock.yml delete mode 100644 external/xamarin-android-tools/.github/workflows/android-tools-reviewer.md delete mode 100644 external/xamarin-android-tools/.github/workflows/copilot-setup-steps.yml delete mode 100644 external/xamarin-android-tools/.github/workflows/shared/pat_pool.README.md delete mode 100644 external/xamarin-android-tools/.github/workflows/shared/pat_pool.md delete mode 100644 external/xamarin-android-tools/.github/workflows/validate-pat-pool.yml delete mode 100644 external/xamarin-android-tools/.vscode/extensions.json delete mode 100644 external/xamarin-android-tools/.vscode/launch.json delete mode 100644 external/xamarin-android-tools/.vscode/settings.json delete mode 100644 external/xamarin-android-tools/.vscode/tasks.json delete mode 100644 external/xamarin-android-tools/CODE-OF-CONDUCT.md delete mode 100644 external/xamarin-android-tools/Localize/onelocbuild.yaml delete mode 100644 external/xamarin-android-tools/SECURITY.md delete mode 100644 external/xamarin-android-tools/Xamarin.Android.Tools.code-workspace delete mode 100644 external/xamarin-android-tools/azure-pipelines.yaml delete mode 100644 external/xamarin-android-tools/docs/CODE_REVIEW_POSTMORTEM.md diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index bd864df90d1..14000ed8eb8 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -18,6 +18,17 @@ - **Run tests:** `dotnet-local.cmd test bin/TestDebug/net9.0/Xamarin.Android.Build.Tests.dll --filter Name~TestName` - **Device tests:** `dotnet-local.cmd test bin/TestDebug/MSBuildDeviceIntegration/net9.0/MSBuildDeviceIntegration.dll` +### external/xamarin-android-tools/ + +The in-tree Android tools libraries include SDK/JDK discovery (`AndroidSdkInfo`, `JdkInfo`, SDK manifest parsing, `AdbRunner`, `EmulatorRunner`) and MSBuild task infrastructure (`AndroidTask`, `AndroidToolTask`, `AsyncTask`, `Files`, `ProcessUtils`, `FileUtil`, `MemoryStreamPool`). They target `netstandard2.0` and/or modern .NET, so verify API availability across all target frameworks before using newer BCL APIs. Prefer `ANDROID_HOME` for new Android SDK environment handling; `ANDROID_SDK_ROOT` is deprecated and should only remain for compatibility. + +Useful focused checks: +```sh +dotnet build external/xamarin-android-tools/Xamarin.Android.Tools.sln +dotnet test external/xamarin-android-tools/tests/Xamarin.Android.Tools.AndroidSdk-Tests/Xamarin.Android.Tools.AndroidSdk-Tests.csproj +dotnet test external/xamarin-android-tools/tests/Microsoft.Android.Build.BaseTasks-Tests/Microsoft.Android.Build.BaseTasks-Tests.csproj +``` + ## Critical Rules **Never use `git commit --amend`:** Always create new commits. The user will squash or fixup as needed. diff --git a/.github/skills/android-reviewer/SKILL.md b/.github/skills/android-reviewer/SKILL.md index 9dcb224930d..6ed99c3a413 100644 --- a/.github/skills/android-reviewer/SKILL.md +++ b/.github/skills/android-reviewer/SKILL.md @@ -9,7 +9,7 @@ description: >- # Android PR Reviewer -Review PRs against guidelines distilled from past reviews by senior maintainers of dotnet/android. +Review PRs against guidelines distilled from past reviews by senior maintainers of dotnet/android, including the in-tree shared tooling under `external/xamarin-android-tools/`. ## Review Mindset @@ -70,7 +70,7 @@ Based on the file types identified in step 2, read the appropriate rule files fr **Conditionally load based on changed file types:** - `references/csharp-rules.md` — When any `.cs` files changed. Covers nullable, async, error handling, performance, and code organization. -- `references/msbuild-rules.md` — When `.targets`, `.props`, `.projitems`, or `.csproj` files changed, or when MSBuild task C# files changed (e.g., files under `src/Xamarin.Android.Build.Tasks/`). +- `references/msbuild-rules.md` — When `.targets`, `.props`, `.projitems`, or `.csproj` files changed, or when MSBuild task C# files changed (e.g., files under `src/Xamarin.Android.Build.Tasks/` or `external/xamarin-android-tools/src/Microsoft.Android.Build.BaseTasks/`). - `references/native-rules.md` — When `.c`, `.cpp`, `.h`, or `.hpp` files changed. Covers memory management, C++ best practices, symbol visibility, and platform-specific code. - `references/interop-rules.md` — When both C# and native files changed, when the diff contains P/Invoke or JNI interop code (e.g., `DllImport`, `[Register]` attribute changes, `JNIEnv` calls, `[MarshalAs]`, `[StructLayout]`, `JniObjectReference`, `JniPeerMembers`, `JniTransition`), or when files under `external/Java.Interop/`, `src/Mono.Android/`, or `src/native/` changed. - `references/testing-rules.md` — When test files changed (e.g., files under `tests/`, `**/Tests/`, or test project directories). diff --git a/.github/skills/android-reviewer/references/csharp-rules.md b/.github/skills/android-reviewer/references/csharp-rules.md index 029c54a2b88..6bb4bb76bb9 100644 --- a/.github/skills/android-reviewer/references/csharp-rules.md +++ b/.github/skills/android-reviewer/references/csharp-rules.md @@ -4,6 +4,15 @@ General C# guidance applicable to any .NET repository. --- +## Target Framework Compatibility + +| Check | What to look for | +|-------|-----------------| +| **Oldest TFM must compile** | Code under `external/xamarin-android-tools/` may target `netstandard2.0` and modern .NET. Verify every API and overload against the oldest target framework; common traps include cancellation-token overloads such as `HttpContent.ReadAsStringAsync(CancellationToken)`, modern `ProcessStartInfo.ArgumentList` usage without the existing fallback helpers, and newer language/BCL features that need `#if` guards or polyfills. | +| **Prefer existing compatibility helpers** | Use repository helpers such as `ProcessUtils`, `FileUtil`, and nullable extension methods instead of direct modern-BCL calls when they provide `netstandard2.0` fallbacks or better annotations. | + +--- + ## Nullable Reference Types | Check | What to look for | @@ -53,6 +62,8 @@ General C# guidance applicable to any .NET repository. |-------|-----------------| | **Avoid unnecessary allocations** | Don't create intermediate collections when LINQ chaining or a single list would do. Char arrays for `string.Split()` should be `static readonly` fields. | | **ArrayPool for large buffers** | Buffers ≥ 1 KB should use `ArrayPool.Shared.Rent()` with `try`/`finally` return. Large allocations go to the LOH and are expensive to GC. | +| **Use existing pools for repeated streams/buffers** | In Android tools code, prefer existing pooling helpers such as `MemoryStreamPool`/`ObjectPool` for repeated temporary streams or buffers instead of allocating new instances in hot paths. If mutable buffers are reused with single-caller assumptions, document the thread-safety invariant. | +| **Static `HttpClient`** | `HttpClient` instances should be `static readonly` fields, not per-instance objects that are repeatedly constructed or disposed. Per-instance clients can exhaust sockets; inject clients only when there is a real caller requirement. | | **`HashSet.Add()` already handles duplicates** | Calling `.Contains()` before `.Add()` does the hash lookup twice. Just call `.Add()`. (Postmortem `#41`) | | **Don't wrap a value in an interpolated string** | `$"{someString}"` creates an unnecessary `string.Format` call when `someString` is already a string. (Postmortem `#42`) | | **Consider allocations when choosing types** | `Stopwatch` is heap-allocated; `DateTime`/`ValueStopwatch` is a struct. On hot paths or startup, prefer value types. (Postmortem `#39`) | diff --git a/.github/skills/android-reviewer/references/repo-conventions.md b/.github/skills/android-reviewer/references/repo-conventions.md index 3317d22d9b1..df66a83a8f4 100644 --- a/.github/skills/android-reviewer/references/repo-conventions.md +++ b/.github/skills/android-reviewer/references/repo-conventions.md @@ -40,10 +40,13 @@ and merge conflicts. | Check | What to look for | |-------|-----------------| | **Use existing utilities** | Check `MonoAndroidHelper`, `FileUtil`, `PathUtil`, `ITaskItemExtensions`, and other utilities before writing new helpers. Duplicating existing logic is the most expensive AI pattern. | +| **Use Android tools utilities** | In `external/xamarin-android-tools/`, process execution should go through `ProcessUtils`, file extraction/download/checksum/path helpers through `FileUtil`, and repeated buffers/streams through `ObjectPool` or `MemoryStreamPool` where applicable. | | **`Log.LogDebugMessage` for diagnostics** | Use `Log.LogDebugMessage(…)` for verbose/debug output, not `Console.WriteLine` or `Debug.WriteLine`. Don't spam logcat with messages that fire on every type lookup miss. (Postmortem `#9`) | +| **Android tools logger delegate** | SDK/JDK discovery helpers in `external/xamarin-android-tools/` commonly use `Action? logger` (see `AndroidSdkInfo.DefaultConsoleLogger`) so callers can route diagnostics into MSBuild or IDE logs. Don't replace this with `Console.WriteLine` or `Debug.WriteLine`. | | **Return `IReadOnlyList`** | Public methods should return `IReadOnlyList` or `IReadOnlyCollection` instead of mutable `List`. | | **Prefer C# pattern matching** | Use `is`, `switch` expressions, and property patterns instead of `if`/`else` type-check chains. | | **Structured args, not string interpolation** | Process arguments should be `IEnumerable` or use `ArgumentList`, not a single interpolated string. | +| **Android SDK environment variables** | In `external/xamarin-android-tools/`, use `EnvironmentVariableNames.AndroidHome`/`ANDROID_HOME` for new SDK-root behavior. `ANDROID_SDK_ROOT` is deprecated by Android and should only be read for backward compatibility. | | **Method names must reflect behavior** | If `CreateFoo()` sometimes returns an existing instance, rename it `GetOrCreateFoo()` or `GetFoo()`. (Postmortem `#4`) | | **Choose collision-proof names** | Types and constants that could collide with user code or Android concepts need disambiguating prefixes (e.g., `__Xamarin.Android.Resource.Designer` with a `__` prefix). (Postmortem `#2`) | | **Don't assume transitive assembly references** | An assembly containing an `Activity` subclass does not necessarily reference `Mono.Android.dll` directly — the reference may be transitive. Skipping assemblies based on direct reference checks can break user code. (Postmortem `#64`) | @@ -93,6 +96,7 @@ and merge conflicts. | Check | What to look for | |-------|-----------------| | **XmlReader over LINQ XML** | For forward-only XML parsing (manifests, config files), prefer `XmlReader` — it's streaming and allocation-free. `XElement`/`XDocument` builds a full DOM tree. | +| **SDK manifest parsing stays streaming** | Android SDK repository manifests can be large; keep `external/xamarin-android-tools/` manifest parsing on streaming `XmlReader`-style paths unless there is measured evidence a DOM is acceptable. | | **p/invoke over process spawn** | For single syscalls like `chmod`, use `[DllImport("libc")]` instead of spawning a child process. Process creation is orders of magnitude more expensive. | | **Use `Files.CopyIfStringChanged()`** | Don't write to a file if the content hasn't changed — it breaks incremental builds by updating timestamps. (Postmortem `#53`) | | **Don't remove caches without measurement** | If a cache (like `TypeDefinitionCache`) had a measured perf win, removing it requires proving the replacement provides equivalent caching. (Postmortem `#57`) | diff --git a/.github/skills/android-reviewer/references/security-rules.md b/.github/skills/android-reviewer/references/security-rules.md index f8e255103f1..1c672d71584 100644 --- a/.github/skills/android-reviewer/references/security-rules.md +++ b/.github/skills/android-reviewer/references/security-rules.md @@ -11,6 +11,7 @@ I/O, archives, or process execution. |-------|-----------------| | **Zip Slip protection** | Archive extraction must validate that every entry path, after `Path.GetFullPath()`, resolves under the destination directory. Never use `ZipFile.ExtractToDirectory()` for untrusted archives without entry-by-entry validation. | | **Path traversal** | `StartsWith()` checks on paths must normalize with `Path.GetFullPath()` first. A path like `C:\Program Files\..\Users\evil` bypasses naive prefix checks. Also check for directory boundary issues (`C:\Program FilesX` matching `C:\Program Files`). | +| **Mandatory checksum verification** | Downloads or archive installs in Android tools code must not proceed unverified when checksum/hash data is expected but missing or mismatched. Fail closed with an actionable error. | --- diff --git a/.github/skills/android-reviewer/references/testing-rules.md b/.github/skills/android-reviewer/references/testing-rules.md index 7f93f6f97cc..2f36333a36a 100644 --- a/.github/skills/android-reviewer/references/testing-rules.md +++ b/.github/skills/android-reviewer/references/testing-rules.md @@ -16,6 +16,7 @@ Guidance for test code. The repo-specific conventions (e.g., `BaseTest`, | **Test assertions must be specific** | `Assert.IsNotNull(result)` or `Assert.IsTrue(success)` don't tell you what went wrong. Prefer `Assert.AreEqual(expected, actual)` or NUnit constraints (`Assert.That` with `Does.Contain`, `Is.EqualTo`, etc.) for richer failure messages. | | **Deterministic test data** | Tests should not depend on system locale, timezone, or current date. Use explicit `CultureInfo.InvariantCulture` and hardcoded dates when testing formatting. | | **Test edge cases** | Empty collections, null inputs, boundary values, concurrent calls, and very large inputs should all be considered. If the PR only tests the happy path, suggest edge cases. | +| **Android tools SDK/JDK fixtures** | Tests under `external/xamarin-android-tools/tests/` commonly build isolated fake SDK/JDK layouts and platform-specific tool scripts (`.bat` on Windows, shell scripts on Unix). Keep these fixtures self-contained and cleaned up in setup/teardown rather than depending on the developer machine's installed SDK/JDK. | | **Generator tests must include Invoker types** | Tests for generated binding code (under `external/Java.Interop/tools/generator/` and related test projects) should verify both the interface/class output and the `*Invoker` type behavior. Invoker codegen has historically had subtle bugs with default interface methods and virtual dispatch. | | **JVM-dependent tests** | Tests that require a running JVM must be in projects that configure the JVM environment (e.g., `Java.Interop-Tests`). Verify that test classes requiring a JVM are not placed in unit-test-only projects, where they will silently skip or fail with obscure errors. | | **Expected codegen output tests** | Generator tests that compare against expected output files should be updated when the expected format changes. Stale expected-output files cause spurious test failures that mask real regressions. | diff --git a/.gitmodules b/.gitmodules index ae4a5871b6e..ed056635dec 100644 --- a/.gitmodules +++ b/.gitmodules @@ -22,10 +22,6 @@ path = external/robin-map url = https://github.com/xamarin/robin-map branch = master -[submodule "external/xamarin-android-tools"] - path = external/xamarin-android-tools - url = https://github.com/dotnet/android-tools - branch = main [submodule "external/xxHash"] path = external/xxHash url = https://github.com/Cyan4973/xxHash.git diff --git a/build-tools/automation/azure-pipelines-public.yaml b/build-tools/automation/azure-pipelines-public.yaml index 436dc31d184..c7835968275 100644 --- a/build-tools/automation/azure-pipelines-public.yaml +++ b/build-tools/automation/azure-pipelines-public.yaml @@ -429,6 +429,20 @@ stages: - ImageOverride -equals ACES_VM_SharedPool_Tahoe os: macOS +# Android Tools Tests Stage +- template: /build-tools/automation/yaml-templates/stage-xamarin-android-tools-tests.yaml@self + parameters: + windowsPool: + name: $(NetCorePublicPoolName) + demands: + - ImageOverride -equals $(WindowsPoolImageNetCorePublic) + os: windows + macPool: + name: AcesShared + demands: + - ImageOverride -equals ACES_VM_SharedPool_Tahoe + os: macOS + # MAUI Tests Stage - stage: maui_tests displayName: MAUI Tests diff --git a/build-tools/automation/azure-pipelines.yaml b/build-tools/automation/azure-pipelines.yaml index d7406c1f7c0..92cfa0b653a 100644 --- a/build-tools/automation/azure-pipelines.yaml +++ b/build-tools/automation/azure-pipelines.yaml @@ -110,6 +110,17 @@ extends: image: $(WindowsPoolImage1ESPT) os: windows + - template: /build-tools/automation/yaml-templates/stage-xamarin-android-tools-tests.yaml@self + parameters: + # The internal Xamarin.Android pipeline uses 1ES Pipeline Templates, which + # require Windows jobs to run on a 1ES-hosted pool. macOS is fine on the + # hosted Azure Pipelines pool as long as `os: macOS` is set (which is now + # the template default). + windowsPool: + name: MAUI-1ESPT + image: $(WindowsPoolImage1ESPT) + os: windows + - stage: maui_tests displayName: MAUI Tests dependsOn: mac_build diff --git a/build-tools/automation/yaml-templates/stage-xamarin-android-tools-tests.yaml b/build-tools/automation/yaml-templates/stage-xamarin-android-tools-tests.yaml new file mode 100644 index 00000000000..f3bc29bed8c --- /dev/null +++ b/build-tools/automation/yaml-templates/stage-xamarin-android-tools-tests.yaml @@ -0,0 +1,135 @@ +# xamarin-android-tools tests stage +# +# Mirrors the build + test jobs from the upstream dotnet/android-tools +# azure-pipelines.yaml so that the Xamarin.Android.Tools build + unit tests +# continue to run as part of dotnet/android CI now that android-tools has been +# merged in-tree under external/xamarin-android-tools/. +# +# The upstream pipeline also packed a NuGet and published build artifacts; +# those are intentionally dropped here (the binaries aren't consumed from this +# stage -- dotnet/android's own build produces the shipping assemblies). Only +# logs + test results are surfaced. +# +# Referenced from both: +# - build-tools/automation/azure-pipelines.yaml (official / 1ES) +# - build-tools/automation/azure-pipelines-public.yaml (public PR validation) + +parameters: +- name: stageName + type: string + default: xamarin_android_tools_tests +- name: dependsOn + type: object + default: [] +- name: condition + type: string + default: succeeded() +- name: windowsPool + type: object + # NOTE: This default is a Microsoft-hosted Azure Pipelines pool, which is NOT + # allowed by 1ES Pipeline Templates in Official mode. Callers that extend the + # 1ES Official template (e.g. build-tools/automation/azure-pipelines.yaml) must + # override this with a 1ES-hosted pool like `MAUI-1ESPT`. + default: + name: Azure Pipelines + vmImage: $(HostedWinImage) + os: windows +- name: macPool + type: object + default: + name: Azure Pipelines + vmImage: $(HostedMacImage) + os: macOS + +stages: +- stage: ${{ parameters.stageName }} + displayName: Android Tools Tests + dependsOn: ${{ parameters.dependsOn }} + condition: ${{ parameters.condition }} + variables: + DotNetCoreVersion: 10.0.x + XatSourceDirectory: $(System.DefaultWorkingDirectory)/external/xamarin-android-tools + # When built in-tree, external/xamarin-android-tools.override.props imports + # dotnet/android's root Directory.Build.props, which pins + # DotNetTargetFrameworkVersion=11.0 and sets AndroidToolsDisableMultiTargeting=true + # (so the product build gets a single netstandard2.0 assembly). Upstream + # dotnet/android-tools CI instead builds standalone: multi-targeting + # netstandard2.0 + net10.0, with the test projects running on net10.0. Its + # ProcessUtils tests assert ProcessStartInfo.ArgumentList, which only exists on + # the net10.0 (NET5_0_OR_GREATER) build -- the netstandard2.0 build takes the + # string-Arguments path. To reproduce upstream's green test configuration we + # re-enable multi-targeting and force net10.0 via these global properties. + XatBuildProperties: -p:AndroidToolsDisableMultiTargeting=false -p:DotNetTargetFrameworkVersion=10.0 + jobs: + + # Check - "Xamarin.Android (Android Tools Tests Windows - .NET)" + - job: xamarin_android_tools_windows + displayName: Windows - .NET + pool: ${{ parameters.windowsPool }} + timeoutInMinutes: 30 + workspace: + clean: all + steps: + - checkout: self + clean: true + + - task: UseDotNet@2 + displayName: Use .NET Core $(DotNetCoreVersion) + inputs: + version: $(DotNetCoreVersion) + + - task: DotNetCoreCLI@2 + displayName: Build Xamarin.Android.Tools.sln + inputs: + command: build + projects: $(XatSourceDirectory)/Xamarin.Android.Tools.sln + arguments: -c Debug $(XatBuildProperties) -bl:$(Build.ArtifactStagingDirectory)/build-windows.binlog + + - task: DotNetCoreCLI@2 + displayName: Run Tests + inputs: + command: test + projects: $(XatSourceDirectory)/bin/TestDebug/**/*-Tests.dll + + - task: PublishBuildArtifacts@1 + displayName: Upload build logs + condition: always() + inputs: + pathToPublish: $(Build.ArtifactStagingDirectory) + artifactName: android-tools-logs-windows + + # Check - "Xamarin.Android (Android Tools Tests Mac - .NET)" + - job: xamarin_android_tools_mac + displayName: Mac - .NET + pool: ${{ parameters.macPool }} + timeoutInMinutes: 30 + workspace: + clean: all + steps: + - checkout: self + clean: true + + - task: UseDotNet@2 + displayName: Use .NET Core $(DotNetCoreVersion) + inputs: + version: $(DotNetCoreVersion) + + - task: DotNetCoreCLI@2 + displayName: Build Xamarin.Android.Tools.sln + inputs: + command: build + projects: $(XatSourceDirectory)/Xamarin.Android.Tools.sln + arguments: -c Debug $(XatBuildProperties) -bl:$(Build.ArtifactStagingDirectory)/build-mac.binlog + + - task: DotNetCoreCLI@2 + displayName: Run Tests + inputs: + command: test + projects: $(XatSourceDirectory)/bin/TestDebug/**/*-Tests.dll + + - task: PublishBuildArtifacts@1 + displayName: Upload build logs + condition: always() + inputs: + pathToPublish: $(Build.ArtifactStagingDirectory) + artifactName: android-tools-logs-mac diff --git a/build-tools/scripts/XAVersionInfo.targets b/build-tools/scripts/XAVersionInfo.targets index 5e2889a3fc7..b1ab9737fa0 100644 --- a/build-tools/scripts/XAVersionInfo.targets +++ b/build-tools/scripts/XAVersionInfo.targets @@ -10,31 +10,11 @@ - - - - - - <_SubmoduleBranchInfo Include="external/xamarin-android-tools"> - _BuildInfo_XamarinAndroidToolsCommit - - - - - - - - - - + + - -# Upgrade workflows -gh aw fix --write -gh aw compile --validate -``` - -## Key Features of gh-aw - -- **Natural Language Workflows**: Write workflows in markdown with YAML frontmatter -- **AI Engine Support**: Copilot, Claude, Codex, or custom engines -- **MCP Server Integration**: Connect to Model Context Protocol servers for tools -- **Safe Outputs**: Structured communication between AI and GitHub API -- **Strict Mode**: Security-first validation and sandboxing -- **Shared Components**: Reusable workflow building blocks -- **Repo Memory**: Persistent git-backed storage for agents -- **Sandboxed Execution**: All workflows run in the Agent Workflow Firewall (AWF) sandbox, enabling full `bash` and `edit` tools by default - -## Important Notes - -- Always reference the instructions file at https://github.com/github/gh-aw/blob/v0.68.3/.github/aw/github-agentic-workflows.md for complete documentation -- Use the MCP tool `agentic-workflows` when running in GitHub Copilot Cloud -- Workflows must be compiled to `.lock.yml` files before running in GitHub Actions -- **Bash tools are enabled by default** - Don't restrict bash commands unnecessarily since workflows are sandboxed by the AWF -- Follow security best practices: minimal permissions, explicit network access, no template injection -- **Network configuration**: Use ecosystem identifiers (`node`, `python`, `go`, etc.) or explicit FQDNs in `network.allowed`. Bare shorthands like `npm` or `pypi` are **not** valid. See https://github.com/github/gh-aw/blob/v0.68.3/.github/aw/network.md for the full list of valid ecosystem identifiers and domain patterns. -- **Single-file output**: When creating a workflow, produce exactly **one** workflow `.md` file. Do not create separate documentation files (architecture docs, runbooks, usage guides, etc.). If documentation is needed, add a brief `## Usage` section inside the workflow file itself. diff --git a/external/xamarin-android-tools/.github/aw/actions-lock.json b/external/xamarin-android-tools/.github/aw/actions-lock.json deleted file mode 100644 index 64a0a0a93a1..00000000000 --- a/external/xamarin-android-tools/.github/aw/actions-lock.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "entries": { - "actions/github-script@v9.0.0": { - "repo": "actions/github-script", - "version": "v9.0.0", - "sha": "3a2844b7e9c422d3c10d287c895573f7108da1b3" - }, - "github/gh-aw-actions/setup@v0.80.9": { - "repo": "github/gh-aw-actions/setup", - "version": "v0.80.9", - "sha": "8c7d04ebf1ece56cd381446125da3e0f6896294a" - } - } -} diff --git a/external/xamarin-android-tools/.github/copilot-instructions.md b/external/xamarin-android-tools/.github/copilot-instructions.md deleted file mode 100644 index 68460df4ace..00000000000 --- a/external/xamarin-android-tools/.github/copilot-instructions.md +++ /dev/null @@ -1,80 +0,0 @@ -# Copilot Instructions for android-tools - -Shared .NET libraries for Android SDK/JDK discovery and MSBuild task infrastructure, consumed by [dotnet/android](https://github.com/dotnet/android) and IDE extensions. - -## Architecture - -Two independent libraries (neither references the other): - -- **`Xamarin.Android.Tools.AndroidSdk`** — SDK/NDK/JDK path discovery, Android version management, manifest parsing. Multi-targets `netstandard2.0`+`net10.0` (trimming/AOT on modern TFM). Entry: `AndroidSdkInfo` → `AndroidSdkWindows`/`AndroidSdkUnix` via `OS.IsWindows`. -- **`Microsoft.Android.Build.BaseTasks`** — MSBuild task bases and build utilities. `netstandard2.0` only. `RootNamespace` = `Microsoft.Android.Build.Tasks` (differs from project name). - -**Patterns:** -- **Platform polymorphism**: `AndroidSdkBase` → `AndroidSdkWindows`/`AndroidSdkUnix` (Template Method). JDK: vendor classes inherit `JdkLocations` partial, aggregated by priority in `JdkInfo.GetKnownSystemJdkInfos()`. Platform files: `Jdks/JdkLocations.{Windows,MacOS}.cs`, `Sdks/AndroidSdk{Windows,Unix}.cs`. -- **Task base types**: `AndroidTask` (common `Task`-based MSBuild tasks), `AndroidToolTask` (`ToolTask`-based wrappers for external tools), and `AsyncTask` (long-running, UI-safe `Task`-based tasks). All use `UnhandledExceptionLogger` for XA error codes. -- **Incremental builds**: `Files.CopyIf*Changed()` skips unchanged writes. `ObjectPool`/`MemoryStreamPool` reduces GC. `JdkInfo` uses `Lazy` for expensive parsing. - -## Build & Test - -```sh -dotnet build Xamarin.Android.Tools.sln -dotnet test tests/Xamarin.Android.Tools.AndroidSdk-Tests/Xamarin.Android.Tools.AndroidSdk-Tests.csproj -dotnet test tests/Microsoft.Android.Build.BaseTasks-Tests/Microsoft.Android.Build.BaseTasks-Tests.csproj -``` - -Output: `bin\$(Configuration)\` (redistributables), `bin\Test$(Configuration)\` (tests). `$(DotNetTargetFrameworkVersion)` = `10.0` in `Directory.Build.props`. Versioning: `nuget.version` has `major.minor`; patch = git commit count since file changed. - -## Android Environment Variables - -Per the [official Android docs](https://developer.android.com/tools/variables#envar): - -- **`ANDROID_HOME`** — the canonical variable for the Android SDK root path. Use this everywhere. -- **`ANDROID_SDK_ROOT`** — **deprecated**. Do not introduce new usages. Existing code may still read it for backward compatibility but always prefer `ANDROID_HOME`. -- **`ANDROID_USER_HOME`** — user-level config/AVD storage (defaults to `~/.android`). -- **`ANDROID_EMULATOR_HOME`** — emulator config (defaults to `$ANDROID_USER_HOME`). -- **`ANDROID_AVD_HOME`** — AVD data (defaults to `$ANDROID_USER_HOME/avd`). - -When setting environment variables for SDK tools (e.g. `sdkmanager`, `avdmanager`), set `ANDROID_HOME`. The `EnvironmentVariableNames` class in this repo defines the constants. - -## Conventions - -- **One type per file**: each public class, struct, enum, or interface must be in its own `.cs` file named after the type (e.g. `JdkVersionInfo` → `JdkVersionInfo.cs`). Do not combine multiple top-level types in a single file. -- **Minimal public API**: prefer `internal` for new methods/classes unless they are consumed by external projects (dotnet/android, IDE extensions). Use `InternalsVisibleTo` for test access. -- **Strongly-typed APIs over strings**: when an API parameter has a finite set of valid forms (e.g. `"tcp:5000"`), use a record/enum pair (e.g. `AdbPortSpec(AdbProtocol.Tcp, 5000)`) instead of raw strings. Callers get compile-time safety, IntelliSense, and pattern matching. -- **Avoid convenience overloads**: don't add `string`, `int`, and strongly-typed overloads for the same method. Pick the strongly-typed signature and let callers construct the type. Fewer overloads = smaller API surface, fewer RS0027 suppressions, and less maintenance. -- **Include stdout in error diagnostics**: when a method captures stdout (e.g. `ListReversePortsAsync`), pass it to `ProcessUtils.ThrowIfFailed(exitCode, command, stderr, stdout)` so failure messages include all output, not just stderr. -- **Update PublicAPI files**: when adding or removing `public` API surface, update the `PublicAPI.Unshipped.txt` files under `src/Xamarin.Android.Tools.AndroidSdk/PublicAPI/net10.0/` and `src/Xamarin.Android.Tools.AndroidSdk/PublicAPI/netstandard2.0/`. New types and members go in the unshipped file. Build with `--no-incremental` and verify zero `RS0016` (missing) or `RS0017` (removed) warnings. See `PublicAPI.Shipped.txt` for the expected entry format. -- **Use `ProcessUtils`**: never use `System.Diagnostics.Process` directly. Use the existing helpers such as `ProcessUtils.CreateProcessStartInfo()`, `ProcessUtils.StartProcess()`, and `ProcessUtils.ExecuteToolAsync()` for launching external tools. This ensures consistent logging, timeout handling, and cancellation. -- **Process arguments**: use `ProcessUtils.CreateProcessStartInfo()` and pass arguments as separate strings instead of building a single arguments string yourself. `ProcessUtils` will use `ProcessStartInfo.ArgumentList` when available and fall back to `Arguments` on `netstandard2.0`. -- **Use `FileUtil`**: file operations like extraction, downloads, checksum verification, and path checks belong in `FileUtil.cs`. Don't duplicate file helpers in domain classes. -- **Concise XML docs**: omit `` tags for self-explanatory methods. Only add doc comments when the behavior is non-obvious. Avoid restating the method name. -- **`netstandard2.0` awareness**: many modern .NET APIs are unavailable or have fewer overloads on `netstandard2.0`. When unsure about API availability, search mslearn to check documentation for the target framework. -- **Reuse buffers**: use `ArrayPool.Shared.Rent()`/`Return()` with `try/finally` for temporary buffers (see `DownloadUtils.cs`). For fixed-size repeated reads, prefer a reusable `readonly byte[]` field. -- **Format your code**: always match the existing file indentation (tabs, not spaces — see `.editorconfig`). Only format code you add or modify; never reformat existing lines. -- **No whitespace-only diffs**: before committing, run `git diff --stat` and verify only files with intentional code changes appear. If a file shows as fully rewritten (all lines removed and re-added) you have introduced line-ending or trailing-whitespace changes — revert that file with `git checkout -- ` and re-apply only your code change. Never commit whitespace-only or line-ending-only changes. -- **File-scoped namespaces**: all new files should use file-scoped namespaces (`namespace Foo;` instead of `namespace Foo { ... }`). -- **Static `HttpClient`**: `HttpClient` instances must be `static` to avoid socket exhaustion. See [HttpClient guidelines](https://learn.microsoft.com/dotnet/fundamentals/networking/http/httpclient-guidelines#recommended-use). Do not create per-instance `HttpClient` fields or dispose them in `IDisposable`. -- [Mono Coding Guidelines](http://www.mono-project.com/community/contributing/coding-guidelines/): tabs, K&R braces, `PascalCase` public members. -- **No null-forgiving operator (`!`)**: do not use the null-forgiving operator after null checks. Instead, use C# property patterns (e.g. `if (value is { Length: > 0 } v)`) which give the compiler proper non-null flow analysis on all target frameworks including `netstandard2.0`. -- **Prefer switch expressions**: use C# switch expressions over switch statements for simple value mappings (e.g. `return state switch { "x" => A, _ => B };`). Use switch statements only when the body has side effects or complex logic. -- **Document thread-safety**: when a class reuses mutable state (buffers, caches) assuming single-caller semantics, add `This class is not thread-safe.`. -- Nullable enabled in `AndroidSdk`. `NullableAttributes.cs` excluded on `net10.0+`. -- Strong-named via `product.snk`. In the AndroidSdk project, tests use `InternalsVisibleTo` with full public key (`Properties/AssemblyInfo.cs`). -- Assembly names support `$(VendorPrefix)`/`$(VendorSuffix)` for branding forks. -- `.resx` localization in multiple languages via OneLocBuild (`Localize/`). Do not hand-edit satellite `.resx`. - -## Tests - -NUnit 3 (`[TestFixture]`, `[Test]`, `[TestCase]`). Tests create isolated temp dirs with faux JDK/SDK structures (`.bat` on Windows, shell scripts on Unix), cleaned in teardown. `AndroidSdk-Tests`: `[OneTimeSetUp]`/`[OneTimeTearDown]`. `BaseTasks-Tests`: imports `MSBuildReferences.projitems` for MSBuild types. - -## Key Files - -- **SDK discovery**: `AndroidSdkInfo.cs`, `Sdks/AndroidSdk{Windows,Unix}.cs` -- **JDK discovery**: `JdkInfo.cs`, `Jdks/` directory -- **Android versions**: `AndroidVersions.cs` (hardcoded `KnownVersions` table) -- **MSBuild tasks**: `AndroidTask.cs`, `AsyncTask.cs`, `Files.cs` -- **Build config**: `Directory.Build.props`, `Directory.Build.targets`, `nuget.version` - -## Adding Android API Levels - -Update `KnownVersions` in `AndroidVersions.cs` — add tuple with ApiLevel, Id, CodeName, OSVersion, TargetFrameworkVersion, Stable flag. diff --git a/external/xamarin-android-tools/.github/dependabot.yml b/external/xamarin-android-tools/.github/dependabot.yml deleted file mode 100644 index 446b951ea26..00000000000 --- a/external/xamarin-android-tools/.github/dependabot.yml +++ /dev/null @@ -1,6 +0,0 @@ -version: 2 -updates: - - package-ecosystem: "nuget" - directory: "/" - schedule: - interval: "weekly" diff --git a/external/xamarin-android-tools/.github/skills/android-tools-reviewer/SKILL.md b/external/xamarin-android-tools/.github/skills/android-tools-reviewer/SKILL.md deleted file mode 100644 index 618083ee8ae..00000000000 --- a/external/xamarin-android-tools/.github/skills/android-tools-reviewer/SKILL.md +++ /dev/null @@ -1,114 +0,0 @@ ---- -name: android-tools-reviewer -description: >- - Review pull requests for dotnet/android-tools using lessons from past code reviews. - Trigger when the user says "review this" with a GitHub PR URL, asks to review a PR, - or wants code review feedback. Fetches the diff, checks it against established rules - (netstandard2.0, async, security, error handling, patterns, performance), and posts - a batched 🤖-prefixed review via gh CLI. ---- - -# Android Tools PR Reviewer - -Review PRs against guidelines distilled from past reviews by senior maintainers. - -## Review Mindset - -Be polite but skeptical. Prioritize bugs, performance regressions, safety issues, and pattern violations over style nitpicks. **3 important comments > 15 nitpicks.** - -Flag severity clearly in every comment: -- ❌ **error** — Must fix before merge. Bugs, security issues, broken patterns. -- ⚠️ **warning** — Should fix. Performance issues, missing validation, inconsistency with patterns. -- 💡 **suggestion** — Consider changing. Style, readability, optional improvements. - -## Workflow - -### 1. Identify the PR - -If triggered from an agentic workflow (slash command on a PR), use the PR from the event context. Otherwise, extract `owner`, `repo`, `pr_number` from a URL or reference provided by the user. -Formats: `https://github.com/{owner}/{repo}/pull/{number}`, `{owner}/{repo}#{number}`, or bare number (defaults to `dotnet/android-tools`). - -### 2. Gather context (before reading PR description) - -``` -gh pr diff {number} --repo {owner}/{repo} -gh pr view {number} --repo {owner}/{repo} --json files -``` - -For each changed file, read the **full source file** (not just the diff) to understand surrounding invariants, call patterns, and data flow. If the change modifies a public/internal API or utility, search for callers. Check whether sibling types need the same fix. - -**Prefer existing repo patterns.** Before suggesting new infrastructure, grep for established patterns (`ArrayPool`, `ObjectPool`, `MemoryStreamPool`, `ProcessUtils`) and follow those. - -**Form an independent assessment** of what the change does and what problems it has *before* reading the PR description. - -### 3. Incorporate PR narrative and reconcile - -``` -gh pr view {number} --repo {owner}/{repo} --json title,body -``` - -Now read the PR description and linked issues. Treat them as claims to verify, not facts to accept. Where your independent reading disagrees with the PR description, investigate further. If the PR claims a performance improvement, require evidence. If it claims a bug fix, verify the bug exists and the fix addresses root cause — not symptoms. - -### 4. Check CI status - -``` -gh pr checks {number} --repo {owner}/{repo} -``` - -Review the CI results. **Never post ✅ LGTM if any required CI check is failing or if the code doesn't build.** If CI is failing: -- Investigate the failure. -- If the failure is caused by the PR's code changes, flag it as ❌ error. -- If the failure is a known infrastructure issue or pre-existing flake unrelated to the PR, note it in the summary but still use ⚠️ Needs Changes — the PR isn't mergeable until CI is green. - -### 5. Load review rules - -Based on the changed files from step 2, load the appropriate rule files from `references/`: - -**Always load:** -- `references/repo-conventions.md` — repo-specific patterns and conventions -- `references/ai-pitfalls.md` — common AI code generation mistakes -- `references/security-rules.md` — security review checklist - -**Conditionally load:** -- `references/csharp-rules.md` — if any `.cs` files changed -- `references/msbuild-rules.md` — if any `.targets`, `.props`, or `.projitems` files changed, or if changed `.cs` files are under `src/Microsoft.Android.Build.BaseTasks/` -- `references/testing-rules.md` — if any files under `tests/` changed or files with `Test` in the path changed - -### 6. Analyze the diff - -For each changed file, check against the review rules. Record issues as: - -```json -{ "path": "src/Example.cs", "line": 42, "side": "RIGHT", "body": "..." } -``` - -Constraints: -- Only comment on added/modified lines in the diff — the API rejects out-of-range lines. -- `line` = line number in the NEW file (right side). Double-check against the diff. -- One issue per comment. -- **Don't pile on.** If the same issue appears many times, flag it once with a note listing all affected files. -- **Don't flag what CI catches.** Skip compiler errors, formatting the linter will catch, etc. -- **Avoid false positives.** Verify the concern actually applies given the full context. If unsure, phrase it as a question rather than a firm claim. - -### 7. Post the review - -Post your findings directly: - -- **Inline comments** on specific lines of the diff with the severity, category, and explanation. -- **Review summary** with the overall verdict (✅ LGTM, ⚠️ Needs Changes, or ❌ Reject), issue counts by severity, and positive callouts. - -If no issues found **and CI is green**, submit with one or two 💡 suggestions on key implementation lines and a positive summary. **Always post at least one inline comment** — the review submission framework requires it. - -**Copilot-authored PRs:** If the PR author is `Copilot` (the GitHub Copilot coding agent) and the verdict is ⚠️ Needs Changes or ❌ Reject, prefix the review summary with `@copilot ` so the comment automatically triggers Copilot to address the feedback. Do NOT add the prefix for ✅ LGTM verdicts. - -## Comment format - -``` -🤖 {severity} **{Category}** — {What's wrong and what to do instead.} - -_{Rule: Brief name (Postmortem `#N`)}_ -``` - -Where `{severity}` is ❌, ⚠️, or 💡. Always wrap `#N` in backticks so GitHub doesn't auto-link to issues. - -**Categories:** Target framework · Async pattern · Resource management · Error handling · Security · Code organization · Naming · Performance · Pattern · YAGNI · API design · Code duplication · Testing · Documentation diff --git a/external/xamarin-android-tools/.github/skills/android-tools-reviewer/references/ai-pitfalls.md b/external/xamarin-android-tools/.github/skills/android-tools-reviewer/references/ai-pitfalls.md deleted file mode 100644 index cfa109e854e..00000000000 --- a/external/xamarin-android-tools/.github/skills/android-tools-reviewer/references/ai-pitfalls.md +++ /dev/null @@ -1,23 +0,0 @@ -# AI Code Generation Pitfalls - -Patterns that AI-generated code consistently gets wrong. Always loaded during reviews. - ---- - -| Pattern | What to watch for | -|---------|------------------| -| **Reinventing the wheel** | AI creates new infrastructure instead of using existing utilities. ALWAYS check if a similar utility exists before accepting new wrapper code. This is the most expensive AI pattern — hundreds of lines of plausible code that duplicates what's already there. | -| **Over-engineering** | HttpClient injection "for testability", elevation auto-detection, speculative helper classes, unused overloads. If no caller needs it today, remove it. | -| **Swallowed errors** | AI catch blocks love to eat exceptions silently. Check EVERY catch block. Also check that exit codes are checked consistently. | -| **Ignoring target framework** | AI generates code for the newest .NET. Check every API call against the oldest supported target framework. | -| **Sloppy structure** | Multiple types in one file, block-scoped namespaces, #region directives, classes where records would do. New helpers marked `public` when `internal` suffices. | -| **Confidently wrong domain facts** | AI once claimed `ANDROID_SDK_ROOT` was the recommended variable (it's deprecated). Always verify domain-specific claims against official docs. | -| **Over-mocking** | Not everything needs to be mocked. Integration tests with `Assert.Ignore` on failure are fine and catch real API changes that mocks never will. | -| **Docs describe intent not reality** | AI doc comments often describe what the code *should* do, not what it *actually* does. Review doc comments against the implementation. | -| **Unused parameters** | AI adds `CancellationToken` parameters but never observes them, or accepts `additionalArgs` as a string and interpolates it into a command. Unused CancellationToken is a broken contract; string args are injection risks. | -| **Null-forgiving operator (`!`)** | The postfix `!` null-forgiving operator (e.g., `foo!.Bar`) is banned in most codebases. If the value can be null, add a proper null check. If it can't be null, make the parameter/variable non-nullable. AI frequently sprinkles `!` to silence the compiler — this turns compile-time warnings into runtime `NullReferenceException`s. Use `IsNullOrEmpty()` extension methods or null-coalescing instead. Note: this rule is about the postfix `!` operator, not the logical negation `!` (e.g., `if (!someBool)` or `if (!string.IsNullOrEmpty (s))`). | -| **`Debug.WriteLine` for logging** | AI catch blocks often log with `System.Diagnostics.Debug.WriteLine()` or `Console.WriteLine()` — neither integrates with most codebase logger patterns. | -| **`git commit --amend`** | AI uses `--amend` on commits that are already pushed or belong to another author. Always create new commits — the maintainer will squash as needed. | -| **Commit messages omit non-obvious choices** | Behavioral decisions and known limitations belong in the commit message, not just the code. | -| **Typos in user-visible strings** | Users copy-paste error messages into bug reports. Get them right. | -| **Filler words in docs** | "So" at the start of a sentence adds nothing. Be direct. | diff --git a/external/xamarin-android-tools/.github/skills/android-tools-reviewer/references/csharp-rules.md b/external/xamarin-android-tools/.github/skills/android-tools-reviewer/references/csharp-rules.md deleted file mode 100644 index c659dcd09f1..00000000000 --- a/external/xamarin-android-tools/.github/skills/android-tools-reviewer/references/csharp-rules.md +++ /dev/null @@ -1,109 +0,0 @@ -# C# Review Rules - -General C# and .NET guidance for code reviews. Applicable to any .NET repository. - ---- - -## 1. Target Framework Compatibility - -When a library multi-targets (e.g., `netstandard2.0` + a modern TFM), every API call must work on all targets. - -| Check | What to look for | -|-------|-----------------| -| **netstandard2.0 API surface** | Methods/overloads that only exist on net5+. Common traps: `HttpContent.ReadAsStringAsync(CancellationToken)`, `ProcessStartInfo.ArgumentList`, `Environment.IsPrivilegedProcess`, `ArrayPool` (needs `System.Buffers` package on ns2.0). When unsure, check MS Learn docs. | -| **C# language features** | `init` accessors, `required` keyword, file-scoped types, raw string literals — may need polyfills or `#if` guards. | -| **Conditional compilation** | New API usage should be behind `#if NET5_0_OR_GREATER` (or similar) with a fallback for netstandard2.0. | - ---- - -## 2. Async & Cancellation Patterns - -| Check | What to look for | -|-------|-----------------| -| **CancellationToken propagation** | Every `async` method that accepts a `CancellationToken` must pass it to ALL downstream async calls (`GetAsync`, `ReadAsStreamAsync`, `SendAsync`, etc.). A token that's accepted but never used is a broken contract. | -| **OperationCanceledException** | Catch-all blocks (`catch (Exception)`) must NOT swallow `OperationCanceledException`. Either catch it explicitly first and rethrow, or use a type filter. | -| **GetStringAsync** | On netstandard2.0, `GetStringAsync(url)` doesn't accept a `CancellationToken`. Use `GetAsync(url, ct)` + `ReadAsStringAsync()` instead. | -| **Thread safety of shared state** | If a new field or property can be accessed from multiple threads (e.g., static caches, event handlers), verify thread-safe access: `ConcurrentDictionary`, `Interlocked`, or explicit locks. A `Dictionary` read concurrently with a write is undefined behavior. | -| **Lock ordering** | If code acquires multiple locks, the order must be consistent everywhere. Document the ordering. Inconsistent ordering → deadlock. | -| **Avoid double-checked locking** | The double-checked locking (DCL) pattern is error-prone and [discouraged by Microsoft](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/volatile). Prefer `Lazy` or `LazyInitializer.EnsureInitialized()` — they handle thread-safe initialization correctly. If DCL is truly necessary (e.g., for performance in a hot path with evidence), verify all initialization completes before the field is assigned and no thread can observe a partially-initialized instance. | -| **Singleton initialization completeness** | When a singleton is initialized behind a lock, ensure ALL setup steps (not just construction) complete before publishing the instance. If `Initialize()` does `instance = new Foo(); instance.Setup();`, another thread can see `instance != null` and use it before `Setup()` runs. Either do all setup in the constructor, or don't publish the reference until all setup is done. | - ---- - -## 3. Resource Management - -| Check | What to look for | -|-------|-----------------| -| **HttpClient must be static** | `HttpClient` instances should be `static readonly` fields, not per-instance. Creating/disposing `HttpClient` leads to socket exhaustion via `TIME_WAIT` accumulation. See [Microsoft guidelines](https://learn.microsoft.com/dotnet/fundamentals/networking/http/httpclient-guidelines). | -| **No HttpClient injection (YAGNI)** | Don't add `HttpClient` constructor parameters "for testability" unless a caller actually needs it today. The AI tends to over-engineer this. | -| **ArrayPool for large buffers** | Buffers ≥ 1KB (especially 80KB+ download buffers) should use `ArrayPool.Shared.Rent()` with `try/finally` return. Large allocations go to the LOH and are expensive to GC. | -| **IDisposable** | Classes that own unmanaged resources or expensive managed resources must implement `IDisposable` with a dispose guard (`ThrowIfDisposed`). | - ---- - -## 4. Error Handling - -| Check | What to look for | -|-------|-----------------| -| **No empty catch blocks** | Every `catch` must capture the `Exception` and log it (or rethrow). No silent swallowing. Even "expected" exceptions should be logged for diagnostics. | -| **Validate parameters** | Enum parameters and string-typed "mode" values must be validated — throw `ArgumentException` or `NotSupportedException` for unexpected values. Don't silently accept garbage. | -| **Fail fast on critical ops** | If an operation like `chmod` or checksum verification fails, throw immediately. Silently continuing leads to confusing downstream failures. | -| **Mandatory verification** | Checksum/hash verification must NOT be optional. If the checksum can't be fetched, the operation must fail — not proceed unverified. | -| **Include actionable details in exceptions** | Use `nameof` for parameter names. Include the unsupported value or unexpected type. Never throw empty exceptions. | -| **Log messages must have context** | A bare `"Operation failed"` could be anything. Include *what* you were doing and relevant identifiers so the message is actionable without a debugger. | -| **Differentiate similar error messages** | Two messages saying `"X failed"` for different operations are impossible to debug. Make each unique so log output is unambiguous. | -| **Assert boundary invariants** | If a name=value pair array must have even length, assert `(length % 2) == 0` before indexing `[i+1]`. Validate structural assumptions at the boundary, not deep inside the logic. | -| **Initialize output parameters in all paths** | Methods with `out` parameters must initialize them in all error paths, not just the success path. | -| **Use `ThrowIf` helpers where available** | Prefer `ArgumentOutOfRangeException.ThrowIfNegative`, `ArgumentNullException.ThrowIfNull`, etc. over manual if-then-throw patterns where these helpers are available. In `netstandard2.0` projects where these helpers are unavailable, use explicit checks such as `if (x is null) throw new ArgumentNullException (nameof (x));`. | -| **Challenge exception swallowing** | When a PR adds `catch { continue; }` or `catch { return null; }`, question whether the exception is truly expected or masking a deeper problem. The default should be to let unexpected exceptions propagate. | - ---- - -## 5. Performance - -| Check | What to look for | -|-------|-----------------| -| **Reuse hot-path buffers** | Pool repeated `byte[]` allocations with `ArrayPool.Shared` (see `DownloadUtils.cs`). For fixed-size repeated reads, use a `readonly byte[]` instance field. Document non-thread-safety in ``. | -| **Reuse loop helpers** | If a loop creates resettable helpers (e.g., `TcpClient`), reuse one instance via `Reconnect()`/`Reset()` instead of `new` per iteration. | -| **XmlReader over LINQ XML** | For forward-only XML parsing (manifests, config files), use `XmlReader` — it's streaming and allocation-free. `XElement`/`XDocument` builds a full DOM tree. | -| **p/invoke over process spawn** | For single syscalls like `chmod`, use `[DllImport("libc")]` instead of spawning a child process. Process creation is orders of magnitude more expensive. | -| **Avoid intermediate collections** | Don't create two lists and `AddRange()` one to the other. Build a single list, or use LINQ to chain. | -| **Cache reusable arrays** | Char arrays for `string.Split()` (like whitespace chars) should be `static readonly` fields, not allocated on each call. | -| **Pre-allocate collections when size is known** | Use `new List(capacity)` or `new Dictionary(count)` when the size is known or estimable. Repeated resizing is O(n) allocation waste. | -| **Avoid closures in hot paths** | Lambdas that capture local variables allocate a closure object on every call. In loops or frequently-called methods, extract the lambda to a static method or cache the delegate. | -| **Place cheap checks before expensive ones** | In validation chains, test simple conditions (null checks, boolean flags) before allocating strings or doing I/O. Short-circuit with `&&`/`||`. | -| **Watch for O(n²)** | Nested loops over the same or related collections, repeated `.Contains()` on a `List`, or LINQ `.Where()` inside a loop are O(n²). Switch to `HashSet` or `Dictionary` for lookups. | -| **`HashSet.Add()` already handles duplicates** | Calling `.Contains()` before `.Add()` does the hash lookup twice. Just call `.Add()` — it returns `false` if the item already exists. | -| **Don't wrap a value in an interpolated string** | `$"{someString}"` creates an unnecessary `string.Format` call when `someString` is already a string. Just use `someString` directly. | -| **`Split()` with count parameter** | `line.Split(new char[]{'='}, 2)` prevents values containing `=` from being split incorrectly. Follow existing patterns that limit the split count. | -| **Cache repeated accessor calls** | If `foo.Bar.Baz` is used multiple times in a block, assign it to a local. This avoids repeated property evaluation and makes intent clearer. | -| **Extract throw helpers** | Code like `if (x) throw new SomeException(...)` in a frequently-called method prevents inlining. Extract into a `[DoesNotReturn]` helper so the JIT can inline the happy path. | - ---- - -## 6. Code Organization - -| Check | What to look for | -|-------|-----------------| -| **File-scoped namespaces** | New files should use `namespace Foo;` (not `namespace Foo { ... }`). Don't reformat existing files. | -| **Use `record` for data types** | Immutable data-carrier types (progress, version info, license info) should be `record` types. They get value equality, `ToString()`, and deconstruction for free. | -| **Document thread-safety invariants** | When a class reuses buffers, caches, or mutable state assuming single-caller semantics, add `This class is not thread-safe.` to the type. Callers need to know if external synchronization is required. | -| **Remove unused code** | Dead methods, speculative helpers, and code "for later" should be removed. Ship only what's needed. | -| **No commented-out code** | Commented-out code should be deleted — Git has history. | -| **Reduce indentation with early returns** | Invert conditions with `continue` or early `return` to reduce nesting. `foreach (var x in items ?? Array.Empty())` eliminates null-check nesting. | -| **Don't initialize fields to default values** | `bool flag = false;` and `int count = 0;` are noise. The CLR zero-initializes all fields. Only assign when the initial value is non-default. | -| **`sealed` classes skip full Dispose** | A `sealed` class doesn't need `Dispose(bool)` + `GC.SuppressFinalize`. Just implement `IDisposable.Dispose()` directly. The full pattern is only for unsealed base classes. | -| **Use interfaces over concrete types** | Fields and parameters should prefer interfaces (`IMetadataResolver`) over concrete classes. When the implementation changes, you swap the implementation — not every call site. | - ---- - -## 7. API Design - -| Check | What to look for | -|-------|-----------------| -| **Return `IReadOnlyList` not `List`** | Public methods should return `IReadOnlyList` (or `IReadOnlyCollection`) instead of mutable `List`. Prevents callers from mutating internal state. | -| **New helpers default to `internal`** | New utility methods should be `internal` unless a confirmed external consumer needs them. Use `InternalsVisibleTo` for test access. | -| **Structured args, not string interpolation** | Additional arguments to processes should be `IEnumerable`, not a single `string` that gets interpolated. | -| **Honor `CancellationToken`** | If a method accepts a `CancellationToken`, it MUST observe it — register a callback to kill processes, check `IsCancellationRequested` in loops, pass it to downstream async calls. Don't just accept it for API completeness. | -| **Add overloads to reduce caller ceremony** | If every caller performs the same conversion before calling a method, the method should have an overload that accepts the unconverted type directly. | -| **Prefer C# pattern matching** | Use `is`, `switch` expressions, and property patterns instead of `if`/`else` type-check chains. Pattern matching is more concise, avoids casts, and enables exhaustiveness checks. | diff --git a/external/xamarin-android-tools/.github/skills/android-tools-reviewer/references/msbuild-rules.md b/external/xamarin-android-tools/.github/skills/android-tools-reviewer/references/msbuild-rules.md deleted file mode 100644 index 617e80e42ab..00000000000 --- a/external/xamarin-android-tools/.github/skills/android-tools-reviewer/references/msbuild-rules.md +++ /dev/null @@ -1,31 +0,0 @@ -# MSBuild Review Rules - -Guidance for MSBuild tasks, targets, and props files. Applicable to any .NET repository with custom MSBuild infrastructure. - ---- - -## 1. Task Logging - -| Check | What to look for | -|-------|-----------------| -| **Don't use `Debug.WriteLine` or `Console.WriteLine`** | MSBuild tasks must use the task's logging facilities (e.g., `Log.LogMessage`, `Log.LogWarning`, `Log.LogError`). `Debug.WriteLine()` only reaches attached debuggers (invisible in CI). `Console.WriteLine()` bypasses MSBuild's logging pipeline entirely. | -| **Use appropriate log levels** | Use `MessageImportance.Low` for verbose diagnostics, `MessageImportance.Normal` for progress, and `MessageImportance.High` for important status. Don't spam high-importance messages. | - ---- - -## 2. Process Management in Tasks - -| Check | What to look for | -|-------|-----------------| -| **Don't redirect stdout/stderr without draining** | Background processes with `RedirectStandardOutput = true` must have async readers draining the output. Otherwise the OS pipe buffer fills and the child process deadlocks. For fire-and-forget processes, set `Redirect* = false`. | -| **Check exit codes consistently** | If one task operation checks the process exit code, ALL similar operations must too. Inconsistent error checking creates a false sense of safety. | -| **Include stdout in error diagnostics** | When a task captures stdout, pass it to error reporting so failure messages include all output, not just stderr. | - ---- - -## 3. Downstream Coordination - -| Check | What to look for | -|-------|-----------------| -| **Port, don't rewrite** | If a downstream consumer already has working logic for the same task, port it rather than writing new code. The existing code has real-world edge cases already handled. | -| **Draft downstream PR before merging** | Shared library changes should be accompanied by a draft PR in the consuming repo that proves the API actually works. Merge the library first, update the submodule pointer, then merge the consumer. | diff --git a/external/xamarin-android-tools/.github/skills/android-tools-reviewer/references/repo-conventions.md b/external/xamarin-android-tools/.github/skills/android-tools-reviewer/references/repo-conventions.md deleted file mode 100644 index 9f5926190e5..00000000000 --- a/external/xamarin-android-tools/.github/skills/android-tools-reviewer/references/repo-conventions.md +++ /dev/null @@ -1,58 +0,0 @@ -# Repo Conventions - - - -Patterns, naming, and style rules specific to this repository (`dotnet/android-tools`). Always loaded during reviews. - ---- - -## 1. Required Utilities - -| Check | What to look for | -|-------|-----------------| -| **Use `ProcessUtils`** | All process creation must go through `ProcessUtils.CreateProcessStartInfo()` and `ProcessUtils.StartProcess()`. No direct `new ProcessStartInfo()` or `Process.Start()`. Pass arguments as separate strings — `ProcessUtils` uses `ArgumentList` on net5+ and falls back to `Arguments` on netstandard2.0. | -| **Use `FileUtil`** | File extraction, downloads, checksum verification, and path operations belong in `FileUtil`. Don't duplicate file helpers in domain classes. | -| **Use `Action` logger** | Diagnostic output must use the existing `Action? logger` delegate pattern — never `System.Diagnostics.Debug.WriteLine()` or `Console.WriteLine()`. Methods that might be called from MSBuild tasks must accept a `logger` parameter and invoke it with the appropriate `TraceLevel`. See `AndroidSdkInfo.DefaultConsoleLogger` for the canonical implementation. | - ---- - -## 2. Null-Object Pattern - -| Check | What to look for | -|-------|-----------------| -| **Assign null-object sentinels early** | Methods accepting nullable dependencies (`IProgress?`, `ILogger?`, `Action?`) should assign a null-object sentinel early (e.g., `progress ??= NullProgress.Instance`) and then use the dependency without `?.` null checks throughout the method. Scattered `?.` calls are noise, invite missed spots, and signal a missing null-object type. If no null-object type exists yet, recommend creating one. | - ---- - -## 3. Naming & Constants - -| Check | What to look for | -|-------|-----------------| -| **Avoid ambiguous names** | Types that could collide with Android concepts (e.g., `ManifestComponent` vs `AndroidManifest.xml`) need disambiguating prefixes (e.g., `SdkManifestComponent`). | -| **No magic numbers** | Literal values like buffer sizes (`81920`), divisors (`1048576`), permission masks (`0x1ED` = 0755) should be named constants. | -| **Environment variable constants** | Use `EnvironmentVariableNames.AndroidHome` — not raw `"ANDROID_HOME"` strings. Typos in env var names produce silent, hard-to-debug failures. | -| **ANDROID_SDK_ROOT is deprecated** | Per [Android docs](https://developer.android.com/tools/variables#envar), use `ANDROID_HOME` everywhere. Do not introduce new references to `ANDROID_SDK_ROOT`. | - ---- - -## 4. File & Directory Patterns - -| Check | What to look for | -|-------|-----------------| -| **Version-based directories** | Install SDK/JDK to versioned paths (`cmdline-tools/19.0/`, not `cmdline-tools/latest/`). Versioned paths are self-documenting and allow side-by-side installs. | -| **Safe directory replacement** | Use move-with-rollback: rename existing → temp, move new in place, validate, delete temp only after validation succeeds. Never delete the backup before confirming the new install works. | -| **Cross-volume moves** | `Directory.Move` is really a rename — it fails across filesystems. Extract archives near the target path (same parent directory), or catch `IOException` and fall back to recursive copy + delete. | - ---- - -## 5. Code Style - -| Check | What to look for | -|-------|-----------------| -| **One type per file** | Each public class, struct, enum, or interface must be in its own `.cs` file named after the type. No multiple top-level types in a single file. | -| **No #region directives** | `#region` hides code and makes reviews harder. Remove them. This also applies to banner/section-separator comments (e.g., `// --- Device Tests ---`) — they signal the file should be split instead. | -| **Method names must reflect behavior** | If `CreateFoo()` sometimes returns an existing instance, rename it `GetOrCreateFoo()` or `GetFoo()`. | -| **Comments explain "why", not "what"** | `// increment i` adds nothing. `// skip the BOM — aapt2 chokes on it` explains intent. If a comment restates the code, delete it. | -| **Track TODOs as issues** | A `// TODO` hidden in code will be forgotten. File an issue and reference it in the comment. | -| **Remove stale comments** | If the code changed, update the comment. Comments that describe old behavior are misleading. | -| **Formatting** | Tabs (not spaces), K&R braces. See [Mono Coding Guidelines](http://www.mono-project.com/community/contributing/coding-guidelines/). Only format code you add or modify; never reformat existing lines. | diff --git a/external/xamarin-android-tools/.github/skills/android-tools-reviewer/references/security-rules.md b/external/xamarin-android-tools/.github/skills/android-tools-reviewer/references/security-rules.md deleted file mode 100644 index fa882b8e101..00000000000 --- a/external/xamarin-android-tools/.github/skills/android-tools-reviewer/references/security-rules.md +++ /dev/null @@ -1,21 +0,0 @@ -# Security Review Rules - -Security checklist for code reviews. Applicable to any repository handling file I/O, archives, or process execution. - ---- - -## 1. Archive & Path Safety - -| Check | What to look for | -|-------|-----------------| -| **Zip Slip protection** | Archive extraction must validate that every entry path, after `Path.GetFullPath()`, resolves under the destination directory. Never use `ZipFile.ExtractToDirectory()` for untrusted archives without entry-by-entry validation. | -| **Path traversal** | `StartsWith()` checks on paths must normalize with `Path.GetFullPath()` first. A path like `C:\Program Files\..\Users\evil` bypasses naive prefix checks. Also check for directory boundary issues (`C:\Program FilesX` matching `C:\Program Files`). | - ---- - -## 2. Process & Command Safety - -| Check | What to look for | -|-------|-----------------| -| **Command injection** | Arguments passed to external processes must be sanitized. Pass arguments as separate strings (not a single interpolated string) so they are never parsed by a shell. Never interpolate user/external input into command strings. | -| **Elevation** | Don't auto-elevate. Don't include `IsElevated()` helpers that silently re-launch elevated. The calling tool should handle elevation prompts. The library should error if it lacks permissions. | diff --git a/external/xamarin-android-tools/.github/skills/android-tools-reviewer/references/testing-rules.md b/external/xamarin-android-tools/.github/skills/android-tools-reviewer/references/testing-rules.md deleted file mode 100644 index fbddfa375af..00000000000 --- a/external/xamarin-android-tools/.github/skills/android-tools-reviewer/references/testing-rules.md +++ /dev/null @@ -1,12 +0,0 @@ -# Testing Review Rules - -Guidance for test code reviews. Loaded when test files change. - ---- - -| Check | What to look for | -|-------|-----------------| -| **Bug fixes need regression tests** | Every PR that fixes a bug should include a test that fails without the fix and passes with it. If the PR description says "fixes #N" but adds no test, ask for one. | -| **Test assertions must be specific** | `Assert.IsNotNull(result)` or `Assert.IsTrue(success)` don't tell you what went wrong. Prefer `Assert.AreEqual(expected, actual)` or NUnit constraints (`Assert.That` with `Does.Contain`, `Is.EqualTo`, etc.) for richer failure messages. | -| **Deterministic test data** | Tests should not depend on system locale, timezone, or current date. Use explicit `CultureInfo.InvariantCulture` and hardcoded dates when testing formatting. | -| **Test edge cases** | Empty collections, null inputs, boundary values, concurrent calls, and very large inputs should all be considered. If the PR only tests the happy path, suggest edge cases. | diff --git a/external/xamarin-android-tools/.github/workflows/agentics-maintenance.yml b/external/xamarin-android-tools/.github/workflows/agentics-maintenance.yml deleted file mode 100644 index dfd2eaf277c..00000000000 --- a/external/xamarin-android-tools/.github/workflows/agentics-maintenance.yml +++ /dev/null @@ -1,608 +0,0 @@ -# This file was automatically generated by pkg/workflow/maintenance_workflow.go (v0.79.8). DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md -# -# ___ _ _ -# / _ \ | | (_) -# | |_| | __ _ ___ _ __ | |_ _ ___ -# | _ |/ _` |/ _ \ '_ \| __| |/ __| -# | | | | (_| | __/ | | | |_| | (__ -# \_| |_/\__, |\___|_| |_|\__|_|\___| -# __/ | -# _ _ |___/ -# | | | | / _| | -# | | | | ___ _ __ _ __| |_| | _____ ____ -# | |/\| |/ _ \ '__| |/ /| _| |/ _ \ \ /\ / / ___| -# \ /\ / (_) | | | | ( | | | | (_) \ V V /\__ \ -# \/ \/ \___/|_| |_|\_\|_| |_|\___/ \_/\_/ |___/ -# -# -# To regenerate this workflow, run: -# gh aw compile -# Not all edits will cause changes to this file. -# -# For more information: https://github.github.com/gh-aw/introduction/overview/ -# -# Alternative regeneration methods: -# make recompile -# -# Or use the gh-aw CLI directly: -# ./gh-aw compile --validate --verbose -# -# The workflow is generated when any workflow uses the 'expires' field -# in create-discussions, create-issues, or create-pull-request safe-outputs configuration. -# Schedule frequency is automatically determined by the shortest expiration time. -# -name: Agentic Maintenance - -on: - schedule: - - cron: "37 0 * * *" # Daily (based on minimum expires: 30 days) - workflow_dispatch: - inputs: - operation: - description: 'Optional maintenance operation to run' - required: false - type: choice - default: '' - options: - - '' - - 'disable' - - 'enable' - - 'update' - - 'upgrade' - - 'safe_outputs' - - 'create_labels' - - 'activity_report' - - 'close_agentic_workflows_issues' - - 'clean_cache_memories' - - 'update_pull_request_branches' - - 'validate' - - 'forecast' - run_url: - description: 'Run URL or run ID to replay safe outputs from (e.g. https://github.com/owner/repo/actions/runs/12345 or 12345). Required when operation is safe_outputs.' - required: false - type: string - default: '' - workflow_call: - inputs: - operation: - description: 'Optional maintenance operation to run (disable, enable, update, upgrade, safe_outputs, create_labels, activity_report, close_agentic_workflows_issues, clean_cache_memories, update_pull_request_branches, validate, forecast)' - required: false - type: string - default: '' - run_url: - description: 'Run URL or run ID to replay safe outputs from (e.g. https://github.com/owner/repo/actions/runs/12345 or 12345). Required when operation is safe_outputs.' - required: false - type: string - default: '' - outputs: - operation_completed: - description: 'The maintenance operation that was completed (empty when none ran or a scheduled job ran)' - value: ${{ jobs.run_operation.outputs.operation || inputs.operation }} - applied_run_url: - description: 'The run URL that safe outputs were applied from' - value: ${{ jobs.apply_safe_outputs.outputs.run_url }} - -permissions: {} - -jobs: - close-expired-entities: - if: ${{ (!(github.event.repository.fork)) && github.event_name != 'push' && (github.event_name != 'workflow_dispatch' && github.event_name != 'workflow_call' || inputs.operation == '') }} - runs-on: ubuntu-slim - permissions: - discussions: write - issues: write - pull-requests: write - steps: - - name: Setup Scripts - uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 - with: - destination: ${{ runner.temp }}/gh-aw/actions - - - name: Close expired discussions - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - with: - script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/close_expired_discussions.cjs'); - await main(); - - - name: Close expired issues - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - with: - script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/close_expired_issues.cjs'); - await main(); - - - name: Close expired pull requests - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - with: - script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/close_expired_pull_requests.cjs'); - await main(); - - cleanup-cache-memory: - if: ${{ (!(github.event.repository.fork)) && github.event_name != 'push' && (github.event_name != 'workflow_dispatch' && github.event_name != 'workflow_call' || inputs.operation == '' || inputs.operation == 'clean_cache_memories') }} - runs-on: ubuntu-slim - permissions: - actions: write - steps: - - name: Setup Scripts - uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 - with: - destination: ${{ runner.temp }}/gh-aw/actions - - - name: Cleanup outdated cache-memory entries - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - with: - script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/cleanup_cache_memory.cjs'); - await main(); - - run_operation: - if: ${{ (github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && inputs.operation != '' && inputs.operation != 'safe_outputs' && inputs.operation != 'create_labels' && inputs.operation != 'activity_report' && inputs.operation != 'close_agentic_workflows_issues' && inputs.operation != 'clean_cache_memories' && inputs.operation != 'update_pull_request_branches' && inputs.operation != 'validate' && inputs.operation != 'forecast' && (!(github.event.repository.fork)) }} - runs-on: ubuntu-slim - permissions: - actions: write - contents: write - pull-requests: write - outputs: - operation: ${{ steps.record.outputs.operation }} - steps: - - name: Checkout repository - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - with: - persist-credentials: false - - - name: Setup Scripts - uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 - with: - destination: ${{ runner.temp }}/gh-aw/actions - - - name: Check admin/maintainer permissions - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/check_team_member.cjs'); - await main(); - - - name: Install gh-aw - uses: github/gh-aw-actions/setup-cli@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 - with: - version: v0.79.8 - - - name: Run operation - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - GH_AW_OPERATION: ${{ inputs.operation }} - GH_AW_CMD_PREFIX: gh aw - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/run_operation_update_upgrade.cjs'); - await main(); - - - name: Record outputs - id: record - run: echo "operation=${{ inputs.operation }}" >> "$GITHUB_OUTPUT" - - update_pull_request_branches: - if: ${{ (github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && inputs.operation == 'update_pull_request_branches' && (!(github.event.repository.fork)) }} - runs-on: ubuntu-slim - permissions: - contents: write - pull-requests: write - steps: - - name: Setup Scripts - uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 - with: - destination: ${{ runner.temp }}/gh-aw/actions - - - name: Check admin/maintainer permissions - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/check_team_member.cjs'); - await main(); - - - name: Update pull request branches - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/update_pull_request_branches.cjs'); - await main(); - - apply_safe_outputs: - if: ${{ (github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && inputs.operation == 'safe_outputs' && (!(github.event.repository.fork)) }} - runs-on: ubuntu-slim - permissions: - actions: read - contents: write - discussions: write - issues: write - pull-requests: write - outputs: - run_url: ${{ steps.record.outputs.run_url }} - steps: - - name: Checkout actions folder - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - with: - sparse-checkout: | - actions - persist-credentials: false - - - name: Setup Scripts - uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 - with: - destination: ${{ runner.temp }}/gh-aw/actions - - - name: Check admin/maintainer permissions - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/check_team_member.cjs'); - await main(); - - - name: Apply Safe Outputs - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - GH_AW_RUN_URL: ${{ inputs.run_url }} - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/apply_safe_outputs_replay.cjs'); - await main(); - - - name: Record outputs - id: record - run: echo "run_url=${{ inputs.run_url }}" >> "$GITHUB_OUTPUT" - - create_labels: - if: ${{ (github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && inputs.operation == 'create_labels' && (!(github.event.repository.fork)) }} - runs-on: ubuntu-slim - permissions: - contents: read - issues: write - steps: - - name: Checkout repository - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - with: - persist-credentials: false - - - name: Setup Scripts - uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 - with: - destination: ${{ runner.temp }}/gh-aw/actions - - - name: Check admin/maintainer permissions - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/check_team_member.cjs'); - await main(); - - - name: Install gh-aw - uses: github/gh-aw-actions/setup-cli@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 - with: - version: v0.79.8 - - - name: Create missing labels - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - env: - GH_AW_CMD_PREFIX: gh aw - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/create_labels.cjs'); - await main(); - - activity_report: - if: ${{ (github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && inputs.operation == 'activity_report' && (!(github.event.repository.fork)) }} - runs-on: ubuntu-slim - timeout-minutes: 120 - permissions: - actions: read - contents: read - issues: write - steps: - - name: Checkout repository - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - with: - persist-credentials: false - - - name: Setup Scripts - uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 - with: - destination: ${{ runner.temp }}/gh-aw/actions - - - name: Check admin/maintainer permissions - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/check_team_member.cjs'); - await main(); - - - name: Install gh-aw - uses: github/gh-aw-actions/setup-cli@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 - with: - version: v0.79.8 - - - name: Restore activity report logs cache - id: activity_report_logs_cache - uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 - with: - path: ./.cache/gh-aw/activity-report-logs - key: ${{ runner.os }}-activity-report-logs-${{ github.repository }}-${{ github.ref_name }}-${{ github.run_id }} - restore-keys: | - ${{ runner.os }}-activity-report-logs-${{ github.repository }}- - ${{ runner.os }}-activity-report-logs- - - name: Download activity report logs - timeout-minutes: 20 - shell: bash - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - GH_AW_CMD_PREFIX: gh aw - run: | - ${GH_AW_CMD_PREFIX} logs \ - --repo "${{ github.repository }}" \ - --start-date -1w \ - --count 100 \ - --output ./.cache/gh-aw/activity-report-logs \ - --format markdown \ - > ./.cache/gh-aw/activity-report-logs/report.md - - - name: Save activity report logs cache - if: ${{ always() }} - uses: actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 - with: - path: ./.cache/gh-aw/activity-report-logs - key: ${{ steps.activity_report_logs_cache.outputs.cache-primary-key }} - - - name: Generate activity report issue - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - script: | - const fs = require('node:fs'); - const reportPath = './.cache/gh-aw/activity-report-logs/report.md'; - if (!fs.existsSync(reportPath)) { - core.warning('Activity report markdown not found at ' + reportPath + '; skipping issue creation.'); - return; - } - let reportBody = ''; - try { - reportBody = fs.readFileSync(reportPath, 'utf8').trim(); - } catch (error) { - core.warning('Failed to read activity report markdown at ' + reportPath + ': ' + error.message); - return; - } - if (!reportBody) { - core.warning('Activity report markdown is empty at ' + reportPath + '; skipping issue creation.'); - return; - } - const repoSlug = context.repo.owner + '/' + context.repo.repo; - const body = [ - '### Agentic workflow activity report', - '', - 'Repository: ' + repoSlug, - 'Generated at: ' + new Date().toISOString(), - '', - reportBody, - ].join('\n'); - const createdIssue = await github.rest.issues.create({ - owner: context.repo.owner, - repo: context.repo.repo, - title: '[aw] agentic status report', - body, - labels: ['agentic-workflows'], - }); - core.info('Created issue #' + createdIssue.data.number + ': ' + createdIssue.data.html_url); - - forecast_report: - if: ${{ (github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && inputs.operation == 'forecast' && (!(github.event.repository.fork)) }} - runs-on: ubuntu-slim - timeout-minutes: 60 - permissions: - actions: read - contents: read - issues: write - steps: - - name: Checkout repository - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - with: - persist-credentials: false - - - name: Setup Scripts - uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 - with: - destination: ${{ runner.temp }}/gh-aw/actions - - - name: Check admin/maintainer permissions - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/check_team_member.cjs'); - await main(); - - - name: Install gh-aw - uses: github/gh-aw-actions/setup-cli@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 - with: - version: v0.79.8 - - - name: Restore forecast report logs cache - id: forecast_report_logs_cache - uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 - with: - path: ./.github/aw/logs - key: ${{ runner.os }}-forecast-report-logs-${{ github.repository }}-${{ github.ref_name }}-${{ github.run_id }} - restore-keys: | - ${{ runner.os }}-forecast-report-logs-${{ github.repository }}- - ${{ runner.os }}-forecast-report-logs- - - - name: Generate forecast report - id: generate_forecast_report - timeout-minutes: 30 - shell: bash - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - DEBUG: "*" - GH_AW_CMD_PREFIX: gh aw - run: | - mkdir -p ./.cache/gh-aw/forecast - set +e - ${GH_AW_CMD_PREFIX} forecast --repo "${{ github.repository }}" --timeout 30 --verbose --json > ./.cache/gh-aw/forecast/report.json - forecast_exit_code=$? - set -e - if [ "${forecast_exit_code}" -eq 124 ]; then - echo '{"outcome":"timeout","message":"Forecast computation timed out after 30 minutes."}' > ./.cache/gh-aw/forecast/error.json - echo "::error::Forecast computation timed out after 30 minutes." - exit 1 - fi - if [ "${forecast_exit_code}" -ne 0 ]; then - echo '{"outcome":"error","message":"Forecast computation failed before producing a report."}' > ./.cache/gh-aw/forecast/error.json - echo "::error::Forecast computation failed with exit code ${forecast_exit_code}." - exit 1 - fi - - - name: Debug forecast logs folder - if: ${{ always() }} - shell: bash - run: | - if [ ! -d ./.github/aw/logs ]; then - echo "Logs directory not found: ./.github/aw/logs" - exit 0 - fi - echo "Files under ./.github/aw/logs:" - find ./.github/aw/logs -type f | sort - - - name: Save forecast report logs cache - if: ${{ always() }} - uses: actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 - with: - path: ./.github/aw/logs - key: ${{ runner.os }}-forecast-report-logs-${{ github.repository }}-${{ github.ref_name }}-${{ github.run_id }} - - - name: Generate forecast issue - if: ${{ always() }} - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - env: - FORECAST_STEP_OUTCOME: ${{ steps.generate_forecast_report.outcome }} - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/create_forecast_issue.cjs'); - await main(); - - close_agentic_workflows_issues: - if: ${{ (github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && inputs.operation == 'close_agentic_workflows_issues' && (!(github.event.repository.fork)) }} - runs-on: ubuntu-slim - permissions: - issues: write - steps: - - name: Setup Scripts - uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 - with: - destination: ${{ runner.temp }}/gh-aw/actions - - - name: Check admin/maintainer permissions - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/check_team_member.cjs'); - await main(); - - - name: Close no-repro agentic-workflows issues - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/close_agentic_workflows_issues.cjs'); - await main(); - - validate_workflows: - if: ${{ (github.event_name == 'workflow_dispatch' || github.event_name == 'workflow_call') && inputs.operation == 'validate' && (!(github.event.repository.fork)) }} - runs-on: ubuntu-latest - permissions: - contents: read - issues: write - steps: - - name: Checkout repository - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 - with: - persist-credentials: false - - - name: Setup Scripts - uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 - with: - destination: ${{ runner.temp }}/gh-aw/actions - - - name: Check admin/maintainer permissions - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/check_team_member.cjs'); - await main(); - - - name: Install gh-aw - uses: github/gh-aw-actions/setup-cli@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 - with: - version: v0.79.8 - - - name: Validate workflows and file issue on findings - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - env: - GH_AW_CMD_PREFIX: gh aw - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/run_validate_workflows.cjs'); - await main(); diff --git a/external/xamarin-android-tools/.github/workflows/android-tools-reviewer.lock.yml b/external/xamarin-android-tools/.github/workflows/android-tools-reviewer.lock.yml deleted file mode 100644 index 36be6842a7c..00000000000 --- a/external/xamarin-android-tools/.github/workflows/android-tools-reviewer.lock.yml +++ /dev/null @@ -1,1734 +0,0 @@ -# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"e0faed06be2be28082e53de5c31e62440e1c2f42b88ba18d39b5bf90562cbb5a","body_hash":"1b21a58fc25436cc68541bee79e0d82f2b44fb168383670638bc2cfc46f89483","compiler_version":"v0.80.9","strict":true,"agent_id":"copilot","agent_model":"claude-opus-4.8","engine_versions":{"copilot":"1.0.63"}} -# gh-aw-manifest: {"version":1,"secrets":["COPILOT_PAT_0","COPILOT_PAT_1","COPILOT_PAT_2","COPILOT_PAT_3","COPILOT_PAT_4","COPILOT_PAT_5","COPILOT_PAT_6","COPILOT_PAT_7","COPILOT_PAT_8","COPILOT_PAT_9","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0","version":"v7.0.0"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"8c7d04ebf1ece56cd381446125da3e0f6896294a","version":"v0.80.9"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.7","digest":"sha256:aae231e4635c8999d039c132f1602d3df850fe9b84a00aa2b5ac981179b5661c","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.7@sha256:aae231e4635c8999d039c132f1602d3df850fe9b84a00aa2b5ac981179b5661c"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.7","digest":"sha256:009caf2e3d88fa77b64e9a03a95a228fc58db0f1701c6d324b29ba5a3c7c79b6","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.7@sha256:009caf2e3d88fa77b64e9a03a95a228fc58db0f1701c6d324b29ba5a3c7c79b6"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.7","digest":"sha256:deb1d4e19de62d51cee0508057a596a19315c3423ada4d675cad136dc8037c96","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.7@sha256:deb1d4e19de62d51cee0508057a596a19315c3423ada4d675cad136dc8037c96"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.27","digest":"sha256:fe984bddde4ec05d756d9043edb0a32912e6b7b72f6a121b1082f29221421cc7","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.27@sha256:fe984bddde4ec05d756d9043edb0a32912e6b7b72f6a121b1082f29221421cc7"},{"image":"ghcr.io/github/gh-aw-node","digest":"sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b","pinned_image":"ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b"},{"image":"ghcr.io/github/github-mcp-server:v1.4.0","digest":"sha256:2afb26356481d1a350e14544a6e160f7f7ec1561a1ea309b823665abf0309036","pinned_image":"ghcr.io/github/github-mcp-server:v1.4.0@sha256:2afb26356481d1a350e14544a6e160f7f7ec1561a1ea309b823665abf0309036"}]} -# This file was automatically generated by gh-aw (v0.80.9). DO NOT EDIT. To debug this workflow, load the skill at https://github.com/github/gh-aw/blob/main/debug.md -# -# ___ _ _ -# / _ \ | | (_) -# | |_| | __ _ ___ _ __ | |_ _ ___ -# | _ |/ _` |/ _ \ '_ \| __| |/ __| -# | | | | (_| | __/ | | | |_| | (__ -# \_| |_/\__, |\___|_| |_|\__|_|\___| -# __/ | -# _ _ |___/ -# | | | | / _| | -# | | | | ___ _ __ _ __| |_| | _____ ____ -# | |/\| |/ _ \ '__| |/ /| _| |/ _ \ \ /\ / / ___| -# \ /\ / (_) | | | | ( | | | | (_) \ V V /\__ \ -# \/ \/ \___/|_| |_|\_\|_| |_|\___/ \_/\_/ |___/ -# -# -# To update this file, edit the corresponding .md file and run: -# gh aw compile -# Not all edits will cause changes to this file. -# -# For more information: https://github.github.com/gh-aw/introduction/overview/ -# -# -# Resolved workflow manifest: -# Imports: -# - shared/pat_pool.md -# -# Secrets used: -# - COPILOT_PAT_0 -# - COPILOT_PAT_1 -# - COPILOT_PAT_2 -# - COPILOT_PAT_3 -# - COPILOT_PAT_4 -# - COPILOT_PAT_5 -# - COPILOT_PAT_6 -# - COPILOT_PAT_7 -# - COPILOT_PAT_8 -# - COPILOT_PAT_9 -# - GITHUB_TOKEN -# -# Custom actions used: -# - actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 -# - actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 -# - actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 -# - actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 -# - actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 -# - github/gh-aw-actions/setup@8c7d04ebf1ece56cd381446125da3e0f6896294a # v0.80.9 -# -# Container images used: -# - ghcr.io/github/gh-aw-firewall/agent:0.27.7@sha256:aae231e4635c8999d039c132f1602d3df850fe9b84a00aa2b5ac981179b5661c -# - ghcr.io/github/gh-aw-firewall/api-proxy:0.27.7@sha256:009caf2e3d88fa77b64e9a03a95a228fc58db0f1701c6d324b29ba5a3c7c79b6 -# - ghcr.io/github/gh-aw-firewall/squid:0.27.7@sha256:deb1d4e19de62d51cee0508057a596a19315c3423ada4d675cad136dc8037c96 -# - ghcr.io/github/gh-aw-mcpg:v0.3.27@sha256:fe984bddde4ec05d756d9043edb0a32912e6b7b72f6a121b1082f29221421cc7 -# - ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b -# - ghcr.io/github/github-mcp-server:v1.4.0@sha256:2afb26356481d1a350e14544a6e160f7f7ec1561a1ea309b823665abf0309036 - -name: "Android Tools PR Reviewer" -on: - issue_comment: - types: - - created - - edited - # roles: # Roles processed as role check in pre-activation job - # - admin # Roles processed as role check in pre-activation job - # - maintainer # Roles processed as role check in pre-activation job - # - write # Roles processed as role check in pre-activation job - -permissions: {} - -concurrency: - group: "gh-aw-${{ github.workflow }}-${{ github.event.issue.number || github.event.pull_request.number || github.run_id }}" - -run-name: "Android Tools PR Reviewer" - -jobs: - activation: - needs: - - pat_pool - - pre_activation - if: "needs.pre_activation.outputs.activated == 'true' && (github.event_name == 'issue_comment' && (startsWith(github.event.comment.body, '/review ') || startsWith(github.event.comment.body, '/review\n') || github.event.comment.body == '/review') && github.event.issue.pull_request != null || !(github.event_name == 'issue_comment'))" - runs-on: ubuntu-slim - permissions: - actions: read - contents: read - issues: write - pull-requests: write - outputs: - body: ${{ steps.sanitized.outputs.body }} - comment_id: ${{ steps.add-comment.outputs.comment-id }} - comment_repo: ${{ steps.add-comment.outputs.comment-repo }} - comment_url: ${{ steps.add-comment.outputs.comment-url }} - engine_id: ${{ steps.generate_aw_info.outputs.engine_id }} - lockdown_check_failed: ${{ steps.generate_aw_info.outputs.lockdown_check_failed == 'true' }} - model: ${{ steps.generate_aw_info.outputs.model }} - setup-parent-span-id: ${{ steps.setup.outputs.parent-span-id || steps.setup.outputs.span-id }} - setup-span-id: ${{ steps.setup.outputs.span-id }} - setup-trace-id: ${{ steps.setup.outputs.trace-id }} - slash_command: ${{ needs.pre_activation.outputs.matched_command }} - stale_lock_file_failed: ${{ steps.check-lock-file.outputs.stale_lock_file_failed == 'true' }} - text: ${{ steps.sanitized.outputs.text }} - title: ${{ steps.sanitized.outputs.title }} - steps: - - name: Setup Scripts - id: setup - uses: github/gh-aw-actions/setup@8c7d04ebf1ece56cd381446125da3e0f6896294a # v0.80.9 - with: - destination: ${{ runner.temp }}/gh-aw/actions - job-name: ${{ github.job }} - trace-id: ${{ needs.pre_activation.outputs.setup-trace-id }} - parent-span-id: ${{ needs.pre_activation.outputs.setup-parent-span-id || needs.pre_activation.outputs.setup-span-id }} - env: - GH_AW_SETUP_WORKFLOW_NAME: "Android Tools PR Reviewer" - GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/android-tools-reviewer.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.63" - GH_AW_INFO_AWF_VERSION: "v0.27.7" - GH_AW_INFO_ENGINE_ID: "copilot" - - name: Generate agentic run info - id: generate_aw_info - env: - GH_AW_INFO_ENGINE_ID: "copilot" - GH_AW_INFO_ENGINE_NAME: "GitHub Copilot CLI" - GH_AW_INFO_MODEL: "claude-opus-4.8" - GH_AW_INFO_VERSION: "1.0.63" - GH_AW_INFO_AGENT_VERSION: "1.0.63" - GH_AW_INFO_CLI_VERSION: "v0.80.9" - GH_AW_INFO_WORKFLOW_NAME: "Android Tools PR Reviewer" - GH_AW_INFO_EXPERIMENTAL: "false" - GH_AW_INFO_SUPPORTS_TOOLS_ALLOWLIST: "true" - GH_AW_INFO_STAGED: "false" - GH_AW_INFO_ALLOWED_DOMAINS: '["defaults","dotnet","github","aka.ms","microsoft.com"]' - GH_AW_INFO_FIREWALL_ENABLED: "true" - GH_AW_INFO_AWF_VERSION: "v0.27.7" - GH_AW_INFO_AWMG_VERSION: "" - GH_AW_INFO_FIREWALL_TYPE: "squid" - GH_AW_COMPILED_STRICT: "true" - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - with: - script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/generate_aw_info.cjs'); - await main(core, context); - - name: Add eyes reaction for immediate feedback - id: react - if: github.event_name == 'issues' || github.event_name == 'issue_comment' || github.event_name == 'pull_request_review_comment' || github.event_name == 'discussion' || github.event_name == 'discussion_comment' || github.event_name == 'pull_request' && github.event.pull_request.head.repo.id == github.repository_id - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - env: - GH_AW_REACTION: "eyes" - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/add_reaction.cjs'); - await main(); - - name: Checkout .github and .agents folders - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - with: - persist-credentials: false - sparse-checkout: | - .github - .agents - .antigravity - .claude - .codex - .crush - .gemini - .opencode - .pi - sparse-checkout-cone-mode: true - fetch-depth: 1 - - name: Save agent config folders for base branch restoration - env: - GH_AW_AGENT_FOLDERS: ".agents .antigravity .claude .codex .crush .gemini .github .opencode .pi" - GH_AW_AGENT_FILES: ".crush.json AGENTS.md ANTIGRAVITY.md CLAUDE.md GEMINI.md PI.md opencode.jsonc" - # poutine:ignore untrusted_checkout_exec - run: bash "${RUNNER_TEMP}/gh-aw/actions/save_base_github_folders.sh" - - name: Check workflow lock file - id: check-lock-file - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - env: - GH_AW_WORKFLOW_FILE: "android-tools-reviewer.lock.yml" - GH_AW_CONTEXT_WORKFLOW_REF: "${{ github.workflow_ref }}" - with: - script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/check_workflow_timestamp_api.cjs'); - await main(); - - name: Check compile-agentic version - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - env: - GH_AW_COMPILED_VERSION: "v0.80.9" - with: - script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/check_version_updates.cjs'); - await main(); - - name: Compute current body text - id: sanitized - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - env: - GH_AW_ALLOWED_DOMAINS: "*.githubusercontent.com,*.vsblob.vsassets.io,aka.ms,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.nuget.org,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,azuresearch-usnc.nuget.org,azuresearch-ussc.nuget.org,builds.dotnet.microsoft.com,ci.dot.net,codeload.github.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,dc.services.visualstudio.com,dist.nuget.org,docs.github.com,dot.net,dotnet.microsoft.com,dotnetcli.blob.core.windows.net,github-cloud.githubusercontent.com,github-cloud.s3.amazonaws.com,github.blog,github.com,github.githubassets.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,lfs.github.com,microsoft.com,nuget.org,nuget.pkg.github.com,nugetregistryv2prod.blob.core.windows.net,objects.githubusercontent.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,oneocsp.microsoft.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,patch-diff.githubusercontent.com,pkgs.dev.azure.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com,www.microsoft.com" - with: - script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/compute_text.cjs'); - await main(); - - name: Add comment with workflow run link - id: add-comment - if: github.event_name == 'issues' || github.event_name == 'issue_comment' || github.event_name == 'pull_request_review_comment' || github.event_name == 'discussion' || github.event_name == 'discussion_comment' || github.event_name == 'pull_request' && github.event.pull_request.head.repo.id == github.repository_id - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - env: - GH_AW_WORKFLOW_NAME: "Android Tools PR Reviewer" - with: - script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/add_workflow_run_comment.cjs'); - await main(); - - name: Create prompt with built-in context - env: - GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt - GH_AW_SAFE_OUTPUTS: ${{ runner.temp }}/gh-aw/safeoutputs/outputs.jsonl - GH_AW_EXPR_1A3A194A: ${{ github.event.discussion.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'discussion' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }} - GH_AW_EXPR_463A214A: ${{ github.event.pull_request.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'pull_request' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }} - GH_AW_EXPR_802A9F6A: ${{ github.event.issue.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'issue' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }} - GH_AW_EXPR_FF1D34CE: ${{ github.event.comment.id || fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').comment_id }} - GH_AW_GITHUB_ACTOR: ${{ github.actor }} - GH_AW_GITHUB_REPOSITORY: ${{ github.repository }} - GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} - GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }} - GH_AW_IS_PR_COMMENT: ${{ github.event.issue.pull_request && 'true' || '' }} - # poutine:ignore untrusted_checkout_exec - run: | - bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh" - { - cat << 'GH_AW_PROMPT_6509d4c84633d1dc_EOF' - - GH_AW_PROMPT_6509d4c84633d1dc_EOF - cat "${RUNNER_TEMP}/gh-aw/prompts/xpia.md" - cat "${RUNNER_TEMP}/gh-aw/prompts/temp_folder_prompt.md" - cat "${RUNNER_TEMP}/gh-aw/prompts/markdown.md" - cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_prompt.md" - cat << 'GH_AW_PROMPT_6509d4c84633d1dc_EOF' - - Tools: create_pull_request_review_comment(max:50), submit_pull_request_review, missing_tool, missing_data, noop - - GH_AW_PROMPT_6509d4c84633d1dc_EOF - cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md" - cat << 'GH_AW_PROMPT_6509d4c84633d1dc_EOF' - - The following GitHub context information is available for this workflow: - {{#if github.actor}} - - **actor**: __GH_AW_GITHUB_ACTOR__ - {{/if}} - {{#if github.repository}} - - **repository**: __GH_AW_GITHUB_REPOSITORY__ - {{/if}} - {{#if github.workspace}} - - **workspace**: __GH_AW_GITHUB_WORKSPACE__ - {{/if}} - {{#if github.event.issue.number || (github.aw.context.item_type == 'issue' && github.aw.context.item_number)}} - - **issue-number**: #__GH_AW_EXPR_802A9F6A__ - {{/if}} - {{#if github.event.discussion.number || (github.aw.context.item_type == 'discussion' && github.aw.context.item_number)}} - - **discussion-number**: #__GH_AW_EXPR_1A3A194A__ - {{/if}} - {{#if github.event.pull_request.number || (github.aw.context.item_type == 'pull_request' && github.aw.context.item_number)}} - - **pull-request-number**: #__GH_AW_EXPR_463A214A__ - {{/if}} - {{#if github.event.comment.id || github.aw.context.comment_id}} - - **comment-id**: __GH_AW_EXPR_FF1D34CE__ - {{/if}} - {{#if github.run_id}} - - **workflow-run-id**: __GH_AW_GITHUB_RUN_ID__ - {{/if}} - - - GH_AW_PROMPT_6509d4c84633d1dc_EOF - cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md" - if [ "$GITHUB_EVENT_NAME" = "issue_comment" ] && [ -n "$GH_AW_IS_PR_COMMENT" ] || [ "$GITHUB_EVENT_NAME" = "pull_request_review_comment" ] || [ "$GITHUB_EVENT_NAME" = "pull_request_review" ]; then - cat "${RUNNER_TEMP}/gh-aw/prompts/pr_context_prompt.md" - fi - cat << 'GH_AW_PROMPT_6509d4c84633d1dc_EOF' - - {{#runtime-import .github/workflows/android-tools-reviewer.md}} - GH_AW_PROMPT_6509d4c84633d1dc_EOF - } > "$GH_AW_PROMPT" - - name: Interpolate variables and render templates - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - env: - GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt - GH_AW_ENGINE_ID: "copilot" - with: - script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/interpolate_prompt.cjs'); - await main(); - - name: Substitute placeholders - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - env: - GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt - GH_AW_EXPR_1A3A194A: ${{ github.event.discussion.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'discussion' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }} - GH_AW_EXPR_463A214A: ${{ github.event.pull_request.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'pull_request' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }} - GH_AW_EXPR_802A9F6A: ${{ github.event.issue.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'issue' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }} - GH_AW_EXPR_FF1D34CE: ${{ github.event.comment.id || fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').comment_id }} - GH_AW_GITHUB_ACTOR: ${{ github.actor }} - GH_AW_GITHUB_REPOSITORY: ${{ github.repository }} - GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} - GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }} - GH_AW_IS_PR_COMMENT: ${{ github.event.issue.pull_request && 'true' || '' }} - GH_AW_MCP_CLI_SERVERS_LIST: '- `safeoutputs` — run `safeoutputs --help` to see available tools' - GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ACTIVATED: ${{ needs.pre_activation.outputs.activated }} - GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_MATCHED_COMMAND: ${{ needs.pre_activation.outputs.matched_command }} - with: - script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io, getOctokit); - - const substitutePlaceholders = require('${{ runner.temp }}/gh-aw/actions/substitute_placeholders.cjs'); - - // Call the substitution function - return await substitutePlaceholders({ - file: process.env.GH_AW_PROMPT, - substitutions: { - GH_AW_EXPR_1A3A194A: process.env.GH_AW_EXPR_1A3A194A, - GH_AW_EXPR_463A214A: process.env.GH_AW_EXPR_463A214A, - GH_AW_EXPR_802A9F6A: process.env.GH_AW_EXPR_802A9F6A, - GH_AW_EXPR_FF1D34CE: process.env.GH_AW_EXPR_FF1D34CE, - GH_AW_GITHUB_ACTOR: process.env.GH_AW_GITHUB_ACTOR, - GH_AW_GITHUB_REPOSITORY: process.env.GH_AW_GITHUB_REPOSITORY, - GH_AW_GITHUB_RUN_ID: process.env.GH_AW_GITHUB_RUN_ID, - GH_AW_GITHUB_WORKSPACE: process.env.GH_AW_GITHUB_WORKSPACE, - GH_AW_IS_PR_COMMENT: process.env.GH_AW_IS_PR_COMMENT, - GH_AW_MCP_CLI_SERVERS_LIST: process.env.GH_AW_MCP_CLI_SERVERS_LIST, - GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ACTIVATED: process.env.GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_ACTIVATED, - GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_MATCHED_COMMAND: process.env.GH_AW_NEEDS_PRE_ACTIVATION_OUTPUTS_MATCHED_COMMAND - } - }); - - name: Validate prompt placeholders - env: - GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt - # poutine:ignore untrusted_checkout_exec - run: bash "${RUNNER_TEMP}/gh-aw/actions/validate_prompt_placeholders.sh" - - name: Print prompt - env: - GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt - # poutine:ignore untrusted_checkout_exec - run: bash "${RUNNER_TEMP}/gh-aw/actions/print_prompt_summary.sh" - - name: Upload activation artifact - if: success() - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 - with: - name: activation - include-hidden-files: true - path: | - /tmp/gh-aw/aw_info.json - /tmp/gh-aw/models.json - /tmp/gh-aw/aw-prompts/prompt.txt - /tmp/gh-aw/aw-prompts/prompt-template.txt - /tmp/gh-aw/aw-prompts/prompt-import-tree.json - /tmp/gh-aw/github_rate_limits.jsonl - /tmp/gh-aw/base - /tmp/gh-aw/.github/agents - /tmp/gh-aw/.github/skills - if-no-files-found: ignore - retention-days: 1 - - agent: - needs: - - activation - - pat_pool - runs-on: ubuntu-latest - environment: copilot-pat-pool - permissions: - contents: read - pull-requests: read - env: - DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} - GH_AW_ASSETS_ALLOWED_EXTS: "" - GH_AW_ASSETS_BRANCH: "" - GH_AW_ASSETS_MAX_SIZE_KB: 0 - GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs - GH_AW_WORKFLOW_ID_SANITIZED: androidtoolsreviewer - outputs: - agentic_engine_timeout: ${{ steps.detect-agent-errors.outputs.agentic_engine_timeout || 'false' }} - ai_credits_rate_limit_error: ${{ steps.parse-mcp-gateway.outputs.ai_credits_rate_limit_error || 'false' }} - aic: ${{ steps.parse-mcp-gateway.outputs.aic }} - ambient_context: ${{ steps.parse-mcp-gateway.outputs.ambient_context }} - checkout_pr_success: ${{ steps.checkout-pr.outputs.checkout_pr_success || 'true' }} - effective_tokens: ${{ steps.parse-mcp-gateway.outputs.effective_tokens }} - has_patch: ${{ steps.collect_output.outputs.has_patch }} - inference_access_error: ${{ steps.detect-agent-errors.outputs.inference_access_error || 'false' }} - mcp_policy_error: ${{ steps.detect-agent-errors.outputs.mcp_policy_error || 'false' }} - model: ${{ needs.activation.outputs.model }} - model_not_supported_error: ${{ steps.detect-agent-errors.outputs.model_not_supported_error || 'false' }} - output: ${{ steps.collect_output.outputs.output }} - output_types: ${{ steps.collect_output.outputs.output_types }} - setup-parent-span-id: ${{ steps.setup.outputs.parent-span-id || steps.setup.outputs.span-id }} - setup-span-id: ${{ steps.setup.outputs.span-id }} - setup-trace-id: ${{ steps.setup.outputs.trace-id }} - unknown_model_ai_credits: ${{ steps.parse-mcp-gateway.outputs.unknown_model_ai_credits || 'false' }} - steps: - - name: Setup Scripts - id: setup - uses: github/gh-aw-actions/setup@8c7d04ebf1ece56cd381446125da3e0f6896294a # v0.80.9 - with: - destination: ${{ runner.temp }}/gh-aw/actions - job-name: ${{ github.job }} - trace-id: ${{ needs.activation.outputs.setup-trace-id }} - parent-span-id: ${{ needs.activation.outputs.setup-parent-span-id || needs.activation.outputs.setup-span-id }} - env: - GH_AW_SETUP_WORKFLOW_NAME: "Android Tools PR Reviewer" - GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/android-tools-reviewer.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.63" - GH_AW_INFO_AWF_VERSION: "v0.27.7" - GH_AW_INFO_ENGINE_ID: "copilot" - - name: Set runtime paths - id: set-runtime-paths - run: | - { - echo "GH_AW_SAFE_OUTPUTS=${RUNNER_TEMP}/gh-aw/safeoutputs/outputs.jsonl" - echo "GH_AW_SAFE_OUTPUTS_CONFIG_PATH=${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" - echo "GH_AW_SAFE_OUTPUTS_TOOLS_PATH=${RUNNER_TEMP}/gh-aw/safeoutputs/tools.json" - } >> "$GITHUB_OUTPUT" - - name: Checkout repository - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - with: - persist-credentials: false - - name: Create gh-aw temp directory - run: bash "${RUNNER_TEMP}/gh-aw/actions/create_gh_aw_tmp_dir.sh" - - name: Configure gh CLI for GitHub Enterprise - run: bash "${RUNNER_TEMP}/gh-aw/actions/configure_gh_for_ghe.sh" - env: - GH_TOKEN: ${{ github.token }} - - name: Configure Git credentials - env: - GITHUB_REPOSITORY: ${{ github.repository }} - GITHUB_SERVER_URL: ${{ github.server_url }} - GITHUB_TOKEN: ${{ github.token }} - run: bash "${RUNNER_TEMP}/gh-aw/actions/configure_git_credentials.sh" - - name: Checkout PR branch - id: checkout-pr - if: | - github.event.pull_request || github.event.issue.pull_request || github.event_name == 'workflow_dispatch' && fromJSON(github.event.inputs.aw_context || '{}').item_type == 'pull_request' - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/checkout_pr_branch.cjs'); - await main(); - - name: Install GitHub Copilot CLI - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.63 - env: - GH_HOST: github.com - - name: Install AWF binary - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.7 - - name: Parse integrity filter lists - id: parse-guard-vars - env: - GH_AW_BLOCKED_USERS_VAR: ${{ vars.GH_AW_GITHUB_BLOCKED_USERS || '' }} - GH_AW_TRUSTED_USERS_VAR: ${{ vars.GH_AW_GITHUB_TRUSTED_USERS || '' }} - GH_AW_APPROVAL_LABELS_VAR: ${{ vars.GH_AW_GITHUB_APPROVAL_LABELS || '' }} - run: bash "${RUNNER_TEMP}/gh-aw/actions/parse_guard_list.sh" - - name: Download activation artifact - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 - with: - name: activation - path: /tmp/gh-aw - - name: Restore agent config folders from base branch - if: steps.checkout-pr.outcome == 'success' - env: - GH_AW_AGENT_FOLDERS: ".agents .antigravity .claude .codex .crush .gemini .github .opencode .pi" - GH_AW_AGENT_FILES: ".crush.json AGENTS.md ANTIGRAVITY.md CLAUDE.md GEMINI.md PI.md opencode.jsonc" - run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_base_github_folders.sh" - - name: Restore inline sub-agents from activation artifact - env: - GH_AW_SUB_AGENT_DIR: ".github/agents" - GH_AW_SUB_AGENT_EXT: ".agent.md" - run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_inline_sub_agents.sh" - - name: Restore inline skills from activation artifact - env: - GH_AW_SKILL_DIR: ".github/skills" - run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_inline_skills.sh" - - name: Download container images - run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.7@sha256:aae231e4635c8999d039c132f1602d3df850fe9b84a00aa2b5ac981179b5661c ghcr.io/github/gh-aw-firewall/api-proxy:0.27.7@sha256:009caf2e3d88fa77b64e9a03a95a228fc58db0f1701c6d324b29ba5a3c7c79b6 ghcr.io/github/gh-aw-firewall/squid:0.27.7@sha256:deb1d4e19de62d51cee0508057a596a19315c3423ada4d675cad136dc8037c96 ghcr.io/github/gh-aw-mcpg:v0.3.27@sha256:fe984bddde4ec05d756d9043edb0a32912e6b7b72f6a121b1082f29221421cc7 ghcr.io/github/gh-aw-node@sha256:529d02eb970b1161aa25c593a9c3df57fdfad5a8add328cb3b6eccef66f3183b ghcr.io/github/github-mcp-server:v1.4.0@sha256:2afb26356481d1a350e14544a6e160f7f7ec1561a1ea309b823665abf0309036 - - name: Generate Safe Outputs Config - run: | - mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" - mkdir -p /tmp/gh-aw/safeoutputs - mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs - cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_0e2b419eba97b7e4_EOF' - {"create_pull_request_review_comment":{"max":50,"side":"RIGHT"},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"report_incomplete":{},"submit_pull_request_review":{"allowed_events":["COMMENT","REQUEST_CHANGES"],"max":1}} - GH_AW_SAFE_OUTPUTS_CONFIG_0e2b419eba97b7e4_EOF - - name: Generate Safe Outputs Tools - env: - GH_AW_TOOLS_META_JSON: | - { - "description_suffixes": { - "create_pull_request_review_comment": " CONSTRAINTS: Maximum 50 review comment(s) can be created. Comments will be on the RIGHT side of the diff.", - "submit_pull_request_review": " CONSTRAINTS: Maximum 1 review(s) can be submitted." - }, - "repo_params": {}, - "dynamic_tools": [] - } - GH_AW_VALIDATION_JSON: | - { - "create_pull_request_review_comment": { - "defaultMax": 1, - "fields": { - "body": { - "required": true, - "type": "string", - "sanitize": true, - "maxLength": 65000 - }, - "line": { - "required": true, - "positiveInteger": true - }, - "path": { - "required": true, - "type": "string" - }, - "pull_request_number": { - "optionalPositiveInteger": true - }, - "repo": { - "type": "string", - "maxLength": 256 - }, - "side": { - "type": "string", - "enum": [ - "LEFT", - "RIGHT" - ] - }, - "start_line": { - "optionalPositiveInteger": true - } - }, - "customValidation": "startLineLessOrEqualLine" - }, - "missing_data": { - "defaultMax": 20, - "fields": { - "alternatives": { - "type": "string", - "sanitize": true, - "maxLength": 256 - }, - "context": { - "type": "string", - "sanitize": true, - "maxLength": 256 - }, - "data_type": { - "type": "string", - "sanitize": true, - "maxLength": 128 - }, - "reason": { - "type": "string", - "sanitize": true, - "maxLength": 256 - } - } - }, - "missing_tool": { - "defaultMax": 20, - "fields": { - "alternatives": { - "type": "string", - "sanitize": true, - "maxLength": 512 - }, - "reason": { - "required": true, - "type": "string", - "sanitize": true, - "maxLength": 256 - }, - "tool": { - "type": "string", - "sanitize": true, - "maxLength": 128 - } - } - }, - "noop": { - "defaultMax": 1, - "fields": { - "message": { - "required": true, - "type": "string", - "sanitize": true, - "maxLength": 65000 - } - } - }, - "report_incomplete": { - "defaultMax": 5, - "fields": { - "details": { - "type": "string", - "sanitize": true, - "maxLength": 65000 - }, - "reason": { - "required": true, - "type": "string", - "sanitize": true, - "maxLength": 1024 - } - } - }, - "submit_pull_request_review": { - "defaultMax": 1, - "fields": { - "body": { - "type": "string", - "sanitize": true, - "maxLength": 65000 - }, - "event": { - "type": "string", - "enum": [ - "APPROVE", - "REQUEST_CHANGES", - "COMMENT" - ] - }, - "pull_request_number": { - "issueOrPRNumber": true - }, - "repo": { - "type": "string", - "maxLength": 256 - } - } - } - } - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - with: - script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/generate_safe_outputs_tools.cjs'); - await main(); - - name: Start MCP Gateway - id: start-mcp-gateway - env: - GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} - GH_AW_SAFE_OUTPUTS_CONFIG_PATH: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS_CONFIG_PATH }} - GH_AW_SAFE_OUTPUTS_TOOLS_PATH: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS_TOOLS_PATH }} - GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GITHUB_TOKEN }} - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - set -eo pipefail - mkdir -p "${RUNNER_TEMP}/gh-aw/mcp-config" - - # Export gateway environment variables for MCP config and gateway script - export MCP_GATEWAY_PORT="8080" - export MCP_GATEWAY_DOMAIN="host.docker.internal" - export MCP_GATEWAY_HOST_DOMAIN="localhost" - MCP_GATEWAY_API_KEY=$(openssl rand -base64 45 | tr -d '/+=') - echo "::add-mask::${MCP_GATEWAY_API_KEY}" - export MCP_GATEWAY_API_KEY - export MCP_GATEWAY_PAYLOAD_DIR="/tmp/gh-aw/mcp-payloads" - mkdir -p "${MCP_GATEWAY_PAYLOAD_DIR}" - export MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD="524288" - export DEBUG="*" - - export GH_AW_ENGINE="copilot" - MCP_GATEWAY_UID=$(id -u 2>/dev/null || echo '0') - MCP_GATEWAY_GID=$(id -g 2>/dev/null || echo '0') - case "${DOCKER_HOST:-}" in - unix://* ) DOCKER_SOCK_PATH="${DOCKER_HOST#unix://}" ;; - /* ) DOCKER_SOCK_PATH="$DOCKER_HOST" ;; - * ) DOCKER_SOCK_PATH=/var/run/docker.sock ;; - esac - DOCKER_SOCK_GID=$(stat -c '%g' "$DOCKER_SOCK_PATH" 2>/dev/null || echo '0') - export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host --name awmg-mcpg --add-host host.docker.internal:127.0.0.1 --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v '"${DOCKER_SOCK_PATH}"':/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DOCKER_HOST=unix:///var/run/docker.sock -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e RUNNER_TEMP -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw -v '"${RUNNER_TEMP}"'/gh-aw/safeoutputs:'"${RUNNER_TEMP}"'/gh-aw/safeoutputs:rw ghcr.io/github/gh-aw-mcpg:v0.3.27' - - mkdir -p "$HOME/.copilot" - GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node) - cat << GH_AW_MCP_CONFIG_4efb80bd6f5d5d9b_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" - { - "mcpServers": { - "github": { - "type": "stdio", - "container": "ghcr.io/github/github-mcp-server:v1.4.0", - "env": { - "GITHUB_HOST": "\${GITHUB_SERVER_URL}", - "GITHUB_PERSONAL_ACCESS_TOKEN": "\${GITHUB_MCP_SERVER_TOKEN}", - "GITHUB_READ_ONLY": "1", - "GITHUB_TOOLSETS": "pull_requests,repos" - }, - "guard-policies": { - "allow-only": { - "approval-labels": ${{ steps.parse-guard-vars.outputs.approval_labels }}, - "blocked-users": ${{ steps.parse-guard-vars.outputs.blocked_users }}, - "min-integrity": "none", - "repos": "all", - "trusted-users": ${{ steps.parse-guard-vars.outputs.trusted_users }} - } - } - }, - "safeoutputs": { - "type": "stdio", - "container": "ghcr.io/github/gh-aw-node", - "mounts": ["\${GITHUB_WORKSPACE}:\${GITHUB_WORKSPACE}:rw", "${RUNNER_TEMP}/gh-aw/safeoutputs:${RUNNER_TEMP}/gh-aw/safeoutputs:rw", "/tmp/gh-aw:/tmp/gh-aw:rw"], - "args": ["-w", "\${GITHUB_WORKSPACE}"], - "entrypoint": "sh", - "entrypointArgs": ["-c", "sh ${RUNNER_TEMP}/gh-aw/safeoutputs/start_safe_outputs_mcp.sh"], - "env": { - "DEBUG": "*", - "DEFAULT_BRANCH": "\${DEFAULT_BRANCH}", - "GH_AW_ASSETS_ALLOWED_EXTS": "\${GH_AW_ASSETS_ALLOWED_EXTS}", - "GH_AW_ASSETS_BRANCH": "\${GH_AW_ASSETS_BRANCH}", - "GH_AW_ASSETS_MAX_SIZE_KB": "\${GH_AW_ASSETS_MAX_SIZE_KB}", - "GH_AW_MCP_LOG_DIR": "\${GH_AW_MCP_LOG_DIR}", - "GH_AW_SAFE_OUTPUTS": "\${GH_AW_SAFE_OUTPUTS}", - "GH_AW_SAFE_OUTPUTS_CONFIG_PATH": "\${GH_AW_SAFE_OUTPUTS_CONFIG_PATH}", - "GH_AW_SAFE_OUTPUTS_TOOLS_PATH": "\${GH_AW_SAFE_OUTPUTS_TOOLS_PATH}", - "GITHUB_REPOSITORY": "\${GITHUB_REPOSITORY}", - "GITHUB_TOKEN": "\${GITHUB_TOKEN}", - "GITHUB_WORKSPACE": "\${GITHUB_WORKSPACE}", - "RUNNER_TEMP": "\${RUNNER_TEMP}" - }, - "guard-policies": { - "write-sink": { - "accept": [ - "*" - ] - } - } - } - }, - "gateway": { - "port": $MCP_GATEWAY_PORT, - "domain": "${MCP_GATEWAY_DOMAIN}", - "apiKey": "${MCP_GATEWAY_API_KEY}", - "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" - } - } - GH_AW_MCP_CONFIG_4efb80bd6f5d5d9b_EOF - - name: Mount MCP servers as CLIs - id: mount-mcp-clis - continue-on-error: true - env: - MCP_GATEWAY_API_KEY: ${{ steps.start-mcp-gateway.outputs.gateway-api-key }} - MCP_GATEWAY_DOMAIN: ${{ steps.start-mcp-gateway.outputs.gateway-domain }} - MCP_GATEWAY_PORT: ${{ steps.start-mcp-gateway.outputs.gateway-port }} - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - with: - script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io); - const { main } = require('${{ runner.temp }}/gh-aw/actions/mount_mcp_as_cli.cjs'); - await main(); - - name: Clean credentials - continue-on-error: true - run: bash "${RUNNER_TEMP}/gh-aw/actions/clean_git_credentials.sh" - - name: Audit pre-agent workspace - id: pre_agent_audit - continue-on-error: true - run: bash "${RUNNER_TEMP}/gh-aw/actions/audit_pre_agent_workspace.sh" - - name: Execute GitHub Copilot CLI - id: agentic_execution - # Copilot CLI tool arguments (sorted): - timeout-minutes: 20 - run: | - set -o pipefail - printf '%s' "$(date +%s%3N)" > /tmp/gh-aw/agent_cli_start_ms.txt - trap 'rm -f "$HOME/.copilot/settings.json"' EXIT - mkdir -p "$HOME/.copilot" - printf '%s' '{"builtInAgents":{"rubberDuck":false}}' > "$HOME/.copilot/settings.json" - export XDG_CONFIG_HOME="$HOME" - export GH_AW_MCP_CONFIG="$HOME/.copilot/mcp-config.json" - touch /tmp/gh-aw/agent-step-summary.md - GH_AW_NODE_BIN=$(command -v node 2>/dev/null || true) - export GH_AW_NODE_BIN - export COPILOT_API_KEY="$COPILOT_DUMMY_BYOK" - (umask 177 && touch /tmp/gh-aw/agent-stdio.log) - printf '%s\n' '{"$schema":"https://github.com/github/gh-aw-firewall/releases/download/v0.27.7/awf-config.schema.json","network":{"allowDomains":["*.githubusercontent.com","*.vsblob.vsassets.io","aka.ms","api.business.githubcopilot.com","api.enterprise.githubcopilot.com","api.github.com","api.githubcopilot.com","api.individual.githubcopilot.com","api.nuget.org","api.snapcraft.io","archive.ubuntu.com","azure.archive.ubuntu.com","azuresearch-usnc.nuget.org","azuresearch-ussc.nuget.org","builds.dotnet.microsoft.com","ci.dot.net","codeload.github.com","crl.geotrust.com","crl.globalsign.com","crl.identrust.com","crl.sectigo.com","crl.thawte.com","crl.usertrust.com","crl.verisign.com","crl3.digicert.com","crl4.digicert.com","crls.ssl.com","dc.services.visualstudio.com","dist.nuget.org","docs.github.com","dot.net","dotnet.microsoft.com","dotnetcli.blob.core.windows.net","github-cloud.githubusercontent.com","github-cloud.s3.amazonaws.com","github.blog","github.com","github.githubassets.com","host.docker.internal","json-schema.org","json.schemastore.org","keyserver.ubuntu.com","lfs.github.com","microsoft.com","nuget.org","nuget.pkg.github.com","nugetregistryv2prod.blob.core.windows.net","objects.githubusercontent.com","ocsp.digicert.com","ocsp.geotrust.com","ocsp.globalsign.com","ocsp.identrust.com","ocsp.sectigo.com","ocsp.ssl.com","ocsp.thawte.com","ocsp.usertrust.com","ocsp.verisign.com","oneocsp.microsoft.com","packagecloud.io","packages.cloud.google.com","packages.microsoft.com","patch-diff.githubusercontent.com","pkgs.dev.azure.com","ppa.launchpad.net","raw.githubusercontent.com","registry.npmjs.org","s.symcb.com","s.symcd.com","security.ubuntu.com","telemetry.enterprise.githubcopilot.com","ts-crl.ws.symantec.com","ts-ocsp.ws.symantec.com","www.googleapis.com","www.microsoft.com"]},"apiProxy":{"enabled":true,"maxRuns":500,"maxCacheMisses":5,"models":{"agent":["sonnet-6x","gpt-5.5","gpt-5.4","gpt-5.3","gemini-pro","any"],"antigravity":["copilot/antigravity*","google/antigravity*","gemini/antigravity*"],"any":["copilot/*","anthropic/*","openai/*","google/*","gemini/*"],"claude":["agent"],"codex":["agent"],"coding":["copilot/gpt-5*codex*","openai/gpt-5*codex*","gpt-5-codex"],"computer-use":["copilot/*computer-use*","google/*computer-use*","gemini/*computer-use*","openai/*computer-use*"],"copilot":["agent"],"deep-research":["copilot/deep-research*","copilot/o3-deep-research*","copilot/o4-mini-deep-research*","google/deep-research*","gemini/deep-research*","openai/o3-deep-research*","openai/o4-mini-deep-research*"],"gemini":["agent"],"gemini-3-flash":["copilot/gemini-3*flash*","google/gemini-3*flash*","gemini/gemini-3*flash*"],"gemini-3-pro":["copilot/gemini-3*pro*","google/gemini-3*pro*","google/nano-banana*","gemini/gemini-3*pro*"],"gemini-3.1-flash":["copilot/gemini-3.1*flash*","google/gemini-3.1*flash*","gemini/gemini-3.1*flash*"],"gemini-3.1-pro":["copilot/gemini-3.1*pro*","google/gemini-3.1*pro*","gemini/gemini-3.1*pro*"],"gemini-3.5-flash":["copilot/gemini-3.5*flash*","google/gemini-3.5*flash*","gemini/gemini-3.5*flash*"],"gemini-flash":["copilot/gemini-*flash*","google/gemini-*flash*","gemini/gemini-*flash*"],"gemini-flash-lite":["copilot/gemini-*flash*lite*","google/gemini-*flash*lite*","gemini/gemini-*flash*lite*"],"gemini-pro":["copilot/gemini-*pro*","google/gemini-*pro*","gemini/gemini-*pro*"],"gemma":["copilot/gemma*","google/gemma*","gemini/gemma*"],"gpt-5":["copilot/gpt-5*","openai/gpt-5*"],"gpt-5-codex":["copilot/gpt-5*codex*","openai/gpt-5*codex*"],"gpt-5-mini":["copilot/gpt-5*mini*","openai/gpt-5*mini*"],"gpt-5-nano":["copilot/gpt-5*nano*","openai/gpt-5*nano*"],"gpt-5-pro":["copilot/gpt-5*pro*","openai/gpt-5*pro*"],"gpt-5.1":["copilot/gpt-5.1*","openai/gpt-5.1*"],"gpt-5.2":["copilot/gpt-5.2*","openai/gpt-5.2*"],"gpt-5.3":["copilot/gpt-5.3*","openai/gpt-5.3*"],"gpt-5.4":["copilot/gpt-5.4*","openai/gpt-5.4*"],"gpt-5.5":["copilot/gpt-5.5*","openai/gpt-5.5*"],"haiku":["copilot/*haiku*","anthropic/*haiku*"],"image-generation":["copilot/gpt-image*","openai/gpt-image*","openai/chatgpt-image*","copilot/gemini-*image*","google/gemini-*image*","gemini/gemini-*image*","google/imagen*"],"large":["sonnet","gpt-5-pro","gpt-5","gemini-pro"],"mai-code":["copilot/MAI-Code*","copilot/mai-code*","openai/MAI-Code*"],"mini":["haiku","gpt-5-mini","gpt-5-nano","gemini-flash-lite"],"nano-banana":["copilot/nano-banana*","google/nano-banana*","gemini/nano-banana*"],"opus":["copilot/*opus*","anthropic/*opus*"],"opusplan":["opus?effort=high"],"reasoning":["copilot/o1*","copilot/o3*","copilot/o4*","openai/o1*","openai/o3*","openai/o4*"],"robotics":["copilot/*robotics*","google/*robotics*","gemini/*robotics*"],"small":["mini"],"small-agent":["haiku","gpt-5-mini","gemini-flash"],"sonnet":["copilot/*sonnet*","anthropic/*sonnet*"],"sonnet-6x":["copilot/*sonnet-4.5*","copilot/*sonnet-4.6*","copilot/*sonnet-4-5-*","anthropic/*sonnet-4-5-*","copilot/*sonnet-4-6*","anthropic/*sonnet-4-6*"],"summarization":["haiku","gpt-5-mini","gemini-flash-lite","mini"],"vision":["copilot/gemini-*image*","google/gemini-*image*","gemini/gemini-*image*","copilot/gemini-*flash*","google/gemini-*flash*","gemini/gemini-*flash*"]}},"container":{"imageTag":"0.27.7,squid=sha256:deb1d4e19de62d51cee0508057a596a19315c3423ada4d675cad136dc8037c96,agent=sha256:aae231e4635c8999d039c132f1602d3df850fe9b84a00aa2b5ac981179b5661c,api-proxy=sha256:009caf2e3d88fa77b64e9a03a95a228fc58db0f1701c6d324b29ba5a3c7c79b6,cli-proxy=sha256:4757f198a3fa20f88bdbe70be7ae1a05f127d9c0a9e96a5d6460ef40c08fc83d"}}' > "${RUNNER_TEMP}/gh-aw/awf-config.json" - cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json - export GH_AW_MODELS_JSON_PATH="/tmp/gh-aw/models.json" - GH_AW_DOCKER_HOST="" - if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then - GH_AW_DOCKER_HOST="${DOCKER_HOST}" - fi - GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="" - if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then - GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="--docker-host-path-prefix /tmp/gh-aw" - python3 - <<'PY' - import json,os,subprocess as sp - from pathlib import Path - try: - p=Path(os.environ["RUNNER_TEMP"])/"gh-aw"/"awf-config.json" - c=json.loads(p.read_text()) - c["chroot"]={"binariesSourcePath":"/tmp/gh-aw","identity":{"user":sp.check_output(["id","-un"],text=True).strip(),"uid":int(sp.check_output(["id","-u"],text=True)),"gid":int(sp.check_output(["id","-g"],text=True)),"home":"/tmp/gh-aw/home"}} - out=json.dumps(c,separators=(",",":"),ensure_ascii=False)+"\n" - p.write_text(out) - Path("/tmp/gh-aw/awf-config.json").write_text(out) - except Exception as e: - raise SystemExit(f"chroot config patch failed: {e}") from e - PY - fi - GH_AW_TOOL_CACHE_MOUNT="" - GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}" - if [ -d "$GH_AW_TOOL_CACHE" ]; then - if [[ "$GH_AW_TOOL_CACHE" != /opt/* ]]; then - GH_AW_TOOL_CACHE_MOUNT="$GH_AW_TOOL_CACHE:$GH_AW_TOOL_CACHE:ro" - fi - fi - # shellcheck disable=SC1003,SC2086 - sudo -E awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST:+--docker-host "$GH_AW_DOCKER_HOST"} ${GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS} --env-all --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --skip-pull \ - -- /bin/bash -c 'set +o histexpand; export PATH="${RUNNER_TEMP}/gh-aw/mcp-cli/bin:$PATH" && : "${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"; GH_AW_TOOL_CACHE="$RUNNER_TOOL_CACHE"; export PATH="$(find "$GH_AW_TOOL_CACHE" -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; GH_AW_NPM_GLOBAL_ROOT="$(npm root -g 2>/dev/null || true)"; if [ -n "$GH_AW_NPM_GLOBAL_ROOT" ]; then export NODE_PATH="${GH_AW_NPM_GLOBAL_ROOT}${NODE_PATH:+:${NODE_PATH}}"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log - env: - AWF_REFLECT_ENABLED: 1 - COPILOT_AGENT_RUNNER_TYPE: STANDALONE - COPILOT_DUMMY_BYOK: dummy-byok-key-for-offline-mode - COPILOT_GITHUB_TOKEN: | - ${{ case( - needs.pat_pool.outputs.pat_number == '0', secrets.COPILOT_PAT_0, - needs.pat_pool.outputs.pat_number == '1', secrets.COPILOT_PAT_1, - needs.pat_pool.outputs.pat_number == '2', secrets.COPILOT_PAT_2, - needs.pat_pool.outputs.pat_number == '3', secrets.COPILOT_PAT_3, - needs.pat_pool.outputs.pat_number == '4', secrets.COPILOT_PAT_4, - needs.pat_pool.outputs.pat_number == '5', secrets.COPILOT_PAT_5, - needs.pat_pool.outputs.pat_number == '6', secrets.COPILOT_PAT_6, - needs.pat_pool.outputs.pat_number == '7', secrets.COPILOT_PAT_7, - needs.pat_pool.outputs.pat_number == '8', secrets.COPILOT_PAT_8, - needs.pat_pool.outputs.pat_number == '9', secrets.COPILOT_PAT_9, - 'NO COPILOT PAT AVAILABLE') - }} - COPILOT_MODEL: claude-opus-4.8 - GH_AW_MAX_TURNS: ${{ vars.GH_AW_DEFAULT_MAX_TURNS || '' }} - GH_AW_PHASE: agent - GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt - GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} - GH_AW_TIMEOUT_MINUTES: 20 - GH_AW_VERSION: v0.80.9 - GITHUB_API_URL: ${{ github.api_url }} - GITHUB_AW: true - GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows - GITHUB_HEAD_REF: ${{ github.head_ref }} - GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GITHUB_TOKEN }} - GITHUB_REF_NAME: ${{ github.ref_name }} - GITHUB_SERVER_URL: ${{ github.server_url }} - GITHUB_STEP_SUMMARY: /tmp/gh-aw/agent-step-summary.md - GITHUB_WORKSPACE: ${{ github.workspace }} - GIT_AUTHOR_EMAIL: github-actions[bot]@users.noreply.github.com - GIT_AUTHOR_NAME: github-actions[bot] - GIT_COMMITTER_EMAIL: github-actions[bot]@users.noreply.github.com - GIT_COMMITTER_NAME: github-actions[bot] - RUNNER_TEMP: ${{ runner.temp }} - TRACEPARENT: ${{ env.GITHUB_AW_OTEL_TRACE_ID != '' && env.GITHUB_AW_OTEL_PARENT_SPAN_ID != '' && format('00-{0}-{1}-01', env.GITHUB_AW_OTEL_TRACE_ID, env.GITHUB_AW_OTEL_PARENT_SPAN_ID) || '' }} - - name: Detect agent errors - if: always() - id: detect-agent-errors - continue-on-error: true - run: node "${RUNNER_TEMP}/gh-aw/actions/detect_agent_errors.cjs" - - name: Configure Git credentials - env: - GITHUB_REPOSITORY: ${{ github.repository }} - GITHUB_SERVER_URL: ${{ github.server_url }} - GITHUB_TOKEN: ${{ github.token }} - run: bash "${RUNNER_TEMP}/gh-aw/actions/configure_git_credentials.sh" - - name: Copy Copilot session state files to logs - if: always() - continue-on-error: true - run: bash "${RUNNER_TEMP}/gh-aw/actions/copy_copilot_session_state.sh" - - name: Stop MCP Gateway - if: always() - continue-on-error: true - env: - MCP_GATEWAY_PORT: ${{ steps.start-mcp-gateway.outputs.gateway-port }} - MCP_GATEWAY_API_KEY: ${{ steps.start-mcp-gateway.outputs.gateway-api-key }} - GATEWAY_PID: ${{ steps.start-mcp-gateway.outputs.gateway-pid }} - run: | - bash "${RUNNER_TEMP}/gh-aw/actions/stop_mcp_gateway.sh" "$GATEWAY_PID" - - name: Redact secrets in logs - if: always() - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - with: - script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/redact_secrets.cjs'); - await main(); - env: - GH_AW_SECRET_NAMES: 'COPILOT_PAT_0,COPILOT_PAT_1,COPILOT_PAT_2,COPILOT_PAT_3,COPILOT_PAT_4,COPILOT_PAT_5,COPILOT_PAT_6,COPILOT_PAT_7,COPILOT_PAT_8,COPILOT_PAT_9,GITHUB_TOKEN' - SECRET_COPILOT_PAT_0: ${{ secrets.COPILOT_PAT_0 }} - SECRET_COPILOT_PAT_1: ${{ secrets.COPILOT_PAT_1 }} - SECRET_COPILOT_PAT_2: ${{ secrets.COPILOT_PAT_2 }} - SECRET_COPILOT_PAT_3: ${{ secrets.COPILOT_PAT_3 }} - SECRET_COPILOT_PAT_4: ${{ secrets.COPILOT_PAT_4 }} - SECRET_COPILOT_PAT_5: ${{ secrets.COPILOT_PAT_5 }} - SECRET_COPILOT_PAT_6: ${{ secrets.COPILOT_PAT_6 }} - SECRET_COPILOT_PAT_7: ${{ secrets.COPILOT_PAT_7 }} - SECRET_COPILOT_PAT_8: ${{ secrets.COPILOT_PAT_8 }} - SECRET_COPILOT_PAT_9: ${{ secrets.COPILOT_PAT_9 }} - SECRET_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - name: Append agent step summary - if: always() - run: bash "${RUNNER_TEMP}/gh-aw/actions/append_agent_step_summary.sh" - - name: Copy Safe Outputs - if: always() - env: - GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} - run: | - mkdir -p /tmp/gh-aw - cp "$GH_AW_SAFE_OUTPUTS" /tmp/gh-aw/safeoutputs.jsonl 2>/dev/null || true - - name: Ingest agent output - id: collect_output - if: always() - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - env: - GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} - GH_AW_ALLOWED_DOMAINS: "*.githubusercontent.com,*.vsblob.vsassets.io,aka.ms,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.nuget.org,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,azuresearch-usnc.nuget.org,azuresearch-ussc.nuget.org,builds.dotnet.microsoft.com,ci.dot.net,codeload.github.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,dc.services.visualstudio.com,dist.nuget.org,docs.github.com,dot.net,dotnet.microsoft.com,dotnetcli.blob.core.windows.net,github-cloud.githubusercontent.com,github-cloud.s3.amazonaws.com,github.blog,github.com,github.githubassets.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,lfs.github.com,microsoft.com,nuget.org,nuget.pkg.github.com,nugetregistryv2prod.blob.core.windows.net,objects.githubusercontent.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,oneocsp.microsoft.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,patch-diff.githubusercontent.com,pkgs.dev.azure.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com,www.microsoft.com" - GITHUB_SERVER_URL: ${{ github.server_url }} - GITHUB_API_URL: ${{ github.api_url }} - GH_AW_COMMANDS: "[\"review\"]" - with: - script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/collect_ndjson_output.cjs'); - await main(); - - name: Parse agent logs for step summary - if: always() - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - env: - GH_AW_AGENT_OUTPUT: /tmp/gh-aw/sandbox/agent/logs/ - with: - script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_copilot_log.cjs'); - await main(); - - name: Parse MCP Gateway logs for step summary - if: always() - id: parse-mcp-gateway - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - with: - script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_mcp_gateway_log.cjs'); - await main(); - - name: Print firewall logs - if: always() - continue-on-error: true - env: - AWF_LOGS_DIR: /tmp/gh-aw/sandbox/firewall/logs - run: | - # Fix permissions on firewall logs/audit dirs so they can be uploaded as artifacts - # AWF runs with sudo, creating files owned by root - sudo chmod -R a+rX /tmp/gh-aw/sandbox/firewall 2>/dev/null || true - # Only run awf logs summary if awf command exists (it may not be installed if workflow failed before install step) - if command -v awf &> /dev/null; then - awf logs summary | tee -a "$GITHUB_STEP_SUMMARY" - else - echo 'AWF binary not installed, skipping firewall log summary' - fi - - name: Parse token usage for step summary - if: always() - continue-on-error: true - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - with: - script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_token_usage.cjs'); - await main(); - - name: Print AWF reflect summary - if: always() - continue-on-error: true - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - with: - script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/awf_reflect_summary.cjs'); - await main(); - - name: Write agent output placeholder if missing - if: always() - run: | - if [ ! -f /tmp/gh-aw/agent_output.json ]; then - echo '{"items":[]}' > /tmp/gh-aw/agent_output.json - fi - - name: Upload agent artifacts - if: always() - continue-on-error: true - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 - with: - name: agent - path: | - /tmp/gh-aw/aw-prompts/prompt.txt - /tmp/gh-aw/sandbox/agent/logs/ - /tmp/gh-aw/redacted-urls.log - /tmp/gh-aw/mcp-logs/ - /tmp/gh-aw/proxy-logs/ - !/tmp/gh-aw/proxy-logs/proxy-tls/ - /tmp/gh-aw/agent_usage.json - /tmp/gh-aw/agent-stdio.log - /tmp/gh-aw/pre-agent-audit.txt - /tmp/gh-aw/agent/ - /tmp/gh-aw/github_rate_limits.jsonl - /tmp/gh-aw/safeoutputs.jsonl - /tmp/gh-aw/agent_output.json - /tmp/gh-aw/aw-*.patch - /tmp/gh-aw/aw-*.bundle - /tmp/gh-aw/awf-config.json - /tmp/gh-aw/sandbox/firewall/logs/ - /tmp/gh-aw/sandbox/firewall/audit/ - /tmp/gh-aw/sandbox/firewall/awf-reflect.json - if-no-files-found: ignore - - conclusion: - needs: - - activation - - agent - - detection - - pat_pool - - safe_outputs - if: > - always() && (needs.agent.result != 'skipped' || needs.activation.outputs.lockdown_check_failed == 'true' || - needs.activation.outputs.stale_lock_file_failed == 'true') - runs-on: ubuntu-slim - environment: copilot-pat-pool - permissions: - contents: read - pull-requests: write - concurrency: - group: "gh-aw-conclusion-android-tools-reviewer" - cancel-in-progress: false - queue: max - outputs: - incomplete_count: ${{ steps.report_incomplete.outputs.incomplete_count }} - noop_message: ${{ steps.noop.outputs.noop_message }} - tools_reported: ${{ steps.missing_tool.outputs.tools_reported }} - total_count: ${{ steps.missing_tool.outputs.total_count }} - steps: - - name: Setup Scripts - id: setup - uses: github/gh-aw-actions/setup@8c7d04ebf1ece56cd381446125da3e0f6896294a # v0.80.9 - with: - destination: ${{ runner.temp }}/gh-aw/actions - job-name: ${{ github.job }} - trace-id: ${{ needs.activation.outputs.setup-trace-id }} - parent-span-id: ${{ needs.activation.outputs.setup-parent-span-id || needs.activation.outputs.setup-span-id }} - env: - GH_AW_SETUP_WORKFLOW_NAME: "Android Tools PR Reviewer" - GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/android-tools-reviewer.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.63" - GH_AW_INFO_AWF_VERSION: "v0.27.7" - GH_AW_INFO_ENGINE_ID: "copilot" - - name: Download agent output artifact - id: download-agent-output - continue-on-error: true - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 - with: - name: agent - path: /tmp/gh-aw/ - - name: Setup agent output environment variable - id: setup-agent-output-env - if: steps.download-agent-output.outcome == 'success' - run: | - mkdir -p /tmp/gh-aw/ - find "/tmp/gh-aw/" -type f -print - echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT" - - name: Collect usage artifact files - if: always() - continue-on-error: true - run: | - mkdir -p /tmp/gh-aw/usage/agent /tmp/gh-aw/usage/detection - echo "Usage artifact source file status:" - for file in /tmp/gh-aw/aw_info.json /tmp/gh-aw/aw-info.jsonl /tmp/gh-aw/agent_usage.jsonl /tmp/gh-aw/detection_usage.jsonl /tmp/gh-aw/github_rate_limits.jsonl /tmp/gh-aw/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/threat-detection/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/threat-detection/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/threat-detection/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl; do - [ -f "$file" ] && echo "FOUND: $file" || echo "MISSING: $file" - done - [ -f /tmp/gh-aw/aw_info.json ] && cp /tmp/gh-aw/aw_info.json /tmp/gh-aw/usage/aw_info.json || true - [ -f /tmp/gh-aw/aw-info.jsonl ] && cp /tmp/gh-aw/aw-info.jsonl /tmp/gh-aw/usage/aw-info.jsonl || true - [ -f /tmp/gh-aw/agent_usage.jsonl ] && cp /tmp/gh-aw/agent_usage.jsonl /tmp/gh-aw/usage/agent_usage.jsonl || true - [ -f /tmp/gh-aw/detection_usage.jsonl ] && cp /tmp/gh-aw/detection_usage.jsonl /tmp/gh-aw/usage/detection_usage.jsonl || true - [ -f /tmp/gh-aw/github_rate_limits.jsonl ] && cp /tmp/gh-aw/github_rate_limits.jsonl /tmp/gh-aw/usage/github_rate_limits.jsonl || true - [ -f /tmp/gh-aw/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl || true - [ -f /tmp/gh-aw/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl || true - [ -f /tmp/gh-aw/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/agent/token_usage.jsonl || true - [ -f /tmp/gh-aw/threat-detection/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/threat-detection/sandbox/firewall-audit-logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl || true - [ -f /tmp/gh-aw/threat-detection/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/threat-detection/sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl || true - [ -f /tmp/gh-aw/threat-detection/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl ] && cp /tmp/gh-aw/threat-detection/sandbox/firewall/audit/api-proxy-logs/token-usage.jsonl /tmp/gh-aw/usage/detection/token_usage.jsonl || true - [ -f /tmp/gh-aw/usage/agent/token_usage.jsonl ] || : > /tmp/gh-aw/usage/agent/token_usage.jsonl - [ -f /tmp/gh-aw/usage/detection/token_usage.jsonl ] || : > /tmp/gh-aw/usage/detection/token_usage.jsonl - mkdir -p /tmp/gh-aw/usage/activity - node ${{ runner.temp }}/gh-aw/actions/generate_usage_activity_summary.cjs - find /tmp/gh-aw/usage -type f -print | sort - - name: Upload usage artifact - if: always() - continue-on-error: true - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 - with: - name: usage - path: | - /tmp/gh-aw/usage/aw_info.json - /tmp/gh-aw/usage/aw-info.jsonl - /tmp/gh-aw/usage/agent_usage.jsonl - /tmp/gh-aw/usage/detection_usage.jsonl - /tmp/gh-aw/usage/github_rate_limits.jsonl - /tmp/gh-aw/usage/agent/token_usage.jsonl - /tmp/gh-aw/usage/detection/token_usage.jsonl - /tmp/gh-aw/usage/activity/summary.json - if-no-files-found: ignore - - name: Process no-op messages - id: noop - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - env: - GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} - GH_AW_NOOP_MAX: "1" - GH_AW_WORKFLOW_NAME: "Android Tools PR Reviewer" - GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/android-tools-reviewer.md" - GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} - GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }} - GH_AW_NOOP_REPORT_AS_ISSUE: "true" - GH_AW_AIC: ${{ needs.agent.outputs.aic }} - GH_AW_THREAT_DETECTION_AIC: ${{ needs.detection.outputs.aic }} - GH_AW_AMBIENT_CONTEXT: ${{ needs.agent.outputs.ambient_context }} - GH_AW_WORKFLOW_ID: "android-tools-reviewer" - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_noop_message.cjs'); - await main(); - - name: Log detection run - id: detection_runs - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - env: - GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "Android Tools PR Reviewer" - GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/android-tools-reviewer.md" - GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} - GH_AW_DETECTION_CONCLUSION: ${{ needs.detection.outputs.detection_conclusion }} - GH_AW_DETECTION_REASON: ${{ needs.detection.outputs.detection_reason }} - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_detection_runs.cjs'); - await main(); - - name: Record missing tool - id: missing_tool - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - env: - GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} - GH_AW_MISSING_TOOL_CREATE_ISSUE: "true" - GH_AW_WORKFLOW_NAME: "Android Tools PR Reviewer" - GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/android-tools-reviewer.md" - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/missing_tool.cjs'); - await main(); - - name: Record incomplete - id: report_incomplete - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - env: - GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} - GH_AW_REPORT_INCOMPLETE_CREATE_ISSUE: "true" - GH_AW_WORKFLOW_NAME: "Android Tools PR Reviewer" - GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/android-tools-reviewer.md" - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/report_incomplete_handler.cjs'); - await main(); - - name: Handle agent failure - id: handle_agent_failure - if: always() - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - env: - GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} - GH_AW_WORKFLOW_NAME: "Android Tools PR Reviewer" - GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/android-tools-reviewer.md" - GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} - GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }} - GH_AW_WORKFLOW_ID: "android-tools-reviewer" - GH_AW_ACTION_FAILURE_ISSUE_EXPIRES_HOURS: "168" - GH_AW_ENGINE_ID: "copilot" - GH_AW_CHECKOUT_PR_SUCCESS: ${{ needs.agent.outputs.checkout_pr_success }} - GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens || '' }} - GH_AW_AI_CREDITS_RATE_LIMIT_ERROR: ${{ needs.agent.outputs.ai_credits_rate_limit_error || 'false' }} - GH_AW_UNKNOWN_MODEL_AI_CREDITS: ${{ needs.agent.outputs.unknown_model_ai_credits || 'false' }} - GH_AW_AIC: ${{ needs.agent.outputs.aic }} - GH_AW_THREAT_DETECTION_AIC: ${{ needs.detection.outputs.aic }} - GH_AW_MAX_AI_CREDITS: "-1" - GH_AW_INFERENCE_ACCESS_ERROR: ${{ needs.agent.outputs.inference_access_error }} - GH_AW_MCP_POLICY_ERROR: ${{ needs.agent.outputs.mcp_policy_error }} - GH_AW_AGENTIC_ENGINE_TIMEOUT: ${{ needs.agent.outputs.agentic_engine_timeout }} - GH_AW_MODEL_NOT_SUPPORTED_ERROR: ${{ needs.agent.outputs.model_not_supported_error }} - GH_AW_ENGINE_API_HOSTS: "api.enterprise.githubcopilot.com,api.githubcopilot.com,api.business.githubcopilot.com,api.individual.githubcopilot.com" - GH_AW_LOCKDOWN_CHECK_FAILED: ${{ needs.activation.outputs.lockdown_check_failed }} - GH_AW_STALE_LOCK_FILE_FAILED: ${{ needs.activation.outputs.stale_lock_file_failed }} - GH_AW_GROUP_REPORTS: "false" - GH_AW_FAILURE_REPORT_AS_ISSUE: "true" - GH_AW_MISSING_TOOL_REPORT_AS_FAILURE: "true" - GH_AW_MISSING_DATA_REPORT_AS_FAILURE: "true" - GH_AW_TIMEOUT_MINUTES: "20" - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_agent_failure.cjs'); - await main(); - - name: Update reaction comment with completion status - id: conclusion - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - env: - GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} - GH_AW_COMMENT_ID: ${{ needs.activation.outputs.comment_id }} - GH_AW_COMMENT_REPO: ${{ needs.activation.outputs.comment_repo }} - GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} - GH_AW_WORKFLOW_NAME: "Android Tools PR Reviewer" - GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }} - GH_AW_SAFE_OUTPUTS_RESULT: ${{ needs.safe_outputs.result }} - GH_AW_DETECTION_CONCLUSION: ${{ needs.detection.outputs.detection_conclusion }} - GH_AW_DETECTION_REASON: ${{ needs.detection.outputs.detection_reason }} - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/notify_comment_error.cjs'); - await main(); - - detection: - needs: - - activation - - agent - if: > - always() && needs.agent.result != 'skipped' && (needs.agent.outputs.output_types != '' || needs.agent.outputs.has_patch == 'true') - runs-on: ubuntu-latest - environment: copilot-pat-pool - permissions: - contents: read - outputs: - aic: ${{ steps.parse_detection_token_usage.outputs.aic }} - detection_conclusion: ${{ steps.detection_conclusion.outputs.conclusion }} - detection_reason: ${{ steps.detection_conclusion.outputs.reason }} - detection_success: ${{ steps.detection_conclusion.outputs.success }} - steps: - - name: Setup Scripts - id: setup - uses: github/gh-aw-actions/setup@8c7d04ebf1ece56cd381446125da3e0f6896294a # v0.80.9 - with: - destination: ${{ runner.temp }}/gh-aw/actions - job-name: ${{ github.job }} - trace-id: ${{ needs.activation.outputs.setup-trace-id }} - parent-span-id: ${{ needs.activation.outputs.setup-parent-span-id || needs.activation.outputs.setup-span-id }} - env: - GH_AW_SETUP_WORKFLOW_NAME: "Android Tools PR Reviewer" - GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/android-tools-reviewer.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.63" - GH_AW_INFO_AWF_VERSION: "v0.27.7" - GH_AW_INFO_ENGINE_ID: "copilot" - - name: Download agent output artifact - id: download-agent-output - continue-on-error: true - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 - with: - name: agent - path: /tmp/gh-aw/ - - name: Setup agent output environment variable - id: setup-agent-output-env - if: steps.download-agent-output.outcome == 'success' - run: | - mkdir -p /tmp/gh-aw/ - find "/tmp/gh-aw/" -type f -print - echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT" - - name: Checkout repository for patch context - if: needs.agent.outputs.has_patch == 'true' - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - with: - persist-credentials: false - # --- Threat Detection --- - - name: Clean stale firewall files from agent artifact - run: | - rm -rf /tmp/gh-aw/sandbox/firewall/logs - rm -rf /tmp/gh-aw/sandbox/firewall/audit - - name: Download container images - run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.27.7@sha256:aae231e4635c8999d039c132f1602d3df850fe9b84a00aa2b5ac981179b5661c ghcr.io/github/gh-aw-firewall/api-proxy:0.27.7@sha256:009caf2e3d88fa77b64e9a03a95a228fc58db0f1701c6d324b29ba5a3c7c79b6 ghcr.io/github/gh-aw-firewall/squid:0.27.7@sha256:deb1d4e19de62d51cee0508057a596a19315c3423ada4d675cad136dc8037c96 - - name: Check if detection needed - id: detection_guard - if: always() - env: - OUTPUT_TYPES: ${{ needs.agent.outputs.output_types }} - HAS_PATCH: ${{ needs.agent.outputs.has_patch }} - run: | - if [[ -n "$OUTPUT_TYPES" || "$HAS_PATCH" == "true" ]]; then - echo "run_detection=true" >> "$GITHUB_OUTPUT" - echo "Detection will run: output_types=$OUTPUT_TYPES, has_patch=$HAS_PATCH" - else - echo "run_detection=false" >> "$GITHUB_OUTPUT" - echo "Detection skipped: no agent outputs or patches to analyze" - fi - - name: Clear MCP Config for detection - if: always() && steps.detection_guard.outputs.run_detection == 'true' - run: | - rm -f "${RUNNER_TEMP}/gh-aw/mcp-config/mcp-servers.json" - rm -f "$HOME/.copilot/mcp-config.json" - rm -f "$GITHUB_WORKSPACE/.gemini/settings.json" - - name: Prepare threat detection files - if: always() && steps.detection_guard.outputs.run_detection == 'true' - run: | - mkdir -p /tmp/gh-aw/threat-detection/aw-prompts - rm -f /tmp/gh-aw/agent_usage.json - cp /tmp/gh-aw/aw-prompts/prompt.txt /tmp/gh-aw/threat-detection/aw-prompts/prompt.txt 2>/dev/null || true - if [ ! -s /tmp/gh-aw/threat-detection/aw-prompts/prompt.txt ]; then - echo "::warning::ERR_VALIDATION: Missing or empty detection context prompt at /tmp/gh-aw/threat-detection/aw-prompts/prompt.txt. Ensure the agent artifact includes /tmp/gh-aw/aw-prompts/prompt.txt. Detection will continue with fallback workflow context." - fi - cp /tmp/gh-aw/agent_output.json /tmp/gh-aw/threat-detection/agent_output.json 2>/dev/null || true - for f in /tmp/gh-aw/aw-*.patch; do - [ -f "$f" ] && cp "$f" /tmp/gh-aw/threat-detection/ 2>/dev/null || true - done - for f in /tmp/gh-aw/aw-*.bundle; do - [ -f "$f" ] && cp "$f" /tmp/gh-aw/threat-detection/ 2>/dev/null || true - done - echo "Prepared threat detection files:" - ls -la /tmp/gh-aw/threat-detection/ 2>/dev/null || true - - name: Setup threat detection - if: always() && steps.detection_guard.outputs.run_detection == 'true' - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - env: - WORKFLOW_NAME: "Android Tools PR Reviewer" - WORKFLOW_DESCRIPTION: "No description provided" - HAS_PATCH: ${{ needs.agent.outputs.has_patch }} - with: - script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/setup_threat_detection.cjs'); - await main(); - - name: Ensure threat-detection directory and log - if: always() && steps.detection_guard.outputs.run_detection == 'true' - run: | - mkdir -p /tmp/gh-aw/threat-detection - touch /tmp/gh-aw/threat-detection/detection.log - - name: Setup Node.js - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 - with: - node-version: '24' - package-manager-cache: false - - name: Install GitHub Copilot CLI - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.63 - env: - GH_HOST: github.com - - name: Install AWF binary - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.27.7 - - name: Execute GitHub Copilot CLI - if: always() && steps.detection_guard.outputs.run_detection == 'true' - continue-on-error: true - id: detection_agentic_execution - # Copilot CLI tool arguments (sorted): - timeout-minutes: 20 - run: | - set -o pipefail - printf '%s' "$(date +%s%3N)" > /tmp/gh-aw/agent_cli_start_ms.txt - trap 'rm -f "$HOME/.copilot/settings.json"' EXIT - mkdir -p "$HOME/.copilot" - printf '%s' '{"builtInAgents":{"rubberDuck":false}}' > "$HOME/.copilot/settings.json" - export XDG_CONFIG_HOME="$HOME" - touch /tmp/gh-aw/agent-step-summary.md - GH_AW_NODE_BIN=$(command -v node 2>/dev/null || true) - export GH_AW_NODE_BIN - export COPILOT_API_KEY="$COPILOT_DUMMY_BYOK" - (umask 177 && touch /tmp/gh-aw/threat-detection/detection.log) - GH_AW_MAX_AI_CREDITS="${GH_AW_MAX_AI_CREDITS:-400}" - printf '%s\n' "{\"\$schema\":\"https://github.com/github/gh-aw-firewall/releases/download/v0.27.7/awf-config.schema.json\",\"network\":{\"allowDomains\":[\"api.business.githubcopilot.com\",\"api.enterprise.githubcopilot.com\",\"api.github.com\",\"api.githubcopilot.com\",\"api.individual.githubcopilot.com\",\"github.com\",\"host.docker.internal\",\"registry.npmjs.org\",\"telemetry.enterprise.githubcopilot.com\"]},\"apiProxy\":{\"enabled\":true,\"enableTokenSteering\":true,\"maxRuns\":500,\"maxAiCredits\":${GH_AW_MAX_AI_CREDITS},\"maxCacheMisses\":5},\"container\":{\"imageTag\":\"0.27.7,squid=sha256:deb1d4e19de62d51cee0508057a596a19315c3423ada4d675cad136dc8037c96,agent=sha256:aae231e4635c8999d039c132f1602d3df850fe9b84a00aa2b5ac981179b5661c,api-proxy=sha256:009caf2e3d88fa77b64e9a03a95a228fc58db0f1701c6d324b29ba5a3c7c79b6,cli-proxy=sha256:4757f198a3fa20f88bdbe70be7ae1a05f127d9c0a9e96a5d6460ef40c08fc83d\"}}" > "${RUNNER_TEMP}/gh-aw/awf-config.json" - cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json - export GH_AW_MODELS_JSON_PATH="/tmp/gh-aw/models.json" - GH_AW_DOCKER_HOST="" - if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then - GH_AW_DOCKER_HOST="${DOCKER_HOST}" - fi - GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="" - if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then - GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="--docker-host-path-prefix /tmp/gh-aw" - _GH_AW_CHROOT_JSON=$(jq -c --arg src /tmp/gh-aw --arg user "$(id -un)" --argjson uid "$(id -u)" --argjson gid "$(id -g)" --arg home /tmp/gh-aw/home '.chroot={"binariesSourcePath":$src,"identity":{"user":$user,"uid":$uid,"gid":$gid,"home":$home}}' "${RUNNER_TEMP}/gh-aw/awf-config.json") || { echo "chroot config patch failed" >&2; exit 1; } - printf '%s\n' "$_GH_AW_CHROOT_JSON" > "${RUNNER_TEMP}/gh-aw/awf-config.json" - printf '%s\n' "$_GH_AW_CHROOT_JSON" > "/tmp/gh-aw/awf-config.json" - fi - GH_AW_TOOL_CACHE_MOUNT="" - GH_AW_TOOL_CACHE="${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}" - if [ -d "$GH_AW_TOOL_CACHE" ]; then - if [[ "$GH_AW_TOOL_CACHE" != /opt/* ]]; then - GH_AW_TOOL_CACHE_MOUNT="$GH_AW_TOOL_CACHE:$GH_AW_TOOL_CACHE:ro" - fi - fi - # shellcheck disable=SC1003,SC2086 - sudo -E awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" ${GH_AW_TOOL_CACHE_MOUNT:+--mount "$GH_AW_TOOL_CACHE_MOUNT"} ${GH_AW_DOCKER_HOST:+--docker-host "$GH_AW_DOCKER_HOST"} ${GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS} --env-all --exclude-env COPILOT_GITHUB_TOKEN --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --skip-pull \ - -- /bin/bash -c 'set +o histexpand; : "${RUNNER_TOOL_CACHE:?RUNNER_TOOL_CACHE must be set}"; GH_AW_TOOL_CACHE="$RUNNER_TOOL_CACHE"; export PATH="$(find "$GH_AW_TOOL_CACHE" -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; GH_AW_NPM_GLOBAL_ROOT="$(npm root -g 2>/dev/null || true)"; if [ -n "$GH_AW_NPM_GLOBAL_ROOT" ]; then export NODE_PATH="${GH_AW_NPM_GLOBAL_ROOT}${NODE_PATH:+:${NODE_PATH}}"; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log - env: - AWF_REFLECT_ENABLED: 1 - COPILOT_AGENT_RUNNER_TYPE: STANDALONE - COPILOT_DUMMY_BYOK: dummy-byok-key-for-offline-mode - COPILOT_GITHUB_TOKEN: | - ${{ case( - needs.pat_pool.outputs.pat_number == '0', secrets.COPILOT_PAT_0, - needs.pat_pool.outputs.pat_number == '1', secrets.COPILOT_PAT_1, - needs.pat_pool.outputs.pat_number == '2', secrets.COPILOT_PAT_2, - needs.pat_pool.outputs.pat_number == '3', secrets.COPILOT_PAT_3, - needs.pat_pool.outputs.pat_number == '4', secrets.COPILOT_PAT_4, - needs.pat_pool.outputs.pat_number == '5', secrets.COPILOT_PAT_5, - needs.pat_pool.outputs.pat_number == '6', secrets.COPILOT_PAT_6, - needs.pat_pool.outputs.pat_number == '7', secrets.COPILOT_PAT_7, - needs.pat_pool.outputs.pat_number == '8', secrets.COPILOT_PAT_8, - needs.pat_pool.outputs.pat_number == '9', secrets.COPILOT_PAT_9, - 'NO COPILOT PAT AVAILABLE') - }} - COPILOT_MODEL: claude-opus-4.8 - GH_AW_MAX_AI_CREDITS: ${{ vars.GH_AW_DEFAULT_DETECTION_MAX_AI_CREDITS || '400' }} - GH_AW_MAX_TURNS: ${{ vars.GH_AW_DEFAULT_MAX_TURNS || '' }} - GH_AW_PHASE: detection - GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt - GH_AW_TIMEOUT_MINUTES: 20 - GH_AW_VERSION: v0.80.9 - GITHUB_API_URL: ${{ github.api_url }} - GITHUB_AW: true - GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows - GITHUB_HEAD_REF: ${{ github.head_ref }} - GITHUB_REF_NAME: ${{ github.ref_name }} - GITHUB_SERVER_URL: ${{ github.server_url }} - GITHUB_STEP_SUMMARY: /tmp/gh-aw/agent-step-summary.md - GITHUB_WORKSPACE: ${{ github.workspace }} - GIT_AUTHOR_EMAIL: github-actions[bot]@users.noreply.github.com - GIT_AUTHOR_NAME: github-actions[bot] - GIT_COMMITTER_EMAIL: github-actions[bot]@users.noreply.github.com - GIT_COMMITTER_NAME: github-actions[bot] - RUNNER_TEMP: ${{ runner.temp }} - TRACEPARENT: ${{ env.GITHUB_AW_OTEL_TRACE_ID != '' && env.GITHUB_AW_OTEL_PARENT_SPAN_ID != '' && format('00-{0}-{1}-01', env.GITHUB_AW_OTEL_TRACE_ID, env.GITHUB_AW_OTEL_PARENT_SPAN_ID) || '' }} - - name: Parse threat detection token usage for step summary - id: parse_detection_token_usage - if: always() - continue-on-error: true - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - env: - GH_AW_TOKEN_USAGE_SUMMARY_TITLE: Threat Detection Token Usage - with: - script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_token_usage.cjs'); - await main(); - - name: Upload threat detection log - if: always() && steps.detection_guard.outputs.run_detection == 'true' - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 - with: - name: detection - path: /tmp/gh-aw/threat-detection/detection.log - if-no-files-found: ignore - - name: Parse and conclude threat detection - id: detection_conclusion - if: always() - continue-on-error: true - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - env: - RUN_DETECTION: ${{ steps.detection_guard.outputs.run_detection }} - DETECTION_AGENTIC_EXECUTION_OUTCOME: ${{ steps.detection_agentic_execution.outcome }} - GH_AW_DETECTION_CONTINUE_ON_ERROR: "true" - with: - script: | - try { - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_threat_detection_results.cjs'); - await main(); - } catch (loadErr) { - const continueOnError = process.env.GH_AW_DETECTION_CONTINUE_ON_ERROR !== 'false'; - const detectionExecutionFailed = process.env.DETECTION_AGENTIC_EXECUTION_OUTCOME === 'failure'; - const msg = 'ERR_SYSTEM: \u274C Unexpected error loading threat detection module: ' + (loadErr && loadErr.message ? loadErr.message : String(loadErr)); - core.error(msg); - core.setOutput('reason', 'parse_error'); - if (continueOnError && !detectionExecutionFailed) { - core.warning('\u26A0\uFE0F ' + msg); - core.setOutput('conclusion', 'warning'); - core.setOutput('success', 'false'); - } else { - core.setOutput('conclusion', 'failure'); - core.setOutput('success', 'false'); - core.setFailed(msg); - } - } - - pat_pool: - needs: pre_activation - runs-on: ubuntu-slim - environment: copilot-pat-pool - outputs: - pat_number: ${{ steps.select-pat-number.outputs.copilot_pat_number }} - steps: - - name: Configure GH_HOST for enterprise compatibility - id: ghes-host-config - shell: bash - run: | # zizmor: ignore[github-env] - GITHUB_SERVER_URL is set by GitHub Actions, not user input. - # Derive GH_HOST from GITHUB_SERVER_URL so the gh CLI targets the correct - # GitHub instance (GHES/GHEC). On github.com this is a harmless no-op. - GH_HOST="${GITHUB_SERVER_URL#https://}" - GH_HOST="${GH_HOST#http://}" - echo "GH_HOST=${GH_HOST}" >> "$GITHUB_ENV" - - name: Select Copilot token from pool - id: select-pat-number - run: | - # Collect pool entries with non-empty secrets from COPILOT_PAT_0..COPILOT_PAT_9. - PAT_NUMBERS=() - POOL_INDICATORS=(➖ ➖ ➖ ➖ ➖ ➖ ➖ ➖ ➖ ➖) - - for i in $(seq 0 9); do - var="COPILOT_PAT_${i}" - val="${!var}" - if [ -n "$val" ]; then - PAT_NUMBERS+=(${i}) - POOL_INDICATORS[${i}]="🟪" - fi - done - - # If none of the entries in the pool have values, emit a warning - # and do not set an output value. The consumer can fall back to - # using COPILOT_GITHUB_TOKEN. - if [ ${#PAT_NUMBERS[@]} -eq 0 ]; then - warning_message="::warning::None of the PAT pool entries had values " - warning_message+="(checked COPILOT_PAT_0 through COPILOT_PAT_9)" - echo "$warning_message" - exit 0 - fi - - # Select a random index using the seed if specified. - if [ -n "$RANDOM_SEED" ]; then - RANDOM=$RANDOM_SEED - fi - - PAT_INDEX=$(( RANDOM % ${#PAT_NUMBERS[@]} )) - PAT_NUMBER="${PAT_NUMBERS[$PAT_INDEX]}" - POOL_INDICATORS[${PAT_NUMBER}]="✅" - - echo "Pool size: ${#PAT_NUMBERS[@]}" - echo "Selected PAT number ${PAT_NUMBER} (index: ${PAT_INDEX})" - - echo "|0|1|2|3|4|5|6|7|8|9|" >> "$GITHUB_STEP_SUMMARY" - echo "|-|-|-|-|-|-|-|-|-|-|" >> "$GITHUB_STEP_SUMMARY" - (IFS='|'; printf '|%s' "${POOL_INDICATORS[@]}"; printf '|\n') >> "$GITHUB_STEP_SUMMARY" - - echo "copilot_pat_number=${PAT_NUMBER}" >> "$GITHUB_OUTPUT" - env: - COPILOT_PAT_0: ${{ secrets.COPILOT_PAT_0 }} - COPILOT_PAT_1: ${{ secrets.COPILOT_PAT_1 }} - COPILOT_PAT_2: ${{ secrets.COPILOT_PAT_2 }} - COPILOT_PAT_3: ${{ secrets.COPILOT_PAT_3 }} - COPILOT_PAT_4: ${{ secrets.COPILOT_PAT_4 }} - COPILOT_PAT_5: ${{ secrets.COPILOT_PAT_5 }} - COPILOT_PAT_6: ${{ secrets.COPILOT_PAT_6 }} - COPILOT_PAT_7: ${{ secrets.COPILOT_PAT_7 }} - COPILOT_PAT_8: ${{ secrets.COPILOT_PAT_8 }} - COPILOT_PAT_9: ${{ secrets.COPILOT_PAT_9 }} - RANDOM_SEED: ${{ github.aw.import-inputs.random_seed }} - shell: bash - - pre_activation: - if: "(github.event_name != 'issue_comment' && github.event_name != 'pull_request_review_comment' || contains(fromJSON('[\"OWNER\",\"MEMBER\",\"COLLABORATOR\"]'), github.event.comment.author_association)) && (github.event_name == 'issue_comment' && (startsWith(github.event.comment.body, '/review ') || startsWith(github.event.comment.body, '/review\n') || github.event.comment.body == '/review') && github.event.issue.pull_request != null || !(github.event_name == 'issue_comment'))" - runs-on: ubuntu-slim - environment: copilot-pat-pool - outputs: - activated: ${{ steps.check_membership.outputs.is_team_member == 'true' && steps.check_command_position.outputs.command_position_ok == 'true' }} - matched_command: ${{ steps.check_command_position.outputs.matched_command }} - setup-parent-span-id: ${{ steps.setup.outputs.parent-span-id || steps.setup.outputs.span-id }} - setup-span-id: ${{ steps.setup.outputs.span-id }} - setup-trace-id: ${{ steps.setup.outputs.trace-id }} - steps: - - name: Setup Scripts - id: setup - uses: github/gh-aw-actions/setup@8c7d04ebf1ece56cd381446125da3e0f6896294a # v0.80.9 - with: - destination: ${{ runner.temp }}/gh-aw/actions - job-name: ${{ github.job }} - env: - GH_AW_SETUP_WORKFLOW_NAME: "Android Tools PR Reviewer" - GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/android-tools-reviewer.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.63" - GH_AW_INFO_AWF_VERSION: "v0.27.7" - GH_AW_INFO_ENGINE_ID: "copilot" - - name: Check team membership for command workflow - id: check_membership - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - env: - GH_AW_REQUIRED_ROLES: "admin,maintainer,write" - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/check_membership.cjs'); - await main(); - - name: Check command position - id: check_command_position - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - env: - GH_AW_COMMANDS: "[\"review\"]" - with: - script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/check_command_position.cjs'); - await main(); - - safe_outputs: - needs: - - activation - - agent - - detection - if: (!cancelled()) && needs.agent.result != 'skipped' && needs.detection.result == 'success' - runs-on: ubuntu-slim - environment: copilot-pat-pool - permissions: - contents: read - pull-requests: write - timeout-minutes: 45 - env: - GH_AW_AGENT_AIC: ${{ needs.agent.outputs.aic }} - GH_AW_AIC: ${{ needs.agent.outputs.aic }} - GH_AW_AMBIENT_CONTEXT: ${{ needs.agent.outputs.ambient_context }} - GH_AW_CALLER_WORKFLOW_ID: "${{ github.repository }}/android-tools-reviewer" - GH_AW_COMMANDS: "[\"review\"]" - GH_AW_DETECTION_CONCLUSION: ${{ needs.detection.outputs.detection_conclusion }} - GH_AW_DETECTION_REASON: ${{ needs.detection.outputs.detection_reason }} - GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens }} - GH_AW_ENGINE_ID: "copilot" - GH_AW_ENGINE_MODEL: "claude-opus-4.8" - GH_AW_ENGINE_VERSION: "1.0.63" - GH_AW_THREAT_DETECTION_AIC: ${{ needs.detection.outputs.aic }} - GH_AW_WORKFLOW_ID: "android-tools-reviewer" - GH_AW_WORKFLOW_NAME: "Android Tools PR Reviewer" - GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/${{ github.repository }}/blob/${{ github.ref_name }}/.github/workflows/android-tools-reviewer.md" - outputs: - code_push_failure_count: ${{ steps.process_safe_outputs.outputs.code_push_failure_count }} - code_push_failure_errors: ${{ steps.process_safe_outputs.outputs.code_push_failure_errors }} - create_discussion_error_count: ${{ steps.process_safe_outputs.outputs.create_discussion_error_count }} - create_discussion_errors: ${{ steps.process_safe_outputs.outputs.create_discussion_errors }} - process_safe_outputs_processed_count: ${{ steps.process_safe_outputs.outputs.processed_count }} - process_safe_outputs_temporary_id_map: ${{ steps.process_safe_outputs.outputs.temporary_id_map }} - steps: - - name: Setup Scripts - id: setup - uses: github/gh-aw-actions/setup@8c7d04ebf1ece56cd381446125da3e0f6896294a # v0.80.9 - with: - destination: ${{ runner.temp }}/gh-aw/actions - job-name: ${{ github.job }} - trace-id: ${{ needs.activation.outputs.setup-trace-id }} - parent-span-id: ${{ needs.activation.outputs.setup-parent-span-id || needs.activation.outputs.setup-span-id }} - env: - GH_AW_SETUP_WORKFLOW_NAME: "Android Tools PR Reviewer" - GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/android-tools-reviewer.lock.yml@${{ github.ref }} - GH_AW_INFO_VERSION: "1.0.63" - GH_AW_INFO_AWF_VERSION: "v0.27.7" - GH_AW_INFO_ENGINE_ID: "copilot" - - name: Download agent output artifact - id: download-agent-output - continue-on-error: true - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 - with: - name: agent - path: /tmp/gh-aw/ - - name: Setup agent output environment variable - id: setup-agent-output-env - if: steps.download-agent-output.outcome == 'success' - run: | - mkdir -p /tmp/gh-aw/ - find "/tmp/gh-aw/" -type f -print - echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT" - - name: Configure GH_HOST for enterprise compatibility - id: ghes-host-config - shell: bash - run: | # zizmor: ignore[github-env] - GITHUB_SERVER_URL is set by GitHub Actions, not user input. - # Derive GH_HOST from GITHUB_SERVER_URL so the gh CLI targets the correct - # GitHub instance (GHES/GHEC). On github.com this is a harmless no-op. - GH_HOST="${GITHUB_SERVER_URL#https://}" - GH_HOST="${GH_HOST#http://}" - echo "GH_HOST=${GH_HOST}" >> "$GITHUB_ENV" - - name: Process Safe Outputs - id: process_safe_outputs - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - env: - GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} - GH_AW_COMMENT_ID: ${{ needs.activation.outputs.comment_id }} - GH_AW_ALLOWED_DOMAINS: "*.githubusercontent.com,*.vsblob.vsassets.io,aka.ms,api.business.githubcopilot.com,api.enterprise.githubcopilot.com,api.github.com,api.githubcopilot.com,api.individual.githubcopilot.com,api.nuget.org,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,azuresearch-usnc.nuget.org,azuresearch-ussc.nuget.org,builds.dotnet.microsoft.com,ci.dot.net,codeload.github.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,dc.services.visualstudio.com,dist.nuget.org,docs.github.com,dot.net,dotnet.microsoft.com,dotnetcli.blob.core.windows.net,github-cloud.githubusercontent.com,github-cloud.s3.amazonaws.com,github.blog,github.com,github.githubassets.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,lfs.github.com,microsoft.com,nuget.org,nuget.pkg.github.com,nugetregistryv2prod.blob.core.windows.net,objects.githubusercontent.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,oneocsp.microsoft.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,patch-diff.githubusercontent.com,pkgs.dev.azure.com,ppa.launchpad.net,raw.githubusercontent.com,registry.npmjs.org,s.symcb.com,s.symcd.com,security.ubuntu.com,telemetry.enterprise.githubcopilot.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com,www.googleapis.com,www.microsoft.com" - GITHUB_SERVER_URL: ${{ github.server_url }} - GITHUB_API_URL: ${{ github.api_url }} - GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"create_pull_request_review_comment\":{\"max\":50,\"side\":\"RIGHT\"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"},\"report_incomplete\":{},\"submit_pull_request_review\":{\"allowed_events\":[\"COMMENT\",\"REQUEST_CHANGES\"],\"max\":1}}" - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - script: | - const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); - setupGlobals(core, github, context, exec, io, getOctokit); - const { main } = require('${{ runner.temp }}/gh-aw/actions/safe_output_handler_manager.cjs'); - await main(); - - name: Upload Safe Outputs Items - if: always() - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 - with: - name: safe-outputs-items - path: | - /tmp/gh-aw/safe-output-items.jsonl - /tmp/gh-aw/temporary-id-map.json - if-no-files-found: ignore - diff --git a/external/xamarin-android-tools/.github/workflows/android-tools-reviewer.md b/external/xamarin-android-tools/.github/workflows/android-tools-reviewer.md deleted file mode 100644 index 9db4f6deb3e..00000000000 --- a/external/xamarin-android-tools/.github/workflows/android-tools-reviewer.md +++ /dev/null @@ -1,92 +0,0 @@ ---- -on: - slash_command: - name: review - events: [pull_request_comment] - roles: [admin, maintainer, write] - -# ############################################################### -# Select a PAT from the pool and override COPILOT_GITHUB_TOKEN. -# Run agentic jobs in an isolated `copilot-pat-pool` environment. -# -# When org-level billing is available, this will be removed. -# See `shared/pat_pool.README.md` for more information. -# ############################################################### -imports: - - uses: shared/pat_pool.md - with: - environment: copilot-pat-pool - -environment: copilot-pat-pool -permissions: - contents: read - pull-requests: read -engine: - id: copilot - model: claude-opus-4.8 - env: - COPILOT_GITHUB_TOKEN: | - ${{ case( - needs.pat_pool.outputs.pat_number == '0', secrets.COPILOT_PAT_0, - needs.pat_pool.outputs.pat_number == '1', secrets.COPILOT_PAT_1, - needs.pat_pool.outputs.pat_number == '2', secrets.COPILOT_PAT_2, - needs.pat_pool.outputs.pat_number == '3', secrets.COPILOT_PAT_3, - needs.pat_pool.outputs.pat_number == '4', secrets.COPILOT_PAT_4, - needs.pat_pool.outputs.pat_number == '5', secrets.COPILOT_PAT_5, - needs.pat_pool.outputs.pat_number == '6', secrets.COPILOT_PAT_6, - needs.pat_pool.outputs.pat_number == '7', secrets.COPILOT_PAT_7, - needs.pat_pool.outputs.pat_number == '8', secrets.COPILOT_PAT_8, - needs.pat_pool.outputs.pat_number == '9', secrets.COPILOT_PAT_9, - 'NO COPILOT PAT AVAILABLE') - }} -max-daily-ai-credits: -1 -max-ai-credits: -1 -network: - allowed: - - defaults - - dotnet - - github - - "aka.ms" - - "microsoft.com" -tools: - github: - github-token: ${{ secrets.GITHUB_TOKEN }} - toolsets: [pull_requests, repos] - # Allow reading PR content from external/first-time contributors. - # The /review command is gated to maintainers, so only trusted users can trigger it. - min-integrity: none -safe-outputs: - github-token: ${{ secrets.GITHUB_TOKEN }} - create-pull-request-review-comment: - max: 50 - submit-pull-request-review: - max: 1 - allowed-events: [COMMENT, REQUEST_CHANGES] ---- - -# Android Tools PR Reviewer - -A maintainer commented `/review` on this pull request. Perform a thorough code review following the dotnet/android-tools review guidelines. - -## Instructions - -1. Read the review methodology from `.github/skills/android-tools-reviewer/SKILL.md` — this defines the review workflow (including which rule files to load), mindset, severity levels, and comment format. -2. Follow the skill's workflow to analyze the pull request: - - Gather context: read the diff and changed files - - For each changed file, read the **full source file** to understand surrounding context - - Form an independent assessment before reading the PR description - - Read the PR title and description — treat claims as things to verify - - Check CI status - - Analyze the diff against the review rules -3. Post your findings as inline review comments and a review summary. - -## Constraints - -- **Always post at least one inline review comment**, even for clean PRs — add a brief 💡 observation on a key implementation line. The review framework requires at least one inline comment to submit the review. -- Only comment on added/modified lines visible in the diff. -- One issue per inline comment. -- If the same issue appears many times, flag it once listing all affected files. -- Don't flag what CI catches (compiler errors, linter issues). -- Avoid false positives — verify concerns given the full file context. -- **Never submit an APPROVE event.** Use COMMENT for clean PRs and REQUEST_CHANGES when issues are found. -- Prioritize: bugs > safety > performance > missing tests > duplication > consistency > documentation. diff --git a/external/xamarin-android-tools/.github/workflows/copilot-setup-steps.yml b/external/xamarin-android-tools/.github/workflows/copilot-setup-steps.yml deleted file mode 100644 index 6e90f640273..00000000000 --- a/external/xamarin-android-tools/.github/workflows/copilot-setup-steps.yml +++ /dev/null @@ -1,25 +0,0 @@ -name: Copilot Setup Steps CI - -on: - workflow_dispatch: - pull_request: - paths: - - '.github/workflows/copilot-setup-steps.yml' - -permissions: - contents: read - pull-requests: write - -jobs: - copilot-setup-steps: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v6 - - name: Install gh-aw extension - uses: github/gh-aw-actions/setup-cli@ba90f2186d7ad780ec640f364005fa24e797b360 # v0.68.3 - with: - version: v0.68.3 - - uses: actions/setup-dotnet@v4 - with: - dotnet-version: "10.0.x" - - run: dotnet restore Xamarin.Android.Tools.sln || true diff --git a/external/xamarin-android-tools/.github/workflows/shared/pat_pool.README.md b/external/xamarin-android-tools/.github/workflows/shared/pat_pool.README.md deleted file mode 100644 index 2f10fc62a2a..00000000000 --- a/external/xamarin-android-tools/.github/workflows/shared/pat_pool.README.md +++ /dev/null @@ -1,205 +0,0 @@ -# PAT Pool - -Selects a random Copilot PAT from a numbered pool of secrets. This addresses limitations that arise from having a single PAT shared across all agentic workflows, such as rate-limiting. - -**This is a stop-gap workaround.** As soon as organization/enterprise billing is available to the dotnet org, this approach will be removed from our workflows. - -## Repository Onboarding - -To use Agentic Workflows in a dotnet org repository: - -1. Follow the instructions for [Configuring Your Repository | Agentic Authoring | GitHub Agentic Workflows][configure-repo]. Use `gh aw` **v0.80.9 or newer**, which supports the agent job dependencies required for this implementation. -2. Copy the `pat_pool.md` and `pat_pool.README.md` files into the repository under `.github/workflows/shared`. -3. Merge those additions into the repository and then follow the instructions for the PAT Creation and Usage below. - -**Install or upgrade the `gh aw` CLI and check the version** - -```sh -gh extension install github/gh-aw --force -gh aw --version -``` - -### Environment - -Create an environment for the agentic workflows: - - _Configuring these settings requires repo admin permission_ - - https://github.com/dotnet/{repo}/settings/environments - - Recommended Name: **copilot-pat-pool** - - Recommended Deployment branches and tags: **Protected branches only** - -This environment is used for all agentic workflows, restricting agentic workflows to the repo's protected branches and preventing the workflows from accessing secrets defined for other environments. - -## PAT Management - -Team members provide PATs into the pool with secret names matching the pattern of `_<0-9>`, such as `COPILOT_PAT_0`. - -[Use this link to prefill the PAT creation form with the required settings][create-pat]: - -1. **Resource owner** is your **user account**, not an organization. -2. **Copilot Requests (Read)** must be the only permission granted. -3. **8-day expiration** must be used, which enforces a weekly renewal. -4. **Repository access** set to **Public repositories** only. - -The **Token Name** _does not_ need to match the secret name and is only visible to the owner of the PAT. It's recommended to use a token name indicating the PAT is used for dotnet org agentic workflows. The **Description** is also only used for your own reference. - -Team members providing PATs for workflows should set weekly recurring reminders to regenerate and update their PATs in the PAT pool. With an 8-day expiration, renewal can be done on the same day each week. - -## PAT Pool Secrets - -For a PAT pool that is specific to an environment, PATs can be added to repositories as **Environment Secrets** for the environment created above. _This requires repo admin permission_. - -* **Settings** > - * **Environments** > - * **copilot-pat-pool** (or other environment name) > - * **Add environment secret** (or edit your existing secret) - * Enter your secret name of `COPILOT_PAT_{0-9}` and paste in your PAT - -This can also be accomplished using the `gh` CLI, specifying the repo and environment arguments. - -```sh -# Register the PAT secret. This will prompt for you to paste the PAT. -gh secret set "_<0-9>" --repo / --env "copilot-pat-pool" -``` - -It's also helpful to record who owns each PAT within the pool. To capture which team member is associated with each PAT, a `_<0-9>_` "sidecar secret" can be added alongside the PAT secret to make the username for the PAT pool entry visible. This sidecar secret must have a non-empty value, but it's never consumed, so any value is sufficient. - -```sh -# Record a sidecar secret that presents who owns this PAT. -gh secret set "_<0-9>_" --body "" --repo / --env "copilot-pat-pool" -``` - -## Workflow Output Attribution - -Team members' PATs are _only_ used for the Copilot requests from within the agentic portion of the workflow. All outputs from the workflow use the `github-actions[bot]` account token. Issues, PRs, comments, and all other content generated by the workflow will be attributed to `github-actions[bot]`--not the team member's account or token. - -## Usage - -The [`pat_pool.md`](./pat_pool.md) workflow import defines a custom job with a `pat_number` output. Consuming workflows need two additions to their frontmatter to import this job and use the PAT number to override the `COPILOT_GITHUB_TOKEN` passed to the workflow's agent job. - -```yml -# ############################################################### -# Select a PAT from the pool and override COPILOT_GITHUB_TOKEN. -# Run agentic jobs in an isolated `copilot-pat-pool` environment. -# -# When org-level billing is available, this will be removed. -# See `shared/pat_pool.README.md` for more information. -# ############################################################### -imports: - - uses: shared/pat_pool.md - with: - environment: copilot-pat-pool - -environment: copilot-pat-pool - -engine: - id: copilot - env: - COPILOT_GITHUB_TOKEN: | - ${{ case( - needs.pat_pool.outputs.pat_number == '0', secrets.COPILOT_PAT_0, - needs.pat_pool.outputs.pat_number == '1', secrets.COPILOT_PAT_1, - needs.pat_pool.outputs.pat_number == '2', secrets.COPILOT_PAT_2, - needs.pat_pool.outputs.pat_number == '3', secrets.COPILOT_PAT_3, - needs.pat_pool.outputs.pat_number == '4', secrets.COPILOT_PAT_4, - needs.pat_pool.outputs.pat_number == '5', secrets.COPILOT_PAT_5, - needs.pat_pool.outputs.pat_number == '6', secrets.COPILOT_PAT_6, - needs.pat_pool.outputs.pat_number == '7', secrets.COPILOT_PAT_7, - needs.pat_pool.outputs.pat_number == '8', secrets.COPILOT_PAT_8, - needs.pat_pool.outputs.pat_number == '9', secrets.COPILOT_PAT_9, - 'NO COPILOT PAT AVAILABLE') - }} -``` - -The `COPILOT_GITHUB_TOKEN` expression can be collapsed onto a single line if desired. `gh-aw compile` automatically wires `pat_pool` into the activation and agent jobs' `needs:` graph because of the `needs.pat_pool.` references within the `engine.env` property. - -```sh -gh aw compile --schedule-seed / -``` - -### Specifying the environment - -The `environment` must be specified both to the `pat_pool.md` import and to the containing workflow to ensure both jobs access the PAT pool from the same environment. The `copilot-pat-pool` environment name is recommended as the isolated environment for agentic workflows that use the PAT pool. - -### Customizing the pool - -The import declares 10 optional inputs (`COPILOT_PAT_0` through `COPILOT_PAT_9`), each defaulting to `secrets.COPILOT_PAT_#` of the matching number. To point a workflow at a different pool of repository secrets, use the parameterized `uses`/`with` form when importing and pass the substitute secrets as the `COPILOT_PAT_#` inputs: - -```yml -imports: - - uses: shared/pat_pool.md - with: - COPILOT_PAT_0: ${{ secrets.MY_TEAM_PAT_0 }} - COPILOT_PAT_1: ${{ secrets.MY_TEAM_PAT_1 }} - # Unspecified inputs default to `secrets.COPILOT_PAT_#` lookups -``` - -The secrets passed via `with:` must match the secrets referenced in the consuming workflow's `case` expression that overrides `COPILOT_GITHUB_TOKEN`--both sides need to agree on which secret backs each `COPILOT_PAT_#` slot. Update the `case` expression accordingly: - -```yml -engine: - id: copilot - env: - COPILOT_GITHUB_TOKEN: ${{ case(needs.pat_pool.outputs.pat_number == '0', secrets.MY_TEAM_PAT_0, needs.pat_pool.outputs.pat_number == '1', secrets.MY_TEAM_PAT_1, ..., 'NO COPILOT PAT AVAILABLE') }} -``` - -This approach aligns with GitHub's documented guidance for [passing secrets][passing-secrets] between workflows, where the `pat_pool` job returns a PAT number and the `case` statement acts as a secret store to look the PAT secret up based on the selected number. - -## Design / Security - -There are several details of this implementation that keep our workflows and repositories safe. - -1. **Secrets adhere to existing trust boundaries.** The pool of PAT secrets is provided to a dedicated step within the `pat_pool` job. That job runs after `pre_activation` and contains only the trusted checkout and action steps--no untrusted context or input is within scope. The `select-pat-number` action only references the secret values to determine which are non-empty, filtering the secret numbers to those with values. -1. **The `pat_pool` job emits only a number, never a secret.** Its sole output, `pat_number`, is the 0-9 index of the selected PAT (or empty when the pool is empty). The actual secret materializes only later, in the activation job's `engine.env` mapping, where the `case()` expression resolves the number to the matching secret. This follows GitHub's guidance for [passing secrets][passing-secrets] between jobs or workflows, with the `case` statement acting as a very simple secret store. -1. **The `select-pat-number` action does not require any permissions.** It reads only the `COPILOT_PAT_#` environment variables passed to it and writes only to `GITHUB_OUTPUT`. The job that hosts it sets `permissions:` to the workflow defaults (no elevated scopes). -1. **The implementation uses supported Agentic Workflow extensibility hooks.** Defining a custom job inside an [imported workflow file][imports] is supported by `gh aw compile`. gh-aw automatically wires `pat_pool` into the activation job's `needs:` graph based on the `needs.pat_pool.outputs.pat_number` references in `engine.env`. The [secret override][secret-override] capability supplies the `COPILOT_GITHUB_TOKEN` value via `engine.env` rather than the default secret of the same name. - -Each of the references below contributed to the design and implementation to ensure a secure and reliable design. - -## Known Issues - -The `pat_pool` import integration requires that the workflow's compilation results in a `pre_activation` job. If nothing in your workflow definition produces a `pre_activation` job, a compilation error will be received. - -```text -✗ Failed workflows: - ✗ .md - -.github\workflows\.md:1:1: error: failed to generate YAML: failed to build and validate jobs: job 'pat_pool' depends on non-existent job 'pre_activation' -``` - -To work around this, add `on.permissions: {}` to your workflow, which forces a no-op `pre_activation` job to be generated. - -```yml -on: - permissions: {} -``` - -See: [Activation 'needs' does not incorporate jobs in engine.env expressions (github/gh-aw#30790)](https://github.com/github/gh-aw/issues/30790) - -## References - -- [Agentic Workflows CLI Extension][cli-setup] -- [Agentic Authoring][configure-repo] -- [Authentication][authentication] -- [Agentic Workflow Imports][imports] -- [Custom Steps][steps] -- [Custom Jobs][jobs] -- [Job Outputs][job-outputs] -- [Engine Configuration][engine] -- [Engine Environment Variables][engine-vars] -- [Update agentic engine token handling to use user-provided secrets (github/gh-aw#18017)][secret-override] -- [Case Function in Workflow Expressions][case-expression] -- [Passing a secret between jobs or workflows][passing-secrets] - -[cli-setup]: https://github.github.com/gh-aw/setup/cli/ -[configure-repo]: https://github.github.com/gh-aw/guides/agentic-authoring/#configuring-your-repository -[authentication]: https://github.github.com/gh-aw/reference/auth/ -[create-pat]: https://github.com/settings/personal-access-tokens/new?name=dotnet%20org%20agentic%20workflows&description=GitHub+Agentic+Workflows+-+Copilot+engine+authentication.++Used+for+dotnet+org+workflows.+MUST+be+configured+with+only+Copilot+Requests+permissions+and+user+account+as+resource+owner.+Weekly+expiration+and+required+renewal.&user_copilot_requests=read&expires_in=8 -[imports]: https://github.github.com/gh-aw/reference/imports/ -[steps]: https://github.github.com/gh-aw/reference/frontmatter/#custom-steps-steps -[jobs]: https://github.github.com/gh-aw/reference/frontmatter/#custom-jobs-jobs -[job-outputs]: https://github.github.com/gh-aw/reference/frontmatter/#job-outputs -[engine]: https://github.github.com/gh-aw/reference/frontmatter/#ai-engine-engine -[engine-vars]: https://github.github.com/gh-aw/reference/engines/#engine-environment-variables -[secret-override]: https://github.com/github/gh-aw/pull/18017 -[case-expression]: https://docs.github.com/actions/reference/workflows-and-actions/expressions#case -[passing-secrets]: https://docs.github.com/actions/reference/workflows-and-actions/workflow-commands#example-masking-and-passing-a-secret-between-jobs-or-workflows diff --git a/external/xamarin-android-tools/.github/workflows/shared/pat_pool.md b/external/xamarin-android-tools/.github/workflows/shared/pat_pool.md deleted file mode 100644 index 7d9b3b974e6..00000000000 --- a/external/xamarin-android-tools/.github/workflows/shared/pat_pool.md +++ /dev/null @@ -1,119 +0,0 @@ ---- -description: Agentic workflow import to integrate the Copilot PAT Pool - -jobs: - pat_pool: - environment: ${{ github.aw.import-inputs.environment }} - needs: [pre_activation] - runs-on: ubuntu-slim - outputs: - pat_number: ${{ steps.select-pat-number.outputs.copilot_pat_number }} - steps: - - id: select-pat-number - name: Select Copilot token from pool - env: - COPILOT_PAT_0: ${{ github.aw.import-inputs.COPILOT_PAT_0 }} - COPILOT_PAT_1: ${{ github.aw.import-inputs.COPILOT_PAT_1 }} - COPILOT_PAT_2: ${{ github.aw.import-inputs.COPILOT_PAT_2 }} - COPILOT_PAT_3: ${{ github.aw.import-inputs.COPILOT_PAT_3 }} - COPILOT_PAT_4: ${{ github.aw.import-inputs.COPILOT_PAT_4 }} - COPILOT_PAT_5: ${{ github.aw.import-inputs.COPILOT_PAT_5 }} - COPILOT_PAT_6: ${{ github.aw.import-inputs.COPILOT_PAT_6 }} - COPILOT_PAT_7: ${{ github.aw.import-inputs.COPILOT_PAT_7 }} - COPILOT_PAT_8: ${{ github.aw.import-inputs.COPILOT_PAT_8 }} - COPILOT_PAT_9: ${{ github.aw.import-inputs.COPILOT_PAT_9 }} - RANDOM_SEED: ${{ github.aw.import-inputs.random_seed }} - shell: bash - run: | - # Collect pool entries with non-empty secrets from COPILOT_PAT_0..COPILOT_PAT_9. - PAT_NUMBERS=() - POOL_INDICATORS=(➖ ➖ ➖ ➖ ➖ ➖ ➖ ➖ ➖ ➖) - - for i in $(seq 0 9); do - var="COPILOT_PAT_${i}" - val="${!var}" - if [ -n "$val" ]; then - PAT_NUMBERS+=(${i}) - POOL_INDICATORS[${i}]="🟪" - fi - done - - # If none of the entries in the pool have values, emit a warning - # and do not set an output value. The consumer can fall back to - # using COPILOT_GITHUB_TOKEN. - if [ ${#PAT_NUMBERS[@]} -eq 0 ]; then - warning_message="::warning::None of the PAT pool entries had values " - warning_message+="(checked COPILOT_PAT_0 through COPILOT_PAT_9)" - echo "$warning_message" - exit 0 - fi - - # Select a random index using the seed if specified. - if [ -n "$RANDOM_SEED" ]; then - RANDOM=$RANDOM_SEED - fi - - PAT_INDEX=$(( RANDOM % ${#PAT_NUMBERS[@]} )) - PAT_NUMBER="${PAT_NUMBERS[$PAT_INDEX]}" - POOL_INDICATORS[${PAT_NUMBER}]="✅" - - echo "Pool size: ${#PAT_NUMBERS[@]}" - echo "Selected PAT number ${PAT_NUMBER} (index: ${PAT_INDEX})" - - echo "|0|1|2|3|4|5|6|7|8|9|" >> "$GITHUB_STEP_SUMMARY" - echo "|-|-|-|-|-|-|-|-|-|-|" >> "$GITHUB_STEP_SUMMARY" - (IFS='|'; printf '|%s' "${POOL_INDICATORS[@]}"; printf '|\n') >> "$GITHUB_STEP_SUMMARY" - - echo "copilot_pat_number=${PAT_NUMBER}" >> "$GITHUB_OUTPUT" - -import-schema: - environment: - type: string - required: true - COPILOT_PAT_0: - type: string - required: false - default: ${{ secrets.COPILOT_PAT_0 }} - COPILOT_PAT_1: - type: string - required: false - default: ${{ secrets.COPILOT_PAT_1 }} - COPILOT_PAT_2: - type: string - required: false - default: ${{ secrets.COPILOT_PAT_2 }} - COPILOT_PAT_3: - type: string - required: false - default: ${{ secrets.COPILOT_PAT_3 }} - COPILOT_PAT_4: - type: string - required: false - default: ${{ secrets.COPILOT_PAT_4 }} - COPILOT_PAT_5: - type: string - required: false - default: ${{ secrets.COPILOT_PAT_5 }} - COPILOT_PAT_6: - type: string - required: false - default: ${{ secrets.COPILOT_PAT_6 }} - COPILOT_PAT_7: - type: string - required: false - default: ${{ secrets.COPILOT_PAT_7 }} - COPILOT_PAT_8: - type: string - required: false - default: ${{ secrets.COPILOT_PAT_8 }} - COPILOT_PAT_9: - type: string - required: false - default: ${{ secrets.COPILOT_PAT_9 }} - random_seed: - type: number - required: false - description: >- - A seed number to use for the random PAT number selection, - for deterministic selection if needed. ---- diff --git a/external/xamarin-android-tools/.github/workflows/validate-pat-pool.yml b/external/xamarin-android-tools/.github/workflows/validate-pat-pool.yml deleted file mode 100644 index 5815082154d..00000000000 --- a/external/xamarin-android-tools/.github/workflows/validate-pat-pool.yml +++ /dev/null @@ -1,206 +0,0 @@ -name: Validate PAT Pool - -on: - schedule: - - cron: '17 2 * * *' # Daily at ~2:17 AM UTC (off-round to reduce contention) - workflow_dispatch: - -permissions: {} - -jobs: - validate: - environment: copilot-pat-pool - name: Validate Copilot PAT Pool - if: ${{ github.event_name == 'workflow_dispatch' || !github.event.repository.fork }} - runs-on: ubuntu-latest - env: - VALIDATE_PAT: | - if [ -z "$COPILOT_GITHUB_TOKEN" ]; then echo "status=empty" >> "$GITHUB_OUTPUT"; exit 0; fi - set +e; timeout 30 copilot --prompt "Say OK" --available-tools="" --silent --effort=low; rc=$?; set -e - if [ $rc -eq 0 ]; then echo "status=valid" >> "$GITHUB_OUTPUT" - elif [ $rc -eq 124 ]; then echo "status=unknown" >> "$GITHUB_OUTPUT" - else echo "status=invalid" >> "$GITHUB_OUTPUT"; fi - steps: - - name: Setup gh-aw scripts - uses: github/gh-aw-actions/setup@8c7d04ebf1ece56cd381446125da3e0f6896294a # v0.80.9 - with: - destination: ${{ runner.temp }}/gh-aw/actions - - - name: Install Copilot CLI - run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.63 - - - name: Validate COPILOT_PAT_0 - id: pat0 - continue-on-error: true - env: - COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_PAT_0 }} - shell: bash - run: | - eval "$VALIDATE_PAT" - - - name: Validate COPILOT_PAT_1 - id: pat1 - continue-on-error: true - env: - COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_PAT_1 }} - shell: bash - run: | - eval "$VALIDATE_PAT" - - - name: Validate COPILOT_PAT_2 - id: pat2 - continue-on-error: true - env: - COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_PAT_2 }} - shell: bash - run: | - eval "$VALIDATE_PAT" - - - name: Validate COPILOT_PAT_3 - id: pat3 - continue-on-error: true - env: - COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_PAT_3 }} - shell: bash - run: | - eval "$VALIDATE_PAT" - - - name: Validate COPILOT_PAT_4 - id: pat4 - continue-on-error: true - env: - COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_PAT_4 }} - shell: bash - run: | - eval "$VALIDATE_PAT" - - - name: Validate COPILOT_PAT_5 - id: pat5 - continue-on-error: true - env: - COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_PAT_5 }} - shell: bash - run: | - eval "$VALIDATE_PAT" - - - name: Validate COPILOT_PAT_6 - id: pat6 - continue-on-error: true - env: - COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_PAT_6 }} - shell: bash - run: | - eval "$VALIDATE_PAT" - - - name: Validate COPILOT_PAT_7 - id: pat7 - continue-on-error: true - env: - COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_PAT_7 }} - shell: bash - run: | - eval "$VALIDATE_PAT" - - - name: Validate COPILOT_PAT_8 - id: pat8 - continue-on-error: true - env: - COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_PAT_8 }} - shell: bash - run: | - eval "$VALIDATE_PAT" - - - name: Validate COPILOT_PAT_9 - id: pat9 - continue-on-error: true - env: - COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_PAT_9 }} - shell: bash - run: | - eval "$VALIDATE_PAT" - - - name: Build summary - if: always() - env: - S0: ${{ steps.pat0.outputs.status }} - S1: ${{ steps.pat1.outputs.status }} - S2: ${{ steps.pat2.outputs.status }} - S3: ${{ steps.pat3.outputs.status }} - S4: ${{ steps.pat4.outputs.status }} - S5: ${{ steps.pat5.outputs.status }} - S6: ${{ steps.pat6.outputs.status }} - S7: ${{ steps.pat7.outputs.status }} - S8: ${{ steps.pat8.outputs.status }} - S9: ${{ steps.pat9.outputs.status }} - shell: bash - run: | - statuses=("$S0" "$S1" "$S2" "$S3" "$S4" "$S5" "$S6" "$S7" "$S8" "$S9") - - valid=0; empty=0; invalid=0; unknown=0 - for s in "${statuses[@]}"; do - case "$s" in - valid) valid=$((valid + 1)) ;; - empty) empty=$((empty + 1)) ;; - invalid) invalid=$((invalid + 1)) ;; - *) unknown=$((unknown + 1)) ;; - esac - done - - { - if [ $invalid -eq 0 ] && [ $unknown -eq 0 ] && [ $valid -gt 0 ]; then - echo "> [!NOTE]" - echo "> **PAT pool is valid** -- no action needed" - echo "" - fi - - if [ $invalid -eq 0 ] && [ $unknown -eq 0 ] && [ $valid -eq 0 ]; then - echo "> [!WARNING]" - echo "> **Empty PAT pool** -- agentic workflows will fall back to the default \`COPILOT_GITHUB_TOKEN\`." - echo "" - fi - - if [ $invalid -gt 0 ]; then - echo "> [!CAUTION]" - echo "> **Invalid PAT pool** -- agentic workflows selecting an invalid PAT will fail." - echo "" - fi - - if [ $unknown -gt 0 ]; then - echo "> [!WARNING]" - echo "> **PAT pool not verified** due to transient errors -- re-run the workflow to retry." - echo "" - fi - - echo "☑️ Valid: ${valid} • ⏹️ Empty: ${empty} • ❌ Invalid: ${invalid} • ❓ Unknown: ${unknown}" - echo "" - - echo "| PAT Secret | Status |" - echo "|:-----------|:-------|" - - for i in $(seq 0 9); do - case "${statuses[$i]}" in - valid) symbol="☑️ Valid" ;; - empty) symbol="⏹️ Empty" ;; - invalid) symbol="❌ Invalid" ;; - *) symbol="❓ Unknown" ;; - esac - echo "| \`COPILOT_PAT_${i}\` | ${symbol} |" - done - } >> "$GITHUB_STEP_SUMMARY" - - if [ $invalid -gt 0 ]; then - echo "::error::${invalid} PAT(s) in the pool are invalid and need to be removed or replaced" - exit 1 - fi - - if [ $unknown -gt 0 ]; then - echo "::error::${unknown} PAT(s) could not be verified due to transient errors -- re-run to retry" - exit 1 - fi - - if [ $valid -eq 0 ]; then - echo "::error::The PAT pool is empty -- no PATs are available" - exit 1 - fi - - echo "PAT pool validation passed: ${valid} valid PAT(s)" diff --git a/external/xamarin-android-tools/.vscode/extensions.json b/external/xamarin-android-tools/.vscode/extensions.json deleted file mode 100644 index 082d07a6cca..00000000000 --- a/external/xamarin-android-tools/.vscode/extensions.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "recommendations": [ - "ms-vscode.csharp", - "ms-vscode.mono-debug", - "hbenl.vscode-test-explorer", - "derivitec-ltd.vscode-dotnet-adapter", - "visualstudioexptteam.vscodeintellicode", - ] -} \ No newline at end of file diff --git a/external/xamarin-android-tools/.vscode/launch.json b/external/xamarin-android-tools/.vscode/launch.json deleted file mode 100644 index a0ddfb7fc04..00000000000 --- a/external/xamarin-android-tools/.vscode/launch.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - // Use IntelliSense to learn about possible attributes. - // Hover to view descriptions of existing attributes. - // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 - "version": "0.2.0", - "configurations": [ - { - "name": "Launch", - "type": "mono", - "request": "launch", - "program": "${workspaceRoot}/packages/NUnit.ConsoleRunner.3.9.0/tools/nunit3-console.exe ${workspaceRoot}/bin/TestDebug/generator-Tests.dll", - "cwd": "${workspaceRoot}bin/TestDebug/" - }, - { - "name": "Attach", - "type": "mono", - "request": "attach", - "address": "localhost", - "port": 55555 - } - ] -} \ No newline at end of file diff --git a/external/xamarin-android-tools/.vscode/settings.json b/external/xamarin-android-tools/.vscode/settings.json deleted file mode 100644 index 6f3397a0a5c..00000000000 --- a/external/xamarin-android-tools/.vscode/settings.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "dotnetCoreExplorer.searchpatterns": "bin/Test*net*/**/*-Tests.dll", -} diff --git a/external/xamarin-android-tools/.vscode/tasks.json b/external/xamarin-android-tools/.vscode/tasks.json deleted file mode 100644 index ae99578adf1..00000000000 --- a/external/xamarin-android-tools/.vscode/tasks.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - // See https://go.microsoft.com/fwlink/?LinkId=733558 - // for the documentation about the tasks.json format - "version": "2.0.0", - "tasks": [ - { - "label": "Build Xamarin.Android.Tools", - "type": "shell", - "command": "msbuild Xamarin.Android.Tools.sln /restore", - "group": { - "kind": "build", - "isDefault": true - }, - "problemMatcher": [ - "$msCompile" - ] - }, - { - "label": "Clean Xamarin.Android.Tools", - "type": "shell", - "command": "msbuild Xamarin.Android.Tools.sln /restore /t:Clean", - "group": { - "kind": "build", - "isDefault": true - }, - "problemMatcher": [ - "$msCompile" - ] - }, - { - "label": "Run Unit Tests", - "type": "shell", - "command": "dotnet", - "args": [ - "test", - "${workspaceFolder}/tests/Xamarin.Android.Tools.AndroidSdk-Tests/Xamarin.Android.Tools.AndroidSdk-Tests.csproj" - ], - "group": { - "kind": "test", - "isDefault": true - }, - "problemMatcher": [ - "$msCompile" - ] - } - ] -} diff --git a/external/xamarin-android-tools/CODE-OF-CONDUCT.md b/external/xamarin-android-tools/CODE-OF-CONDUCT.md deleted file mode 100644 index 775f221c98e..00000000000 --- a/external/xamarin-android-tools/CODE-OF-CONDUCT.md +++ /dev/null @@ -1,6 +0,0 @@ -# Code of Conduct - -This project has adopted the code of conduct defined by the Contributor Covenant -to clarify expected behavior in our community. - -For more information, see the [.NET Foundation Code of Conduct](https://dotnetfoundation.org/code-of-conduct). diff --git a/external/xamarin-android-tools/Localize/onelocbuild.yaml b/external/xamarin-android-tools/Localize/onelocbuild.yaml deleted file mode 100644 index 4b0220f630b..00000000000 --- a/external/xamarin-android-tools/Localize/onelocbuild.yaml +++ /dev/null @@ -1,77 +0,0 @@ -# xamarin-android-tools - OneLocBuild - -name: $(Build.SourceBranchName)-$(Build.SourceVersion)-$(Rev:r) - -trigger: none - -pr: none - -schedules: -- cron: "0 6 * * *" - displayName: Run daily at 6:00 UTC - branches: - include: - - main -- cron: "0 6 * * Sunday" - displayName: Run weekly on Sunday at 6:00 UTC - branches: - include: - - main - always: true - -jobs: -- job: OneLocBuild - displayName: OneLocBuild - condition: and(eq(variables['System.TeamProject'], 'DevDiv'), eq(variables['Build.SourceBranch'], 'refs/heads/main')) - pool: - name: AzurePipelines-EO - demands: - - ImageOverride -equals 1ESPT-Windows2022 - timeoutInMinutes: 30 - variables: - - group: Xamarin-Secrets - workspace: - clean: all - steps: - - checkout: self - clean: true - - # https://eng.ms/docs/cloud-ai-platform/devdiv/one-engineering-system-1es/1es-docs/1es-security-configuration/configuration-guides/pat-burndown-guidance#authentication-from-pipelines - # Requires Azure client 2.x - - task: AzureCLI@2 - displayName: 'Set AzDO.OneLocBuildToken' - enabled: true - inputs: - azureSubscription: 'VSEng-AzureDevOps-ceapex-OneLocBuild' # Azure DevOps service connection - scriptType: 'pscore' - scriptLocation: 'inlineScript' - inlineScript: | - # if this fails, check out this bash script that includes diagnostics: - # https://gist.github.com/johnterickson/19f80a3e969e39f1000d118739176e62 - # Note that the resource is specified to limit the token to Azure DevOps - $token = az account get-access-token --query accessToken --resource 499b84ac-1321-427f-aa17-267ca6975798 -o tsv - Write-Host "##vso[task.setvariable variable=AzDO.OneLocBuildToken;issecret=true]${token}" - - - task: OneLocBuild@2 - displayName: OneLocBuild - env: - SYSTEM_ACCESSTOKEN: $(System.AccessToken) - inputs: - locProj: Localize/LocProject.json - outDir: $(Build.StagingDirectory) - packageSourceAuth: patAuth - patVariable: $(AzDO.OneLocBuildToken) - isCreatePrSelected: true - repoType: gitHub - gitHubPatVariable: $(github--pat--vs-mobiletools-engineering-service2) - prSourceBranchPrefix: locpr - isShouldReusePrSelected: true - isAutoCompletePrSelected: false - isUseLfLineEndingsSelected: true - - - task: PublishBuildArtifacts@1 - displayName: Publish Localization Files - inputs: - PathtoPublish: $(Build.StagingDirectory)/loc - ArtifactName: Loc - condition: succeededOrFailed() diff --git a/external/xamarin-android-tools/SECURITY.md b/external/xamarin-android-tools/SECURITY.md deleted file mode 100644 index 1fa12d855c0..00000000000 --- a/external/xamarin-android-tools/SECURITY.md +++ /dev/null @@ -1,15 +0,0 @@ -# Security Policy - -xamarin-android-tools is supported as a component of Xamarin.Android. - -## Supported Versions - -The Xamarin.Android support policy, including supported versions, can be found at the [Xamarin Support Policy](https://dotnet.microsoft.com/platform/support/policy/xamarin). - -## Reporting a Vulnerability - -Security issues and bugs should be reported privately to the Microsoft Security Response Center (MSRC), either by emailing secure@microsoft.com or via the portal at https://msrc.microsoft.com. -You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your -original message. Further information, including the MSRC PGP key, can be found in the [MSRC Report an Issue FAQ](https://www.microsoft.com/en-us/msrc/faqs-report-an-issue). - -Please do not open issues for anything you think might have a security implication. diff --git a/external/xamarin-android-tools/Xamarin.Android.Tools.code-workspace b/external/xamarin-android-tools/Xamarin.Android.Tools.code-workspace deleted file mode 100644 index be71cd0b901..00000000000 --- a/external/xamarin-android-tools/Xamarin.Android.Tools.code-workspace +++ /dev/null @@ -1,9 +0,0 @@ -{ - "folders": [ - { - "path": "." - } - ], - "settings": { - } -} diff --git a/external/xamarin-android-tools/Xamarin.Android.Tools.sln b/external/xamarin-android-tools/Xamarin.Android.Tools.sln index ed193ca9617..d4618ff7b63 100644 --- a/external/xamarin-android-tools/Xamarin.Android.Tools.sln +++ b/external/xamarin-android-tools/Xamarin.Android.Tools.sln @@ -1,59 +1,59 @@ -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.30709.64 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Xamarin.Android.Tools.AndroidSdk", "src\Xamarin.Android.Tools.AndroidSdk\Xamarin.Android.Tools.AndroidSdk.csproj", "{E34BCFA0-CAA4-412C-AA1C-75DB8D67D157}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Xamarin.Android.Tools.AndroidSdk-Tests", "tests\Xamarin.Android.Tools.AndroidSdk-Tests\Xamarin.Android.Tools.AndroidSdk-Tests.csproj", "{1E5501E8-49C1-4659-838D-CC9720C5208F}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Android.Build.BaseTasks", "src\Microsoft.Android.Build.BaseTasks\Microsoft.Android.Build.BaseTasks.csproj", "{C8E51A26-F7A5-4B81-B0BC-F9A4BFB43D38}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Android.Build.BaseTasks-Tests", "tests\Microsoft.Android.Build.BaseTasks-Tests\Microsoft.Android.Build.BaseTasks-Tests.csproj", "{4F5FD01C-B5A6-4F9F-91CE-3E78B3F942AC}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tools", "tools", "{19FE1B44-DB71-4F97-A07E-085888690DAE}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ls-jdks", "tools\ls-jdks\ls-jdks.csproj", "{3D2ADF77-62C7-4E0E-AB29-BDD266B7D56D}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Xamarin.Android.Tools.Benchmarks", "tests\Xamarin.Android.Tools.Benchmarks\Xamarin.Android.Tools.Benchmarks.csproj", "{F1A2B3C4-D5E6-4F7A-8B9C-0D1E2F3A4B5C}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {E34BCFA0-CAA4-412C-AA1C-75DB8D67D157}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E34BCFA0-CAA4-412C-AA1C-75DB8D67D157}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E34BCFA0-CAA4-412C-AA1C-75DB8D67D157}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E34BCFA0-CAA4-412C-AA1C-75DB8D67D157}.Release|Any CPU.Build.0 = Release|Any CPU - {1E5501E8-49C1-4659-838D-CC9720C5208F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {1E5501E8-49C1-4659-838D-CC9720C5208F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {1E5501E8-49C1-4659-838D-CC9720C5208F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {1E5501E8-49C1-4659-838D-CC9720C5208F}.Release|Any CPU.Build.0 = Release|Any CPU - {C8E51A26-F7A5-4B81-B0BC-F9A4BFB43D38}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C8E51A26-F7A5-4B81-B0BC-F9A4BFB43D38}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C8E51A26-F7A5-4B81-B0BC-F9A4BFB43D38}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C8E51A26-F7A5-4B81-B0BC-F9A4BFB43D38}.Release|Any CPU.Build.0 = Release|Any CPU - {4F5FD01C-B5A6-4F9F-91CE-3E78B3F942AC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4F5FD01C-B5A6-4F9F-91CE-3E78B3F942AC}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4F5FD01C-B5A6-4F9F-91CE-3E78B3F942AC}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4F5FD01C-B5A6-4F9F-91CE-3E78B3F942AC}.Release|Any CPU.Build.0 = Release|Any CPU - {3D2ADF77-62C7-4E0E-AB29-BDD266B7D56D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {3D2ADF77-62C7-4E0E-AB29-BDD266B7D56D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {3D2ADF77-62C7-4E0E-AB29-BDD266B7D56D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {3D2ADF77-62C7-4E0E-AB29-BDD266B7D56D}.Release|Any CPU.Build.0 = Release|Any CPU - {F1A2B3C4-D5E6-4F7A-8B9C-0D1E2F3A4B5C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F1A2B3C4-D5E6-4F7A-8B9C-0D1E2F3A4B5C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F1A2B3C4-D5E6-4F7A-8B9C-0D1E2F3A4B5C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F1A2B3C4-D5E6-4F7A-8B9C-0D1E2F3A4B5C}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {BA1CD771-F00B-4DE8-93EE-7690D81F6A5A} - EndGlobalSection - GlobalSection(NestedProjects) = preSolution - {3D2ADF77-62C7-4E0E-AB29-BDD266B7D56D} = {19FE1B44-DB71-4F97-A07E-085888690DAE} - EndGlobalSection -EndGlobal +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.30709.64 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Xamarin.Android.Tools.AndroidSdk", "src\Xamarin.Android.Tools.AndroidSdk\Xamarin.Android.Tools.AndroidSdk.csproj", "{E34BCFA0-CAA4-412C-AA1C-75DB8D67D157}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Xamarin.Android.Tools.AndroidSdk-Tests", "tests\Xamarin.Android.Tools.AndroidSdk-Tests\Xamarin.Android.Tools.AndroidSdk-Tests.csproj", "{1E5501E8-49C1-4659-838D-CC9720C5208F}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Android.Build.BaseTasks", "src\Microsoft.Android.Build.BaseTasks\Microsoft.Android.Build.BaseTasks.csproj", "{C8E51A26-F7A5-4B81-B0BC-F9A4BFB43D38}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Android.Build.BaseTasks-Tests", "tests\Microsoft.Android.Build.BaseTasks-Tests\Microsoft.Android.Build.BaseTasks-Tests.csproj", "{4F5FD01C-B5A6-4F9F-91CE-3E78B3F942AC}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tools", "tools", "{19FE1B44-DB71-4F97-A07E-085888690DAE}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ls-jdks", "tools\ls-jdks\ls-jdks.csproj", "{3D2ADF77-62C7-4E0E-AB29-BDD266B7D56D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Xamarin.Android.Tools.Benchmarks", "tests\Xamarin.Android.Tools.Benchmarks\Xamarin.Android.Tools.Benchmarks.csproj", "{F1A2B3C4-D5E6-4F7A-8B9C-0D1E2F3A4B5C}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {E34BCFA0-CAA4-412C-AA1C-75DB8D67D157}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E34BCFA0-CAA4-412C-AA1C-75DB8D67D157}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E34BCFA0-CAA4-412C-AA1C-75DB8D67D157}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E34BCFA0-CAA4-412C-AA1C-75DB8D67D157}.Release|Any CPU.Build.0 = Release|Any CPU + {1E5501E8-49C1-4659-838D-CC9720C5208F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1E5501E8-49C1-4659-838D-CC9720C5208F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1E5501E8-49C1-4659-838D-CC9720C5208F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1E5501E8-49C1-4659-838D-CC9720C5208F}.Release|Any CPU.Build.0 = Release|Any CPU + {C8E51A26-F7A5-4B81-B0BC-F9A4BFB43D38}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C8E51A26-F7A5-4B81-B0BC-F9A4BFB43D38}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C8E51A26-F7A5-4B81-B0BC-F9A4BFB43D38}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C8E51A26-F7A5-4B81-B0BC-F9A4BFB43D38}.Release|Any CPU.Build.0 = Release|Any CPU + {4F5FD01C-B5A6-4F9F-91CE-3E78B3F942AC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4F5FD01C-B5A6-4F9F-91CE-3E78B3F942AC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4F5FD01C-B5A6-4F9F-91CE-3E78B3F942AC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4F5FD01C-B5A6-4F9F-91CE-3E78B3F942AC}.Release|Any CPU.Build.0 = Release|Any CPU + {3D2ADF77-62C7-4E0E-AB29-BDD266B7D56D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3D2ADF77-62C7-4E0E-AB29-BDD266B7D56D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3D2ADF77-62C7-4E0E-AB29-BDD266B7D56D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3D2ADF77-62C7-4E0E-AB29-BDD266B7D56D}.Release|Any CPU.Build.0 = Release|Any CPU + {F1A2B3C4-D5E6-4F7A-8B9C-0D1E2F3A4B5C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F1A2B3C4-D5E6-4F7A-8B9C-0D1E2F3A4B5C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F1A2B3C4-D5E6-4F7A-8B9C-0D1E2F3A4B5C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F1A2B3C4-D5E6-4F7A-8B9C-0D1E2F3A4B5C}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {BA1CD771-F00B-4DE8-93EE-7690D81F6A5A} + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {3D2ADF77-62C7-4E0E-AB29-BDD266B7D56D} = {19FE1B44-DB71-4F97-A07E-085888690DAE} + EndGlobalSection +EndGlobal diff --git a/external/xamarin-android-tools/azure-pipelines.yaml b/external/xamarin-android-tools/azure-pipelines.yaml deleted file mode 100644 index e6beef609d1..00000000000 --- a/external/xamarin-android-tools/azure-pipelines.yaml +++ /dev/null @@ -1,85 +0,0 @@ -name: android-tools $(Rev:r) - -trigger: -- main -- release/* - -pr: -- main -- release/* - -# Global variables -variables: - - name: DotNetCoreVersion - value: 10.0.x - -jobs: -- job: build - displayName: Build and Test - timeoutInMinutes: 60 - strategy: - matrix: - macOS: - vmImage: macOS-15 - windows: - vmImage: windows-2022 - pool: - vmImage: $(vmImage) - workspace: - clean: all - steps: - - checkout: self - clean: true - - - task: UseDotNet@2 - displayName: Use .NET Core $(DotNetCoreVersion) - inputs: - version: $(DotNetCoreVersion) - - - task: DotNetCoreCLI@2 - displayName: Build solution Xamarin.Android.Tools.sln - inputs: - projects: Xamarin.Android.Tools.sln - arguments: -bl:$(Build.ArtifactStagingDirectory)/build.binlog - - - task: DotNetCoreCLI@2 - displayName: Run Tests - inputs: - command: test - projects: bin/TestDebug/**/*-Tests.dll - - - powershell: | - $hashOfLastVersionChange = & "git" "log" "--follow" "-1" "--pretty=%H" "nuget.version" - $commitsSinceVersionChange = & "git" "rev-list" "--count" "$hashOfLastVersionChange..HEAD" - $majorMinor = Get-Content "nuget.version" - $version = "$majorMinor.$commitsSinceVersionChange" - Write-Host "##vso[task.setvariable variable=xat.nuget.version]$version" - condition: and(succeeded(), eq(variables['agent.os'], 'Windows_NT')) - - - task: DotNetCoreCLI@2 - displayName: Build NuGet - inputs: - command: custom - projects: src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj - custom: pack - arguments: -p:Version=$(xat.nuget.version) -p:PackageOutputPath=$(Build.ArtifactStagingDirectory) -bl:$(Build.ArtifactStagingDirectory)/pack.binlog - condition: and(succeeded(), eq(variables['agent.os'], 'Windows_NT')) - - - powershell: | - $suffix = if ($(System.JobAttempt) -gt 1) { " (Attempt $(System.JobAttempt))" } else { "" } - Write-Host "##vso[task.setvariable variable=ArtifactSuffix;]$suffix" - displayName: Set artifact suffix for retries - condition: always() - - - task: PublishPipelineArtifact@1 - displayName: Upload Build Output - inputs: - path: bin/Debug - artifactName: Output - $(System.JobName)$(ArtifactSuffix) - - - task: PublishPipelineArtifact@1 - displayName: Upload Artifacts - inputs: - path: $(Build.ArtifactStagingDirectory) - artifactName: Artifacts - $(System.JobName)$(ArtifactSuffix) - condition: always() diff --git a/external/xamarin-android-tools/docs/CODE_REVIEW_POSTMORTEM.md b/external/xamarin-android-tools/docs/CODE_REVIEW_POSTMORTEM.md deleted file mode 100644 index 8fad7f2c2aa..00000000000 --- a/external/xamarin-android-tools/docs/CODE_REVIEW_POSTMORTEM.md +++ /dev/null @@ -1,614 +0,0 @@ -# Code Review Postmortem - -Review feedback from **@jonathanpeppers** (and Copilot reviewer) on Copilot-assisted PRs by @rmarinho, adding new infrastructure to `Xamarin.Android.Tools.AndroidSdk`. - -| PR | Title | Status | -|----|-------|--------| -| [#274](https://github.com/dotnet/android-tools/pull/274) | Add JDK installation support (Microsoft OpenJDK) | Merged 2026-02-26 | -| [#275](https://github.com/dotnet/android-tools/pull/275) | Add SDK bootstrap and sdkmanager wrapper | Merged 2026-03-02 | -| [#281](https://github.com/dotnet/android-tools/pull/281) | Add tool runner base infrastructure | Open | -| [#282](https://github.com/dotnet/android-tools/pull/282) | Add AvdManagerRunner for avdmanager CLI operations | Open | -| [#283](https://github.com/dotnet/android-tools/pull/283) | Add AdbRunner for adb CLI operations | Open | -| [#284](https://github.com/dotnet/android-tools/pull/284) | Add EmulatorRunner for emulator CLI operations | Open | - ---- - -## 1. Use the right source: Microsoft OpenJDK, not Adoptium - -**PR #274** — The initial implementation downloaded JDKs from the Eclipse Adoptium (Temurin) API. Jonathan redirected it to Microsoft's own OpenJDK build at . - -> "Should use instead: https://www.microsoft.com/openjdk" - -**Why it matters:** The library ships inside Microsoft tooling (Visual Studio, .NET Android SDK). Downloading from a third-party distribution introduces supply-chain risk and licensing ambiguity. Using Microsoft's own build keeps the provenance chain tight and ensures the JDK is tested against Microsoft's own CI. - ---- - -## 2. Don't ship support for versions that aren't ready - -**PR #274** — The original code supported both JDK 17 and JDK 21. Jonathan asked to remove 17 and ship only 21, while keeping the `SupportedVersions` array so more can be added later. - -> "I wouldn't support 17 yet, but it would be ok to leave this as an `int[]` so more could be added later." - -**Why it matters:** Supporting a version means testing, documenting, and maintaining it. Shipping JDK 17 support without the infrastructure to validate it invites bugs that only appear in production. Leaving the extensibility point (the array) costs nothing and keeps the door open. - ---- - -## 3. Must target netstandard2.0 — this runs inside Visual Studio - -**PR #274** — The code originally targeted only modern .NET. Jonathan flagged that it must support `netstandard2.0` because the library loads inside Visual Studio (which runs on .NET Framework). - -> "This code needs to be able run on .NET framework — so it needs to support netstandard. It could be running inside Visual Studio." - -**Why it matters:** A library that compiles fine on `net10.0` but uses APIs absent from `netstandard2.0` will crash at runtime inside VS. This is the kind of bug that passes CI (tests run on modern .NET) but fails in the field. Every new API call must be checked against the `netstandard2.0` surface area. - ---- - -## 4. New C# language features may not be available - -**PR #274** (commit `b2a8a7be`) — Jonathan fixed code that used newer C# language features not available when targeting `netstandard2.0` / older compilers. - -**Why it matters:** When Copilot generates code, it tends to use the newest syntax it knows. If the project's `LangVersion` or target framework doesn't support those features, the build breaks. Reviewers must watch for this, especially in multi-target projects. - ---- - -## 5. Thread `CancellationToken` through every async call - -**PR #274** (commit `212ac436`) and **PR #275** (review comment) — Multiple places accepted a `CancellationToken` parameter but never passed it to the underlying HTTP call (`GetStringAsync`, `ReadAsStreamAsync`, etc.). Jonathan fixed this throughout. - -> (PR #275): Copilot reviewer flagged `GetStringAsync` not passing `cancellationToken`. Jonathan's commit wired it through properly. - -**Why it matters:** An un-cancellable async method is a lie — callers think they can cancel but the operation keeps running, holding sockets, memory, and threads. In an IDE (VS), a stuck HTTP call can freeze the UI. Always propagate the token to the lowest-level async operation. - ---- - -## 6. `HttpClient` must be static — socket exhaustion - -**PR #275** (commit `d30a5d8e`) — The `SdkManager` class created a per-instance `HttpClient`. Jonathan changed it to `static readonly` and removed disposal. - -> Commit message: "Per Microsoft guidelines, HttpClient instances should be static and shared across the application lifetime." - -**Why it matters:** Creating and disposing `HttpClient` instances leads to socket exhaustion under load because `TIME_WAIT` sockets accumulate. The [official guidance](https://learn.microsoft.com/dotnet/fundamentals/networking/http/httpclient-guidelines) is to reuse a single static instance. This is a well-known .NET pitfall that AI-generated code consistently gets wrong. - ---- - -## 7. Remove `HttpClient` injection until there's a real need - -**PR #275** (threads 11, 26) — The AI-generated code accepted an optional `HttpClient` parameter for "testability" and "enterprise proxy scenarios." Jonathan pushed back twice, noting the justification was speculative. - -> "Why do we need to pass in an `HttpClient`? Could `SdkManager` just use its own and it would simplify some things?" -> -> "I would still remove the option to pass in a `HttpClient` until you have a reason you actually need it. The AI gave some made-up/theoretical answer on why not to remove it." - -**Why it matters:** YAGNI (You Aren't Gonna Need It). Every public API parameter is a maintenance commitment. AI assistants tend to over-engineer for hypothetical scenarios. If no caller needs to inject an `HttpClient` today, don't add the parameter — it complicates the constructor, the dispose logic, and the ownership semantics. - ---- - -## 8. Use `XmlReader` instead of `XElement` / LINQ to XML - -**PR #275** (thread 12) — Manifest parsing used `XDocument.Parse()` + LINQ queries. Jonathan requested `XmlReader` for lower overhead. - -> "If copilot wrote this, can we make it use `XmlReader` instead? `XElement` is based on System.Linq, so it's slower." - -**Why it matters:** `XElement` builds a full DOM tree in memory. For a manifest that only needs forward-only reading, `XmlReader` is streaming and allocation-free. In a library that may run in constrained IDE processes, avoiding unnecessary allocations matters. - ---- - -## 9. One type per file, and update Copilot instructions - -**PR #275** (threads 9, 25) — Multiple types were crammed into `SdkManager.cs`. Jonathan asked for each to live in its own file and for the convention to be added to `copilot-instructions.md` so AI assistants follow it going forward. - -> "Can we put each type in its own file, `SdkManifestComponent.cs`, and update `copilot-instructions.md` so AIs will always do this?" - -**Why it matters:** One-type-per-file is a core C# convention in this repo. It makes types discoverable via filename, simplifies git blame, and reduces merge conflicts. Encoding the convention in Copilot instructions prevents the same issue from recurring in every AI-assisted PR. - ---- - -## 10. Name types to avoid ambiguity — `SdkManifestComponent`, not `ManifestComponent` - -**PR #275** (thread 9) — The type was originally named `ManifestComponent`, which collides conceptually with `AndroidManifest.xml`. - -> "You may also want to pick a better name other than `Manifest`? There is `AndroidManifest.xml`, so maybe this needs to be `SdkManifestComponent`?" - -**Why it matters:** In a codebase that deals with both Android app manifests and SDK repository manifests, an ambiguous name causes confusion. Prefixing with `Sdk` makes the domain clear at a glance. - ---- - -## 11. No empty catch blocks — always log the exception - -**PR #275** (threads 17, 18) — Multiple `catch` blocks swallowed exceptions silently. - -> "Every `catch` should get the `Exception` and log it, so no empty catch blocks." - -**Why it matters:** Empty catch blocks hide bugs. When something goes wrong in production, there's no diagnostic trail. Even if the exception is expected (e.g., declining a license), logging it provides a breadcrumb for debugging. This was a recurring pattern the AI generated. - ---- - -## 12. Remove `#region` directives - -**PR #275** (thread 19) — Test files had `#region` blocks. - -> "Can we remove all `#region` and update `copilot-instructions.md` so it won't make them in the future?" - -**Why it matters:** `#region` hides code and makes reviews harder — collapsed regions are easy to skip. Modern IDEs make them unnecessary. Banning them in Copilot instructions prevents AI from reintroducing them. - ---- - -## 13. Use `ArrayPool` for download buffers - -**PR #274** (commit `6e167e00`) and **PR #275** (thread 21) — Download code allocated `new byte[81920]` on each call. Jonathan replaced this with `ArrayPool.Shared.Rent()` with `try/finally` return. - -> "Can this use `ArrayPool.Rent()` and return it in a `try-finally` block?" - -**Why it matters:** An 80 KB allocation goes straight to the Large Object Heap (LOH), which is expensive to collect. `ArrayPool` rents from a shared pool, avoiding GC pressure. In a library that may download multiple files, this adds up. - ---- - -## 14. Extract magic numbers into named constants - -**PR #274** (commit `6e167e00`) — The download buffer size (`81920`), bytes-per-MB divisor (`1048576`), and whitespace char array were inline literals. Jonathan extracted them to `const` fields (`BufferSize`, `BytesPerMB`, `WhitespaceChars`). - -**Why it matters:** Magic numbers obscure intent. A reviewer seeing `81920` has to mentally compute "oh, that's 80 KB." A constant named `BufferSize` communicates instantly. It also creates a single point of change. - ---- - -## 15. Centralize process creation in `ProcessUtils` - -**PR #274** (commits `d0f3bea2`, `9ed5ddc8`) and **PR #275** (thread 29) — The code manually constructed `ProcessStartInfo` in multiple places. Jonathan created `ProcessUtils.CreateProcessStartInfo()` that uses `ArgumentList` on modern .NET and falls back to a safe `Arguments` string on `netstandard2.0`. - -> "Can this use: ProcessUtils.cs#L22" - -**Why it matters:** Duplicated process-launch code means duplicated bugs — inconsistent argument escaping, missing `UseShellExecute = false`, forgotten `RedirectStandardOutput`. A centralized helper gets it right once. The `ArgumentList` vs `Arguments` split is particularly tricky to get right across target frameworks. - ---- - -## 16. Use `NullProgress` pattern instead of `progress?.Report()` - -**PR #274** (commit `48ede26d`) and **PR #275** (threads 98–99) — Code was littered with `progress?.Report(...)` null checks. Jonathan replaced these with a `NullProgress` sentinel assigned via `progress ??= NullProgress.Instance`. - -> "Can we set `progress ??=` with a 'null/no-op progress' so you don't have to put `progress?.` checks everywhere?" - -**Why it matters:** Null-propagation operators scattered throughout a method add visual noise and make it easy to miss a spot. The null-object pattern eliminates the entire class of "forgot to null-check" bugs and makes the code cleaner. - ---- - -## 17. `IsElevated()` is unnecessary — let the caller handle elevation - -**PR #274** (commit `e8e9db78`) and **PR #275** (thread 88) — The code had an `IsElevated()` helper with P/Invoke to check admin rights, with logic to re-launch elevated. Jonathan removed it. - -> "Should we even have this? It seems like we should check for elevation (only where required!) and error if we don't have it. Seems like VS Code or developers should run this tool already elevated?" - -**Why it matters:** Auto-elevation is a security anti-pattern — it silently escalates privileges. The calling tool (VS, VS Code, CLI) should prompt the user for elevation. The library should simply fail with a clear error if it lacks permissions. - ---- - -## 18. `ANDROID_SDK_ROOT` is deprecated — use `ANDROID_HOME` - -**PR #275** (thread 31) — The code set both `ANDROID_HOME` and `ANDROID_SDK_ROOT` environment variables. - -> "`ANDROID_SDK_ROOT` is deprecated: https://developer.android.com/tools/variables#envar — I don't think we should add new code using it." - -**Why it matters:** Setting a deprecated variable trains downstream tools and developers to depend on it, prolonging the migration. Following the official Android documentation (`ANDROID_HOME` only) keeps the codebase aligned with the platform's direction. - ---- - -## 19. Create `EnvironmentVariableNames` constants — no raw strings - -**PR #275** (threads 30, 42) — Environment variable names like `"ANDROID_HOME"` were scattered as string literals. - -> "Should we create a `EnvironmentVariableNames.cs` that has important env var names like these?" - -**Why it matters:** String typos in environment variable names produce silent, hard-to-debug failures (the wrong variable is read/set, but no error is thrown). A constants class catches typos at compile time and makes it easy to find all usages via "Find References." - ---- - -## 20. Validate enum/parameter values — don't silently accept garbage - -**PR #275** (thread 22) — The `checksumType` was a string parameter that only handled `"sha1"` but accepted anything without error. - -> "Why is there a `checksumType` parameter if it only does `SHA1`? Should it be checking the value and throwing an exception for unexpected hash types?" - -This also led to making `checksumType` an enum instead of a string (thread 85): - -> "Can `checksumType` be an enum, so no typos can occur? Also would avoid string comparisons." - -**Why it matters:** A function that silently ignores an unsupported value is a correctness bug waiting to happen. If a new checksum type is added to the manifest but the code doesn't support it, downloads would pass "verification" without actually being checked. Throw early, throw loud. - ---- - -## 21. Use p/invoke for `chmod` instead of spawning a process - -**PR #275** (thread 23) — The code ran `chmod +x` by spawning a child process. Jonathan suggested using p/invoke directly. - -> "Is there a p/invoke way to do this instead? Then it wouldn't run a new process." - -**Why it matters:** Spawning a process for a single syscall is expensive — it forks, execs, waits, and produces overhead for logging and error handling. A direct `[DllImport("libc")] chmod()` call is instantaneous and more reliable. It also makes error handling straightforward (check the return value) instead of parsing process exit codes. - ---- - -## 22. If `chmod` fails, throw — don't silently continue - -**PR #275** (thread 32) — After adding p/invoke for `chmod`, the failure path caught the exception and continued. - -> "If this fails, should we just let the exception happen? The problem is you won't be able to use `sdkmanager` if it fails, so seems like it should error?" - -**Why it matters:** If `chmod` fails, the `sdkmanager` binary won't be executable, and every subsequent operation will fail with a confusing "file not found" or "permission denied" error. Failing fast at the point of the real problem (chmod) gives the user an actionable error message. - ---- - -## 23. Use version-based directory names, not `latest` - -**PR #275** (thread 15) — The bootstrap extracted cmdline-tools to a `latest/` directory. - -> "On macOS, `latest` is a symlink to the versioned one, like `latest -> 19.0` or whatever. Could we just use the version number of the package for the directory name and not do anything with `latest` at all?" - -**Why it matters:** `latest` is ambiguous — it doesn't tell you which version is installed, and upgrading is a destructive overwrite with no rollback. A versioned directory (`cmdline-tools/19.0/`) is self-documenting, allows side-by-side versions, and matches how `sdkmanager` itself organizes packages. - ---- - -## 24. License presentation API — don't just auto-accept - -**PR #275** (thread 20) — The original `AcceptLicensesAsync` blindly sent `"y"` to stdin for every license prompt. Jonathan asked for an API to present licenses to the user first. - -> "If we have code here to _accept_ licenses, is there supposed to be an API that can be used to present them to the user? It seems like IDEs will show some UI for these, and CLI tools should prompt at the terminal." - -**Why it matters:** Auto-accepting licenses without user consent may violate legal requirements. IDEs need to display the license text and get explicit user approval. The resulting `GetPendingLicensesAsync()` + `AcceptLicensesAsync(IEnumerable)` API separates presentation from acceptance. - ---- - -## 25. Prefer `record` types for immutable data models - -**PR #275** (threads 78–80) — `SdkBootstrapProgress`, `SdkLicense`, and `SdkManifestComponent` were plain classes. Jonathan asked them to be `record` types. - -> "Can this be a `record`?" - -**Why it matters:** Records provide value equality, `ToString()`, and deconstruction for free. For small data-carrier types, they eliminate boilerplate and make intent clear: this is an immutable data bag, not a stateful object. - ---- - -## 26. Use file-scoped namespaces in all new files - -**PR #275** (threads 95–96) — New files used traditional block-scoped namespaces. - -> "Should we use file-scoped namespaces in all new files?" - -**Why it matters:** File-scoped namespaces (`namespace Foo;`) reduce one level of indentation across the entire file, improving readability. Jonathan also asked this to be added to `copilot-instructions.md` to prevent regression. - ---- - -## 27. Don't duplicate code — unify platform mappings - -**PR #275** (threads 81, 92) — Both `JdkInstaller` and `SdkManager` had independent OS/architecture-to-string mapping logic. - -> "This mapping seems specific to the `sdkmanager`, we already have this here: JdkInstaller.cs#L319-L334. We should either: Unify with `JdkInstaller` or put this code in SDKManager, if it is specific to Android SDK." - -**Why it matters:** Duplicated platform-detection logic means two places to update when a new architecture (e.g., RISC-V) is added, and two places that can diverge silently. Either share the code or make each copy clearly scoped to its specific use case. - ---- - -## 28. Remove unused / speculative code - -**PR #275** (threads 82–84) — An `AndroidEnvironmentHelper` class contained methods that weren't called, duplicated existing functionality, or belonged in a different PR. - -> "Is this method even called? We have other code that does this, we should find it and unify. I'd remove this for now, until it's needed." -> -> "We should completely remove this and use `AndroidVersions` classes directly." - -**Why it matters:** Dead code is worse than no code — it still needs to be read, understood, and maintained. It also misleads future contributors into thinking it's the right way to do things. Ship only what's needed for the current PR. - ---- - -## 29. Don't tell AI to run `dotnet format` globally - -**PR #275** (thread 77) — The Copilot instructions initially told the AI to run `dotnet format`. - -> "I wouldn't tell it to `dotnet-format`, or it's going to make 100s of changes across the repo!" - -**Why it matters:** An AI following "run dotnet format" will reformat every file in the repository, creating massive, unrelated diffs that obscure the actual changes and cause merge conflicts. Format only the files you're changing. - ---- - -## 30. Guide AI with `copilot-instructions.md` for netstandard2.0 awareness - -**PR #275** (thread 94) — Rather than listing specific API incompatibilities, Jonathan suggested a general instruction. - -> "Maybe this should just say: 'many modern .NET APIs are unavailable or have fewer overloads on `netstandard2.0`. When unsure about API availability, search mslearn to check documentation for the target framework.'" - -**Why it matters:** You can't enumerate every API difference between `net10.0` and `netstandard2.0`. Teaching the AI *how to check* (search MS Learn) is more durable than giving it a static list that will inevitably go stale. - ---- - -## 31. Simplify code — merge lists, short-circuit with LINQ - -**PR #275** (thread 93) — Package-list parsing created two separate lists and used `AddRange()`. - -> "This creates two lists and AddRange() one to the other. It's already using System.Linq (slowish), but could it just create 1 list? That would probably make it 'good enough'." - -**Why it matters:** Unnecessary intermediate collections waste memory and make the code harder to follow. Even when absolute performance isn't critical, simpler code is easier to review and less likely to hide bugs. - ---- - -## 32. Write thorough tests — especially for parsing and utilities - -**PR #274** (commit `6e4d1174`: "MOAR Tests!", commit `7c85bd7c`) — Jonathan personally wrote 437 lines of tests across `DownloadUtilsTests.cs`, `FileUtilTests.cs`, `JdkVersionInfoTests.cs`, and `ProcessUtilsTests.cs`. - -**Why it matters:** The AI-generated code came with some tests, but Jonathan significantly expanded coverage — particularly for parsing logic (`ParseChecksumFile`), file utilities (`MoveWithRollback`, `IsUnderDirectory`), and process argument construction. These are exactly the kind of functions where edge cases hide. - ---- - -## 33. Stdin write-then-delay ordering matters - -**PR #275** (threads 90–91) — The license acceptance code had a 500ms initial delay before sending input to `sdkmanager --licenses`, then a 200ms delay between subsequent writes. - -> "Why delay 500ms? Can we remove this line and it sends `n` every 200ms?" -> -> "Seems like we could just put the delay first and remove the 500ms above." - -**Why it matters:** Process stdin interaction is timing-sensitive. Writing before the process is ready for input loses the write; delaying too long slows down the operation. The correct pattern is delay-then-write (wait for the process to be ready, then send), not write-then-delay. - ---- - -## 34. Zip Slip protection — validate archive entry paths - -**PR #274** (Copilot reviewer + Jonathan's guidance) — The original code used `ZipFile.ExtractToDirectory()` which doesn't validate entry paths. A malicious archive could contain entries like `../../etc/passwd` that escape the target directory. This was replaced with entry-by-entry extraction that validates each path resolves under the destination. - -**Why it matters:** Zip Slip is a well-known archive extraction vulnerability ([CVE-2018-1002200](https://snyk.io/research/zip-slip-vulnerability)). Libraries that extract archives from the internet (JDK downloads, SDK packages) are high-value targets. Always validate that `Path.GetFullPath(entryPath)` starts with the destination directory. - ---- - -## 35. Checksum verification must be mandatory, not optional - -**PR #274** (Copilot reviewer, threads 26, 37) — The original code proceeded with installation even if the checksum fetch failed, silently skipping verification. - -> Copilot: "InstallAsync proceeds without checksum verification if the checksum fetch fails. This weakens the supply-chain guarantees." - -This was fixed to throw `InvalidOperationException` when checksum fetch fails, making verification mandatory for both archive extraction and elevated platform-installer paths. - -**Why it matters:** Optional checksum verification is the same as no checksum verification — an attacker who can interfere with the checksum URL gets a free pass. For supply-chain security, the download must fail if integrity cannot be confirmed. - ---- - -## 36. `Directory.Move` fails across volumes — extract near the target - -**PR #274** (Copilot reviewer, thread 25) — The code extracted archives to `Path.GetTempPath()` then used `Directory.Move()` to the target. On systems where `/tmp` is a different filesystem (common on Linux with tmpfs), this fails. - -> Copilot: "Directory.Move fails across volumes/filesystems, so installs will reliably fail when targetPath is on a different drive/mount than the temp directory." - -The fix was to create the temp extraction directory under the same parent as the target path. - -**Why it matters:** This is a classic cross-platform pitfall. `Directory.Move` is really a rename syscall, which only works within the same filesystem. Extracting near the target ensures same-filesystem semantics. PR #275 additionally added a recursive-copy fallback for robustness. - ---- - -## 37. Don't swallow `OperationCanceledException` in catch-all blocks - -**PR #274** (Copilot reviewer, threads 34–35) — `DiscoverAsync` and `FetchChecksumAsync` caught all exceptions, including `OperationCanceledException`, converting cancellation into empty results or null checksums instead of properly propagating it. - -> Copilot: "`DiscoverAsync` catches all exceptions inside the per-version loop, which will also swallow `OperationCanceledException`... That breaks expected cancellation semantics." - -The fix was to explicitly catch and rethrow `OperationCanceledException` before the general `catch (Exception)` block. - -**Why it matters:** Callers who pass a `CancellationToken` expect cancellation to propagate as `OperationCanceledException` or `TaskCanceledException`. Swallowing it means the caller gets wrong results (empty list, null checksum) instead of a proper cancellation signal. This breaks `async`/`await` contracts. - ---- - -## 38. Rollback on extraction failure — don't delete the backup too early - -**PR #274** (Copilot reviewer, threads 15, 21, 36) — The original code deleted the backup of the previous JDK immediately after moving the new one into place. If validation or a later step failed, the user was left with no working JDK. - -> Copilot: "If InstallAsync later fails validation (or any post-extraction step throws), the previous JDK at targetPath has already been permanently deleted, leaving the user with no working JDK." - -The fix was to keep the backup until after validation succeeds, with a `CommitMove` step that only deletes the backup on confirmed success. - -**Why it matters:** Safe replacement of installed software requires a two-phase commit: (1) move new into place, (2) validate, (3) delete backup only on success. Deleting the backup before validation turns a recoverable failure into data loss. - ---- - -## 39. Command injection in elevated `.cmd` scripts - -**PR #275** (Copilot reviewer, thread 67) — The elevated execution path on Windows wrote a `.cmd` script that interpolated package arguments directly into a command line run under `cmd.exe`. Characters like `&`, `|`, `>` in package names could escape the command. - -> Copilot: "The elevated path writes a .cmd script that interpolates `arguments` directly into a command line. Because this runs under `cmd.exe`, special characters... can lead to command injection, which is especially risky when running elevated." - -The fix added `SanitizeCmdArgument()` plus argument validation that rejects dangerous characters. - -**Why it matters:** Any code that generates shell commands from variable inputs is a command injection risk. This is doubly dangerous when the command runs elevated (admin/root). Always sanitize or use `ArgumentList` (which bypasses the shell entirely). - ---- - -## 40. Copilot reviewer was wrong about `ANDROID_HOME` vs `ANDROID_SDK_ROOT` - -**PR #275** (thread 42) — The Copilot reviewer incorrectly stated that `ANDROID_SDK_ROOT` is the recommended variable and `ANDROID_HOME` is deprecated — the exact opposite of the truth per [Android documentation](https://developer.android.com/tools/variables#envar). - -> Copilot: "These docs describe `ANDROID_HOME` as the preferred variable and `ANDROID_SDK_ROOT` as the older one, but Android tooling guidance is the opposite." -> -> @rmarinho: "Copilot is wrong here right?" -> -> @jonathanpeppers: Added guidance to `copilot-instructions.md` to prevent this confusion. - -**Why it matters:** AI reviewers can be confidently wrong about domain-specific facts. In this case, the Copilot reviewer would have introduced a regression by swapping to a deprecated variable. Human reviewers must verify AI claims against authoritative sources, especially for environment/configuration decisions. - ---- - -## 41. Network tests are acceptable — don't over-mock - -**PR #275** (thread 44) — The Copilot reviewer suggested replacing network-calling tests with mocked/faked alternatives for CI reliability. - -> Copilot: "This test performs a real network call to the manifest feed... Prefer a unit test that injects/fakes the HTTP response." -> -> @jonathanpeppers: "Network calls are fine in these tests." - -**Why it matters:** Not every Copilot suggestion should be accepted. Integration tests that hit real endpoints catch real problems (API changes, format changes, certificate issues) that mocks never will. The tests already use `Assert.Ignore` on network failure for CI resilience. Over-mocking creates tests that pass but don't actually verify the system works. - ---- - -## 42. Don't reinvent existing infrastructure — AI ignores what's already there - -**PRs #281–284** — The AI created a brand-new `AndroidToolRunner` class that wrapped `System.Diagnostics.Process` with timeout, cancellation, and output capture — duplicating everything `ProcessUtils` already provided. Jonathan left the same comment on PRs #282, #283, and #284: - -> "This is inventing lots of new code that just wraps System.Diagnostics.Process. Can we just use the existing code for this instead? ProcessUtils.cs. This is adding lots of more lines of code to maintain, and it's like the AI didn't even look at what is already here." - -**Why it matters:** This is the single most expensive AI code review pattern: the AI generates hundreds of lines of plausible-looking code that duplicates existing functionality. It compiles, it passes tests, but it creates a parallel maintenance burden and diverges from established patterns. Reviewers must check whether existing utilities already solve the problem before approving new infrastructure. - ---- - -## 43. Port code from downstream consumers — don't rewrite - -**PRs #283, #284** — Jonathan asked to port the device-listing logic from `dotnet/android`'s `GetAvailableAndroidDevices` MSBuild task and the emulator boot logic from `BootAndroidEmulator`, rather than writing new implementations. - -> (PR #283): "Open a draft dotnet/android PR that updates its submodule to the branch of #283... Review/merge dotnet/android-tools. Bring dotnet/android out of draft, switch to dotnet/android-tools/main. Merge it second." - -**Why it matters:** The downstream consumer (`dotnet/android`) already has battle-tested parsing and boot logic with real-world edge cases handled. Rewriting it means losing those edge cases and creating two implementations that can diverge. Porting preserves the institutional knowledge embedded in the existing code. - ---- - -## 44. Prove code sharing with a draft downstream PR before merging - -**PRs #283, #284** — Jonathan required a specific merge workflow: (1) create the shared library code in `android-tools`, (2) open a draft PR in `dotnet/android` that uses the new APIs via the submodule, (3) merge `android-tools` first, (4) update the submodule pointer and merge `dotnet/android` second. - -**Why it matters:** A shared library that isn't actually consumed by its intended consumer is speculative code. The draft downstream PR proves the API surface actually works, catches design mismatches early, and demonstrates that the shared code reduces (not increases) total code. It also makes the review concrete — the reviewer can see both sides of the change. - ---- - -## 45. Check exit codes consistently across all operations - -**PR #283** (Copilot reviewer, thread 27) — `StopEmulatorAsync` didn't check the adb exit code, while `ListDevicesAsync` and `WaitForDeviceAsync` did. This inconsistency meant stop failures were silent. - -> Copilot: "`StopEmulatorAsync` doesn't check the adb exit code, so it can succeed silently even if `adb -s emu kill` fails." - -Fixed by adding `ThrowIfFailed` consistently. - -**Why it matters:** Inconsistent error handling is worse than no error handling — it creates a false sense of safety. If some operations check exit codes and others don't, developers assume all operations are checked. Apply the same error-handling pattern (like `ThrowIfFailed`) to every process invocation. - ---- - -## 46. New helper methods should default to `internal` - -**PR #283** (Copilot reviewer, thread 29) — `ProcessUtils.ThrowIfFailed` was added as `public` even though it was only used internally. - -> Copilot: "`ThrowIfFailed` is introduced as a new `public` API on `ProcessUtils`, but it's only used internally in this PR. Given the repo convention to keep the public API minimal, consider making this `internal`." - -**Why it matters:** Every `public` method is a compatibility contract. Once external consumers depend on it, you can't change the signature without a breaking change. Default to `internal` and promote to `public` only when a confirmed external consumer needs it. This repo uses `InternalsVisibleTo` for test access. - ---- - -## 47. Return `IReadOnlyList`, not `List`, from public APIs - -**PR #284** (Copilot reviewer) — Public methods returned `Task>`, exposing a mutable concrete collection. - -> Copilot: "Public API returns `Task>`, which exposes a mutable concrete collection. Consider returning `Task>` to avoid leaking mutability." - -**Why it matters:** Returning `List` lets callers mutate the collection, potentially corrupting internal state if the list is cached. `IReadOnlyList` communicates intent ("you can read this, not change it") and allows the implementation to switch to arrays, immutable lists, or other backing stores without breaking callers. - ---- - -## 48. Don't redirect stdout/stderr on background processes without draining - -**PR #284** (Copilot reviewer) — `StartBackground` set `RedirectStandardOutput = true` and `RedirectStandardError = true` but never started async readers to drain the output. - -> Copilot: "`StartBackground` sets `RedirectStandardOutput/RedirectStandardError = true` but does not start any readers. This can cause the emulator process to block once stdout/stderr buffers fill up." - -**Why it matters:** Redirected output goes to an OS pipe buffer (typically 4-64 KB). When the buffer fills, the child process blocks on its next write. For long-running processes like the Android emulator, this means the process silently hangs. Either don't redirect (set to `false`) or immediately start async readers. - ---- - -## 49. `CancellationToken` accepted but never observed is a broken contract - -**PRs #281, #284** (Copilot reviewer) — `AndroidToolRunner.Run()` accepted a `CancellationToken` parameter but never checked it or passed it to any downstream operation. - -> Copilot: "`Run` method accepts a `cancellationToken` parameter but never uses it. The token should be checked before starting the process and should trigger process termination when cancelled." - -**Why it matters:** An unused `CancellationToken` parameter is an API lie. Callers write `await runner.Run(..., cts.Token)` expecting cancellation to work, then wonder why the process keeps running after they cancel. Either honor the token (register a callback to kill the process) or don't accept it. - ---- - -## 50. Take structured argument lists, not interpolated strings - -**PR #284** (Copilot reviewer) — `StartAvd` built a single argument string by interpolating `avdName` and appending `additionalArgs` verbatim, risking argument injection. - -> Copilot: "If `avdName` contains quotes/whitespace (or `additionalArgs` comes from untrusted input), this can break parsing or allow argument injection. Consider... taking additional args as a structured list (`IEnumerable`)." - -**Why it matters:** String interpolation into process arguments is the process-launch equivalent of SQL injection. Use `ProcessUtils.CreateProcessStartInfo()` which populates `ArgumentList` (safe, no shell parsing) on net5+ and falls back to proper escaping on netstandard2.0. Accept `IEnumerable` instead of `string` for additional arguments. - ---- - -## 51. Doc comments must match actual behavior - -**PR #283** (Copilot reviewer, thread 28) — A file-level doc comment said "bootstraps cmdline-tools" but the code only bootstrapped when `ANDROID_HOME` was missing — it never checked for cmdline-tools specifically. - -> Copilot: "The file-level doc comment says 'When the pre-installed SDK lacks cmdline-tools, the tests bootstrap them', but `OneTimeSetUp` currently only bootstraps when `ANDROID_HOME` is missing/invalid." - -**Why it matters:** Inaccurate doc comments are worse than no doc comments — they actively mislead. When AI generates both code and documentation, the docs often describe the *intended* behavior rather than the *actual* behavior. Review doc comments against the code, not against the PR description. - ---- - -## 52. Never use the null-forgiving operator (`!`) - -**PR #283** (review 3892527868, `AdbRunner.cs:195` and `AndroidEnvironmentHelper.cs:20`) — Code had parameters typed as nullable but then used `!` to suppress warnings at call sites. Jonathan flagged both spots. - -> "We should avoid using `!`, do you even need it if you use our `IsNullOrEmpty()` extension method?" - -> "Why are the paths allowed to be null? Then we have `!` below? Can we make sure `copilot-instructions.md` says to never use `!` and to actually fix nullable warnings correctly?" - -**Why it matters:** The null-forgiving operator `!` silences the compiler without fixing the bug. If a value can actually be null at runtime, `!` turns a compile-time warning into a `NullReferenceException`. The correct fix is to either make the parameter non-nullable (if null is never valid) or add a proper null check. AI frequently sprinkles `!` to make warnings disappear. - ---- - -## 53. Add overloads to reduce caller ceremony - -**PR #283** (review 3892527868, `AdbRunner.cs:207`) — Multiple callers were calling `writer.ToString()` before passing to `ThrowIfFailed()`. Jonathan suggested adding a `StringWriter` overload instead. - -> "Should `ThrowIfFailed()` just have an overload for `StringWriter` and you wouldn't have to call `ToString()` from callers?" - -**Why it matters:** When every caller has to perform the same conversion before calling a method, the conversion belongs inside the method (or an overload). This reduces boilerplate, eliminates a class of copy-paste errors, and makes the API easier to use correctly. - ---- - -## 54. Delete unused overloads - -**PR #283** (review 3892527868, `AdbRunner.cs:233`) — An overload existed but wasn't needed. - -> "Can we delete this overload?" - -**Why it matters:** Dead code is a maintenance burden and a trap for readers who assume it exists for a reason. AI tends to generate speculative overloads "for completeness" — review each public method and ask whether any caller actually needs it. - ---- - -## 55. Prefer C# pattern matching - -**PR #283** (review 3892527868, `AdbRunner.cs:312`) — Code used traditional `if`/`else` chains to check types or values. Jonathan asked for pattern matching and to update `copilot-instructions.md`. - -> "Can this use C# pattern matching, and update `copilot-instructions.md` to do this for all new code?" - -**Why it matters:** Pattern matching (`is`, `switch` expressions, property patterns) is more concise, avoids unnecessary casts, and makes exhaustiveness checks possible. Encoding this in `copilot-instructions.md` prevents AI from generating old-style conditional chains. - ---- - -## 56. Section-separator comments are `#region` in disguise - -**PR #283** (review 3892527868, `RunnerIntegrationTests.cs:151`) — Test files had block comments acting as visual section dividers between groups of tests. - -> "It doesn't seem like we should be writing comments like this, they are basically `#region` and `#endregion`" - -**Why it matters:** This extends the `#region` ban (Finding #12). Any mechanism to partition code into visual "sections" — whether `#region`, banner comments, or ASCII art dividers — signals the file should be split or the test organization rethought. AI loves generating these as pseudo-structure. - ---- - -## Summary of Themes - -| Theme | Occurrences | Key Lesson | -|-------|------------|------------| -| **AI reinvents the wheel** | AndroidToolRunner vs ProcessUtils, rewriting downstream logic | Check existing code FIRST — AI doesn't look at what's already there | -| **AI over-engineers** | HttpClient injection, IsElevated, speculative code, unused overloads | Remove code until you need it (YAGNI) | -| **AI ignores target framework** | netstandard2.0, new lang features | Always check API availability against the lowest TFM | -| **AI swallows errors** | Empty catch blocks, chmod failure, checksumType, OperationCanceledException, unchecked exit codes | Fail fast with clear errors; apply error handling consistently | -| **AI generates sloppy structure** | One type per file, #region, section-separator comments, empty lines, naming, `public` when `internal` suffices | Encode conventions in copilot-instructions.md | -| **AI fights the type system** | Null-forgiving `!`, nullable params that shouldn't be, `!` to silence warnings | Fix nullability at the source; never use `!` | -| **API design** | `List` vs `IReadOnlyList`, unused CancellationToken, string args vs structured args, missing overloads | Public APIs are contracts — get them right the first time | -| **Modern C# idioms** | Pattern matching, records, file-scoped namespaces | Use modern syntax when available on the target framework | -| **Performance awareness** | ArrayPool, XmlReader, p/invoke, list merging, cached arrays | Small allocations add up in library code | -| **Security & correctness** | Zip Slip, command injection, path traversal, mandatory checksums, license consent | Libraries must be correct by default | -| **AI reviewer can be wrong** | ANDROID_HOME vs ANDROID_SDK_ROOT, over-mocking | Always verify AI claims against authoritative docs | -| **AI docs diverge from code** | Doc comments describing intended not actual behavior | Review docs against the code, not the PR description | -| **Rollback & resilience** | Cross-volume moves, backup-before-replace, cross-device fallback, stdout buffer deadlocks | Assume failure at every step; design for recovery | -| **Code sharing workflow** | Port from downstream, draft PR before merge, submodule coordination | Prove shared code works with its consumer before merging | -| **Reviewer wrote code too** | 11 fix-up commits on PR #274, 437 lines of new tests | Good review includes hands-on fixes, not just comments | diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Models/Sdk/SdkBootstrapPhase.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Models/Sdk/SdkBootstrapPhase.cs index 1ad780faff7..d2a1f0195b0 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Models/Sdk/SdkBootstrapPhase.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Models/Sdk/SdkBootstrapPhase.cs @@ -1,21 +1,21 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace Xamarin.Android.Tools; - -/// -/// Phases of the SDK bootstrap operation. -/// -public enum SdkBootstrapPhase -{ - /// Reading the manifest feed. - ReadingManifest, - /// Downloading the command-line tools archive. - Downloading, - /// Verifying the downloaded archive checksum. - Verifying, - /// Extracting the archive. - Extracting, - /// Bootstrap completed successfully. - Complete -} +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Xamarin.Android.Tools; + +/// +/// Phases of the SDK bootstrap operation. +/// +public enum SdkBootstrapPhase +{ + /// Reading the manifest feed. + ReadingManifest, + /// Downloading the command-line tools archive. + Downloading, + /// Verifying the downloaded archive checksum. + Verifying, + /// Extracting the archive. + Extracting, + /// Bootstrap completed successfully. + Complete +} diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Models/Sdk/SdkBootstrapProgress.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Models/Sdk/SdkBootstrapProgress.cs index 6771ed9dc48..3fe59a4d4b5 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Models/Sdk/SdkBootstrapProgress.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Models/Sdk/SdkBootstrapProgress.cs @@ -1,7 +1,7 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace Xamarin.Android.Tools; - -/// Progress information for SDK bootstrap operations. -public record SdkBootstrapProgress (SdkBootstrapPhase Phase, int PercentComplete = -1, string Message = ""); +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Xamarin.Android.Tools; + +/// Progress information for SDK bootstrap operations. +public record SdkBootstrapProgress (SdkBootstrapPhase Phase, int PercentComplete = -1, string Message = ""); diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Models/Sdk/SdkLicense.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Models/Sdk/SdkLicense.cs index 3ec5064cae9..7519c4822ff 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Models/Sdk/SdkLicense.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Models/Sdk/SdkLicense.cs @@ -1,6 +1,6 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace Xamarin.Android.Tools; - -public record SdkLicense (string Id, string Text); +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Xamarin.Android.Tools; + +public record SdkLicense (string Id, string Text); diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Models/Sdk/SdkManifestComponent.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Models/Sdk/SdkManifestComponent.cs index 04fece9131c..9fc5892bbb6 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Models/Sdk/SdkManifestComponent.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Models/Sdk/SdkManifestComponent.cs @@ -1,18 +1,18 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace Xamarin.Android.Tools; - -internal record SdkManifestComponent -{ - public string ElementName { get; set; } = ""; - public string Revision { get; set; } = ""; - public string? Path { get; set; } - public string? FilesystemPath { get; set; } - public string? Description { get; set; } - public string? DownloadUrl { get; set; } - public long Size { get; set; } - public string? Checksum { get; set; } - public ChecksumType ChecksumType { get; set; } = ChecksumType.Sha1; - public bool IsObsolete { get; set; } -} +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Xamarin.Android.Tools; + +internal record SdkManifestComponent +{ + public string ElementName { get; set; } = ""; + public string Revision { get; set; } = ""; + public string? Path { get; set; } + public string? FilesystemPath { get; set; } + public string? Description { get; set; } + public string? DownloadUrl { get; set; } + public long Size { get; set; } + public string? Checksum { get; set; } + public ChecksumType ChecksumType { get; set; } = ChecksumType.Sha1; + public bool IsObsolete { get; set; } +} diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Models/Sdk/SdkPackage.cs b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Models/Sdk/SdkPackage.cs index d9beee796ab..01fd5c63730 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Models/Sdk/SdkPackage.cs +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Models/Sdk/SdkPackage.cs @@ -1,8 +1,8 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace Xamarin.Android.Tools; - -/// Information about an SDK package as reported by the sdkmanager CLI. -public record SdkPackage (string Path, string? Version = null, string? Description = null, bool IsInstalled = false); - +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Xamarin.Android.Tools; + +/// Information about an SDK package as reported by the sdkmanager CLI. +public record SdkPackage (string Path, string? Version = null, string? Description = null, bool IsInstalled = false); + diff --git a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj index e46a29c0143..47f817434a0 100644 --- a/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj +++ b/external/xamarin-android-tools/src/Xamarin.Android.Tools.AndroidSdk/Xamarin.Android.Tools.AndroidSdk.csproj @@ -1,75 +1,75 @@ - - - - netstandard2.0 - $(TargetFrameworks);$(DotNetTargetFramework) - latest - enable - INTERNAL_NULLABLE_ATTRIBUTES - true - ..\..\product.snk - Xamarin.Android.Tools - Xamarin - MIT - https://github.com/dotnet/android-tools - Xamarin tools for interacting with the Android SDK. - Copyright © Xamarin 2011-2016 - Xamarin;Xamarin.Android - $(VendorPrefix)Xamarin.Android.Tools.AndroidSdk$(VendorSuffix) - - - - $(ToolOutputFullPath) - - - - true - true - - - - - - - - - - all - runtime; build; native; contentfiles; analyzers - - - - - - - - - - - - - - - Microsoft400 - - - Microsoft400 - - - - - - - True - True - Resources.resx - - - - - - ResXFileCodeGenerator - Resources.Designer.cs - - - + + + + netstandard2.0 + $(TargetFrameworks);$(DotNetTargetFramework) + latest + enable + INTERNAL_NULLABLE_ATTRIBUTES + true + ..\..\product.snk + Xamarin.Android.Tools + Xamarin + MIT + https://github.com/dotnet/android-tools + Xamarin tools for interacting with the Android SDK. + Copyright © Xamarin 2011-2016 + Xamarin;Xamarin.Android + $(VendorPrefix)Xamarin.Android.Tools.AndroidSdk$(VendorSuffix) + + + + $(ToolOutputFullPath) + + + + true + true + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers + + + + + + + + + + + + + + + Microsoft400 + + + Microsoft400 + + + + + + + True + True + Resources.resx + + + + + + ResXFileCodeGenerator + Resources.Designer.cs + + + diff --git a/external/xamarin-android-tools/tools/ls-jdks/App.cs b/external/xamarin-android-tools/tools/ls-jdks/App.cs index 0eefbb1c580..ebcc5d5bed2 100644 --- a/external/xamarin-android-tools/tools/ls-jdks/App.cs +++ b/external/xamarin-android-tools/tools/ls-jdks/App.cs @@ -1,40 +1,40 @@ -using System; - -namespace Xamarin.Android.Tools -{ - class App - { - static void Main(string[] args) - { - foreach (var path in args) { - PrintProperties (path); - } - if (args.Length != 0) - return; - foreach (var jdk in JdkInfo.GetKnownSystemJdkInfos ()) { - Console.WriteLine ($"Found JDK: {jdk.HomePath}"); - Console.WriteLine ($" Locator: {jdk.Locator}"); - // Force parsing of java properties. - var keys = jdk.JavaSettingsPropertyKeys; - } - } - - static void PrintProperties (string jdkPath) - { - try { - var jdk = new JdkInfo (jdkPath, "ls-jdks"); - Console.WriteLine ($"Property settings for JDK Path: {jdk.HomePath}"); - foreach (var key in jdk.JavaSettingsPropertyKeys) { - if (!jdk.GetJavaSettingsPropertyValues (key, out var v)) { - Console.Error.WriteLine ($"ls-jdks: Could not retrieve value for key {key}."); - continue; - } - Console.WriteLine ($" {key} = {string.Join (Environment.NewLine + " ", v)}"); - } - } - catch (Exception e) { - Console.Error.WriteLine (e); - } - } - } -} +using System; + +namespace Xamarin.Android.Tools +{ + class App + { + static void Main(string[] args) + { + foreach (var path in args) { + PrintProperties (path); + } + if (args.Length != 0) + return; + foreach (var jdk in JdkInfo.GetKnownSystemJdkInfos ()) { + Console.WriteLine ($"Found JDK: {jdk.HomePath}"); + Console.WriteLine ($" Locator: {jdk.Locator}"); + // Force parsing of java properties. + var keys = jdk.JavaSettingsPropertyKeys; + } + } + + static void PrintProperties (string jdkPath) + { + try { + var jdk = new JdkInfo (jdkPath, "ls-jdks"); + Console.WriteLine ($"Property settings for JDK Path: {jdk.HomePath}"); + foreach (var key in jdk.JavaSettingsPropertyKeys) { + if (!jdk.GetJavaSettingsPropertyValues (key, out var v)) { + Console.Error.WriteLine ($"ls-jdks: Could not retrieve value for key {key}."); + continue; + } + Console.WriteLine ($" {key} = {string.Join (Environment.NewLine + " ", v)}"); + } + } + catch (Exception e) { + Console.Error.WriteLine (e); + } + } + } +} diff --git a/external/xamarin-android-tools/tools/ls-jdks/ls-jdks.csproj b/external/xamarin-android-tools/tools/ls-jdks/ls-jdks.csproj index b87bfa7a480..523b2fd0c78 100644 --- a/external/xamarin-android-tools/tools/ls-jdks/ls-jdks.csproj +++ b/external/xamarin-android-tools/tools/ls-jdks/ls-jdks.csproj @@ -1,15 +1,15 @@ - - - - Exe - $(DotNetTargetFramework) - Xamarin.Android.Tools - false - $(ToolOutputFullPath) - - - - - - - + + + + Exe + $(DotNetTargetFramework) + Xamarin.Android.Tools + false + $(ToolOutputFullPath) + + + + + + + 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 6c562f5378c..db484eb0660 100644 --- a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.targets +++ b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Build.Tasks.targets @@ -137,7 +137,7 @@ + Replacements="@JAVA_INTEROP_COMMIT@=in-tree;@SQLITE_COMMIT@=$(_BuildInfo_SqliteCommit);@XAMARIN_ANDROID_TOOLS_COMMIT@=in-tree;"> <_XACommonPropsReplacement Include="@COMMAND_LINE_TOOLS_VERSION@=$(CommandLineToolsFolder)" />