From 1ab86cccf2b421c296770055decd26d7b7e2f35b Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 6 Mar 2026 21:22:23 +0000
Subject: [PATCH 1/3] Initial plan
From 5256daa70a814297e59253cd693c4ed52fa2c2c2 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 6 Mar 2026 21:28:06 +0000
Subject: [PATCH 2/3] Fix XcodeVersion not populated when Xcode discovered via
xcode-select
Co-authored-by: emaf <3286258+emaf@users.noreply.github.com>
---
Xamarin.MacDev/XcodeLocator.cs | 2 +-
tests/XcodeLocatorTests.cs | 153 +++++++++++++++++++++++++++++++++
2 files changed, 154 insertions(+), 1 deletion(-)
create mode 100644 tests/XcodeLocatorTests.cs
diff --git a/Xamarin.MacDev/XcodeLocator.cs b/Xamarin.MacDev/XcodeLocator.cs
index ec4ab66..0c68fe5 100644
--- a/Xamarin.MacDev/XcodeLocator.cs
+++ b/Xamarin.MacDev/XcodeLocator.cs
@@ -125,7 +125,7 @@ public bool TryLocatingXcode (string? xcodeLocationOverride)
}
// 3. Not optional
- if (TryGetSystemXcode (log, out location)) {
+ if (TryGetSystemXcode (log, out var systemXcodePath) && TryLocatingSpecificXcode (systemXcodePath, out location)) {
log.LogInfo ($"Found a valid Xcode from the system settings ('xcode-select -p').");
XcodeLocation = location;
return true;
diff --git a/tests/XcodeLocatorTests.cs b/tests/XcodeLocatorTests.cs
new file mode 100644
index 0000000..09dab57
--- /dev/null
+++ b/tests/XcodeLocatorTests.cs
@@ -0,0 +1,153 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+#nullable enable
+
+using System;
+using System.IO;
+
+using NUnit.Framework;
+
+using Xamarin.MacDev;
+
+namespace Tests {
+
+ [TestFixture]
+ public class XcodeLocatorTests {
+
+ static string CreateFakeXcodeBundle (string version = "16.2", string dtXcode = "1620", string? cfBundleVersion = null)
+ {
+ var dir = Path.Combine (Path.GetTempPath (), Path.GetRandomFileName () + ".app");
+ var contentsDir = Path.Combine (dir, "Contents");
+ Directory.CreateDirectory (contentsDir);
+
+ var buildVersion = cfBundleVersion ?? "16C5032a";
+
+ File.WriteAllText (Path.Combine (contentsDir, "version.plist"), $@"
+
+
+
+ CFBundleShortVersionString
+ {version}
+ CFBundleVersion
+ {buildVersion}
+
+
+");
+
+ File.WriteAllText (Path.Combine (contentsDir, "Info.plist"), $@"
+
+
+
+ DTXcode
+ {dtXcode}
+
+
+");
+
+ return dir;
+ }
+
+ [Test]
+ public void TryLocatingXcode_Override_PopulatesXcodeVersion ()
+ {
+ var fakeXcode = CreateFakeXcodeBundle (version: "16.2", dtXcode: "1620");
+ try {
+ var locator = new XcodeLocator (ConsoleLogger.Instance);
+ var found = locator.TryLocatingXcode (fakeXcode);
+ Assert.That (found, Is.True, "TryLocatingXcode should return true for a valid Xcode bundle.");
+ Assert.That (locator.XcodeVersion, Is.EqualTo (new Version (16, 2)), "XcodeVersion should be populated.");
+ Assert.That (locator.DTXcode, Is.EqualTo ("1620"), "DTXcode should be populated.");
+ Assert.That (locator.XcodeLocation, Is.EqualTo (fakeXcode), "XcodeLocation should be set.");
+ } finally {
+ Directory.Delete (fakeXcode, recursive: true);
+ }
+ }
+
+ [Test]
+ public void TryLocatingXcode_Override_WithContentsDeveloper_PopulatesXcodeVersion ()
+ {
+ var fakeXcode = CreateFakeXcodeBundle (version: "26.2", dtXcode: "2620");
+ try {
+ var locator = new XcodeLocator (ConsoleLogger.Instance);
+ var pathWithDeveloper = Path.Combine (fakeXcode, "Contents", "Developer");
+ Directory.CreateDirectory (pathWithDeveloper);
+ var found = locator.TryLocatingXcode (pathWithDeveloper);
+ Assert.That (found, Is.True, "TryLocatingXcode should return true when path includes /Contents/Developer.");
+ Assert.That (locator.XcodeVersion, Is.EqualTo (new Version (26, 2)), "XcodeVersion should be populated.");
+ Assert.That (locator.DTXcode, Is.EqualTo ("2620"), "DTXcode should be populated.");
+ Assert.That (locator.XcodeLocation, Is.EqualTo (fakeXcode), "XcodeLocation should be canonicalized (no /Contents/Developer suffix).");
+ } finally {
+ Directory.Delete (fakeXcode, recursive: true);
+ }
+ }
+
+ [Test]
+ public void TryLocatingXcode_NullOverride_ReturnsFalseWhenNothingFound ()
+ {
+ // With SupportEnvironmentVariableLookup and SupportSettingsFileLookup both false
+ // and no xcode-select available (Linux CI), TryLocatingXcode should return false.
+ var locator = new XcodeLocator (ConsoleLogger.Instance) {
+ SupportEnvironmentVariableLookup = false,
+ SupportSettingsFileLookup = false,
+ };
+ // On Linux, xcode-select doesn't exist so TryGetSystemXcode returns false.
+ // We just verify it doesn't throw.
+ Assert.DoesNotThrow (() => locator.TryLocatingXcode (null));
+ }
+
+ [Test]
+ public void TryLocatingXcode_Override_MissingVersionPlist_ReturnsFalse ()
+ {
+ var dir = Path.Combine (Path.GetTempPath (), Path.GetRandomFileName () + ".app");
+ var contentsDir = Path.Combine (dir, "Contents");
+ Directory.CreateDirectory (contentsDir);
+ // No version.plist created.
+ File.WriteAllText (Path.Combine (contentsDir, "Info.plist"), @"
+
+DTXcode1620
+");
+ try {
+ var locator = new XcodeLocator (ConsoleLogger.Instance);
+ var found = locator.TryLocatingXcode (dir);
+ Assert.That (found, Is.False, "TryLocatingXcode should return false when version.plist is missing.");
+ Assert.That (locator.XcodeVersion, Is.EqualTo (new Version (0, 0, 0)), "XcodeVersion should remain at default.");
+ } finally {
+ Directory.Delete (dir, recursive: true);
+ }
+ }
+
+ [Test]
+ public void TryLocatingXcode_Override_MissingInfoPlist_ReturnsFalse ()
+ {
+ var dir = Path.Combine (Path.GetTempPath (), Path.GetRandomFileName () + ".app");
+ var contentsDir = Path.Combine (dir, "Contents");
+ Directory.CreateDirectory (contentsDir);
+ File.WriteAllText (Path.Combine (contentsDir, "version.plist"), @"
+
+CFBundleShortVersionString16.2CFBundleVersion16C5032a
+");
+ // No Info.plist created.
+ try {
+ var locator = new XcodeLocator (ConsoleLogger.Instance);
+ var found = locator.TryLocatingXcode (dir);
+ Assert.That (found, Is.False, "TryLocatingXcode should return false when Info.plist is missing.");
+ Assert.That (locator.XcodeVersion, Is.EqualTo (new Version (0, 0, 0)), "XcodeVersion should remain at default.");
+ } finally {
+ Directory.Delete (dir, recursive: true);
+ }
+ }
+
+ [Test]
+ public void TryGetSystemXcode_WhenXcodeSelectMissing_ReturnsFalse ()
+ {
+ // On Linux (CI), /usr/bin/xcode-select doesn't exist, so TryGetSystemXcode returns false.
+ if (File.Exists ("/usr/bin/xcode-select"))
+ Assert.Ignore ("This test only applies when xcode-select is not present.");
+
+ var result = XcodeLocator.TryGetSystemXcode (ConsoleLogger.Instance, out var path);
+ Assert.That (result, Is.False, "TryGetSystemXcode should return false when xcode-select is not installed.");
+ Assert.That (path, Is.Null, "path should be null when xcode-select is not installed.");
+ }
+ }
+}
From 862456f9bb38e0c8a53ce7767981a117ed3865b0 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Fri, 6 Mar 2026 22:02:04 +0000
Subject: [PATCH 3/3] Fix failing tests: skip on Windows, assert correct
invariant for broken-path tests
Co-authored-by: emaf <3286258+emaf@users.noreply.github.com>
---
tests/XcodeLocatorTests.cs | 19 +++++++++++--------
1 file changed, 11 insertions(+), 8 deletions(-)
diff --git a/tests/XcodeLocatorTests.cs b/tests/XcodeLocatorTests.cs
index 09dab57..893bdd0 100644
--- a/tests/XcodeLocatorTests.cs
+++ b/tests/XcodeLocatorTests.cs
@@ -13,6 +13,7 @@
namespace Tests {
[TestFixture]
+ [Platform (Exclude = "Win", Reason = "Xcode is a macOS-only tool; xcode-select and Xcode bundles are not valid on Windows.")]
public class XcodeLocatorTests {
static string CreateFakeXcodeBundle (string version = "16.2", string dtXcode = "1620", string? cfBundleVersion = null)
@@ -97,7 +98,7 @@ public void TryLocatingXcode_NullOverride_ReturnsFalseWhenNothingFound ()
}
[Test]
- public void TryLocatingXcode_Override_MissingVersionPlist_ReturnsFalse ()
+ public void TryLocatingXcode_Override_MissingVersionPlist_IgnoresBrokenPath ()
{
var dir = Path.Combine (Path.GetTempPath (), Path.GetRandomFileName () + ".app");
var contentsDir = Path.Combine (dir, "Contents");
@@ -109,16 +110,17 @@ public void TryLocatingXcode_Override_MissingVersionPlist_ReturnsFalse ()
");
try {
var locator = new XcodeLocator (ConsoleLogger.Instance);
- var found = locator.TryLocatingXcode (dir);
- Assert.That (found, Is.False, "TryLocatingXcode should return false when version.plist is missing.");
- Assert.That (locator.XcodeVersion, Is.EqualTo (new Version (0, 0, 0)), "XcodeVersion should remain at default.");
+ locator.TryLocatingXcode (dir);
+ // The broken path must never be accepted as the Xcode location,
+ // even if a fallback (xcode-select) finds a different valid Xcode.
+ Assert.That (locator.XcodeLocation, Is.Not.EqualTo (dir), "A bundle missing version.plist should not be set as XcodeLocation.");
} finally {
Directory.Delete (dir, recursive: true);
}
}
[Test]
- public void TryLocatingXcode_Override_MissingInfoPlist_ReturnsFalse ()
+ public void TryLocatingXcode_Override_MissingInfoPlist_IgnoresBrokenPath ()
{
var dir = Path.Combine (Path.GetTempPath (), Path.GetRandomFileName () + ".app");
var contentsDir = Path.Combine (dir, "Contents");
@@ -130,9 +132,10 @@ public void TryLocatingXcode_Override_MissingInfoPlist_ReturnsFalse ()
// No Info.plist created.
try {
var locator = new XcodeLocator (ConsoleLogger.Instance);
- var found = locator.TryLocatingXcode (dir);
- Assert.That (found, Is.False, "TryLocatingXcode should return false when Info.plist is missing.");
- Assert.That (locator.XcodeVersion, Is.EqualTo (new Version (0, 0, 0)), "XcodeVersion should remain at default.");
+ locator.TryLocatingXcode (dir);
+ // The broken path must never be accepted as the Xcode location,
+ // even if a fallback (xcode-select) finds a different valid Xcode.
+ Assert.That (locator.XcodeLocation, Is.Not.EqualTo (dir), "A bundle missing Info.plist should not be set as XcodeLocation.");
} finally {
Directory.Delete (dir, recursive: true);
}