diff --git a/UnitTests/TestHelper.cs b/UnitTests/TestHelper.cs new file mode 100644 index 0000000..52004a7 --- /dev/null +++ b/UnitTests/TestHelper.cs @@ -0,0 +1,61 @@ +// +// TestHelper.cs +// +// Author: Jeffrey Stedfast +// +// Copyright (c) 2017 Microsoft Corp. +// +// 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. + +using System; +using System.IO; + +using NUnit.Framework; + +namespace UnitTests { + [SetUpFixture] + static class TestHelper { + public static readonly string ProjectDir; + + static TestHelper () + { +#if NET5_0_OR_GREATER + var codeBase = typeof (TestHelper).Assembly.Location; +#else + var codeBase = typeof (TestHelper).Assembly.CodeBase; + if (codeBase.StartsWith ("file://", StringComparison.OrdinalIgnoreCase)) + codeBase = codeBase.Substring ("file://".Length); + + if (Path.DirectorySeparatorChar == '\\') { + if (codeBase [0] == '/') + codeBase = codeBase.Substring (1); + + codeBase = codeBase.Replace ('/', '\\'); + } +#endif + + var dir = Path.GetDirectoryName (codeBase); + + while (Path.GetFileName (dir) != "UnitTests") + dir = Path.GetFullPath (Path.Combine (dir, "..")); + + ProjectDir = Path.GetFullPath (dir); + } + } +} diff --git a/UnitTests/TestMobileProvisionIndex.cs b/UnitTests/TestMobileProvisionIndex.cs index 373d730..3d771d9 100644 --- a/UnitTests/TestMobileProvisionIndex.cs +++ b/UnitTests/TestMobileProvisionIndex.cs @@ -33,10 +33,19 @@ namespace UnitTests { [TestFixture] public class TestMobileProvisionIndex { + static readonly string [] ProfileDirectories; + + static TestMobileProvisionIndex () + { + ProfileDirectories = new string [] { + Path.Combine (TestHelper.ProjectDir, "TestData", "Provisioning Profiles") + }; + } + [Test] public void TestCreateIndex () { - var index = MobileProvisionIndex.CreateIndex ("../../TestData/Provisioning Profiles", "profiles.index"); + var index = MobileProvisionIndex.CreateIndex (ProfileDirectories, "profiles.index"); Assert.AreEqual (2, index.ProvisioningProfiles.Count); @@ -77,7 +86,7 @@ public void TestCreateIndex () [Test] public void TestOpenIndex () { - var index = MobileProvisionIndex.OpenIndex ("../../TestData/Provisioning Profiles", "profiles.index"); + var index = MobileProvisionIndex.OpenIndex (ProfileDirectories, "profiles.index"); Assert.AreEqual (2, index.ProvisioningProfiles.Count); diff --git a/Xamarin.MacDev/MobileProvision.cs b/Xamarin.MacDev/MobileProvision.cs index c45f689..b1b533b 100644 --- a/Xamarin.MacDev/MobileProvision.cs +++ b/Xamarin.MacDev/MobileProvision.cs @@ -50,21 +50,27 @@ public class MobileProvision { public const string AutomaticAppStore = "Automatic:AppStore"; public const string AutomaticInHouse = "Automatic:InHouse"; public const string AutomaticAdHoc = "Automatic:AdHoc"; - public static readonly string ProfileDirectory; + public static readonly string [] ProfileDirectories; static MobileProvision () { if (Environment.OSVersion.Platform == PlatformID.MacOSX || Environment.OSVersion.Platform == PlatformID.Unix) { string personal = Environment.GetFolderPath (Environment.SpecialFolder.UserProfile); - ProfileDirectory = Path.Combine (personal, "Library", "MobileDevice", "Provisioning Profiles"); + + ProfileDirectories = new string [] { + // Xcode >= 16.x uses ~/Library/Developer/Xcode/UserData/Provisioning Profiles + Path.Combine (personal, "Library", "Developer", "Xcode", "UserData", "Provisioning Profiles"), + + // Xcode < 16.x uses ~/Library/MobileDevice/Provisioning Profiles + Path.Combine (personal, "Library", "MobileDevice", "Provisioning Profiles"), + }; } else { - ProfileDirectory = Path.Combine ( - Environment.GetFolderPath (Environment.SpecialFolder.LocalApplicationData), - "Xamarin", - "iOS", - "Provisioning", - "Profiles"); + var appDataLocal = Environment.GetFolderPath (Environment.SpecialFolder.LocalApplicationData); + + ProfileDirectories = new string [] { + Path.Combine (appDataLocal, "Xamarin", "iOS", "Provisioning", "Profiles") + }; } } @@ -145,9 +151,6 @@ public static IList GetAllInstalledProvisions (MobileProvisionP /// public static IList GetAllInstalledProvisions (MobileProvisionPlatform platform, bool includeExpired) { - if (!Directory.Exists (ProfileDirectory)) - return new MobileProvision [0]; - var uuids = new Dictionary (); var list = new List (); var now = DateTime.Now; @@ -165,33 +168,38 @@ public static IList GetAllInstalledProvisions (MobileProvisionP throw new ArgumentOutOfRangeException (nameof (platform)); } - foreach (var file in Directory.EnumerateFiles (ProfileDirectory, pattern)) { - try { - var data = File.ReadAllBytes (file); - - var m = new MobileProvision (); - m.Load (PDictionary.FromBinaryXml (data)); - m.Data = data; - - if (includeExpired || m.ExpirationDate > now) { - if (uuids.ContainsKey (m.Uuid)) { - // we always want the most recently created/updated provision - if (m.CreationDate > uuids [m.Uuid].CreationDate) { - int index = list.IndexOf (uuids [m.Uuid]); - uuids [m.Uuid] = m; - list [index] = m; + foreach (var profileDir in ProfileDirectories) { + if (!Directory.Exists (profileDir)) + continue; + + foreach (var file in Directory.EnumerateFiles (profileDir, pattern)) { + try { + var data = File.ReadAllBytes (file); + + var m = new MobileProvision (); + m.Load (PDictionary.FromBinaryXml (data)); + m.Data = data; + + if (includeExpired || m.ExpirationDate > now) { + if (uuids.ContainsKey (m.Uuid)) { + // we always want the most recently created/updated provision + if (m.CreationDate > uuids [m.Uuid].CreationDate) { + int index = list.IndexOf (uuids [m.Uuid]); + uuids [m.Uuid] = m; + list [index] = m; + } + } else { + uuids.Add (m.Uuid, m); + list.Add (m); } - } else { - uuids.Add (m.Uuid, m); - list.Add (m); } + } catch (Exception ex) { + LoggingService.LogWarning ("Error reading " + platform + " provision file '" + file + "'", ex); } - } catch (Exception ex) { - LoggingService.LogWarning ("Error reading " + platform + " provision file '" + file + "'", ex); } } - //newest first + // newest first list.Sort ((x, y) => y.CreationDate.CompareTo (x.CreationDate)); return list; diff --git a/Xamarin.MacDev/MobileProvisionIndex.cs b/Xamarin.MacDev/MobileProvisionIndex.cs index 62f5bc3..cc8a306 100644 --- a/Xamarin.MacDev/MobileProvisionIndex.cs +++ b/Xamarin.MacDev/MobileProvisionIndex.cs @@ -263,102 +263,72 @@ public void Save (string fileName) } } - public static MobileProvisionIndex CreateIndex (string profilesDir, string indexName) + public static MobileProvisionIndex CreateIndex (string [] profileDirs, string indexName) { var index = new MobileProvisionIndex (); + var mtime = DateTime.MinValue; - if (Directory.Exists (profilesDir)) { - foreach (var fileName in Directory.EnumerateFiles (profilesDir)) { - if (!fileName.EndsWith (".mobileprovision", StringComparison.Ordinal) && !fileName.EndsWith (".provisionprofile", StringComparison.Ordinal)) - continue; + foreach (var profileDir in profileDirs) { + if (Directory.Exists (profileDir)) { + var mtime2 = Directory.GetLastWriteTimeUtc (profileDir); - try { - var profile = ProvisioningProfile.Load (fileName); - index.ProvisioningProfiles.Add (profile); - } catch (Exception ex) { - LoggingService.LogWarning ("Error reading provisioning profile '{0}': {1}", fileName, ex); + if (mtime2 > mtime) + mtime = mtime2; + + foreach (var fileName in Directory.EnumerateFiles (profileDir)) { + if (!fileName.EndsWith (".mobileprovision", StringComparison.Ordinal) && !fileName.EndsWith (".provisionprofile", StringComparison.Ordinal)) + continue; + + try { + var profile = ProvisioningProfile.Load (fileName); + index.ProvisioningProfiles.Add (profile); + } catch (Exception ex) { + LoggingService.LogWarning ("Error reading provisioning profile '{0}': {1}", fileName, ex); + } } + } else { + Directory.CreateDirectory (profileDir); } - - index.ProvisioningProfiles.Sort (CreationDateComparer); - } else { - Directory.CreateDirectory (profilesDir); } + index.ProvisioningProfiles.Sort (CreationDateComparer); index.Version = IndexVersion; - index.LastModified = Directory.GetLastWriteTimeUtc (profilesDir); + index.LastModified = mtime; index.Save (indexName); return index; } - public static MobileProvisionIndex OpenIndex (string profilesDir, string indexName) + static bool AnyProfileDirModifiedSince (string [] dirs, DateTime mtime) { - MobileProvisionIndex index; + foreach (var dir in dirs) { + if (!Directory.Exists (dir)) { + // Note: When creating the index, we make sure to create each profile dir which means if any + // are deleted, then we likely need to reindex. + return true; + } - try { - index = Load (indexName); + var mtime2 = Directory.GetLastWriteTimeUtc (dir); - if (Directory.Exists (profilesDir)) { - var mtime = Directory.GetLastWriteTimeUtc (profilesDir); - - if (index.Version != IndexVersion) { - index = CreateIndex (profilesDir, indexName); - } else if (index.LastModified < mtime) { - var table = new Dictionary (); - - foreach (var profile in index.ProvisioningProfiles) - table [profile.FileName] = profile; - - foreach (var fileName in Directory.EnumerateFiles (profilesDir)) { - if (!fileName.EndsWith (".mobileprovision", StringComparison.Ordinal) && !fileName.EndsWith (".provisionprofile", StringComparison.Ordinal)) - continue; - - ProvisioningProfile profile; - bool unknown = false; - - if (table.TryGetValue (Path.GetFileName (fileName), out profile)) { - // remove from our lookup table (any leftover key/valie pairs will be used to determine deleted files) - table.Remove (Path.GetFileName (fileName)); - - // check if the file has changed since our last resync - mtime = File.GetLastWriteTimeUtc (fileName); - - if (profile.LastModified < mtime) { - // remove the old record - index.ProvisioningProfiles.Remove (profile); - - // treat this provisioning profile as if it is unknown - unknown = true; - } - } else { - unknown = true; - } - - if (unknown) { - // unknown provisioning profile; add it to our ProvisioningProfiles array - try { - profile = ProvisioningProfile.Load (fileName); - index.ProvisioningProfiles.Add (profile); - } catch (Exception ex) { - LoggingService.LogWarning ("Error reading provisioning profile '{0}': {1}", fileName, ex); - } - } - } + if (mtime2 > mtime) + return true; + } - // remove provisioning profiles which have been deleted from the file system - foreach (var item in table) - index.ProvisioningProfiles.Remove (item.Value); + return false; + } - index.LastModified = Directory.GetLastWriteTimeUtc (profilesDir); - index.Version = IndexVersion; + public static MobileProvisionIndex OpenIndex (string [] profileDirs, string indexName) + { + MobileProvisionIndex index; - index.ProvisioningProfiles.Sort (CreationDateComparer); + try { + index = Load (indexName); - index.Save (indexName); - } - } else { + if (index.Version != IndexVersion || AnyProfileDirModifiedSince (profileDirs, index.LastModified)) + index = CreateIndex (profileDirs, indexName); + + if (index.ProvisioningProfiles.Count == 0) { try { File.Delete (indexName); } catch (Exception ex) { @@ -368,7 +338,7 @@ public static MobileProvisionIndex OpenIndex (string profilesDir, string indexNa index.ProvisioningProfiles.Clear (); } } catch { - index = CreateIndex (profilesDir, indexName); + index = CreateIndex (profileDirs, indexName); } return index; @@ -377,12 +347,15 @@ public static MobileProvisionIndex OpenIndex (string profilesDir, string indexNa public static MobileProvision GetMobileProvision (MobileProvisionPlatform platform, string name, List failures = null) { var extension = MobileProvision.GetFileExtension (platform); - var path = Path.Combine (MobileProvision.ProfileDirectory, name + extension); - if (File.Exists (path)) - return MobileProvision.LoadFromFile (path); + foreach (var profileDir in MobileProvision.ProfileDirectories) { + var path = Path.Combine (profileDir, name + extension); + + if (File.Exists (path)) + return MobileProvision.LoadFromFile (path); + } - var index = OpenIndex (MobileProvision.ProfileDirectory, IndexFileName); + var index = OpenIndex (MobileProvision.ProfileDirectories, IndexFileName); var latestCreationDate = DateTime.MinValue; if (index.ProvisioningProfiles.Count == 0) { @@ -402,7 +375,7 @@ public static MobileProvision GetMobileProvision (MobileProvisionPlatform platfo } if (name == profile.Name || name == profile.Uuid) - return MobileProvision.LoadFromFile (Path.Combine (MobileProvision.ProfileDirectory, profile.FileName)); + return MobileProvision.LoadFromFile (profile.FileName); failures?.Add ($"The profile '{profile.Name}' is not applicable because its Name and Uuid ({profile.Uuid}) do not match '{name}'."); } @@ -412,7 +385,7 @@ public static MobileProvision GetMobileProvision (MobileProvisionPlatform platfo public static IList GetMobileProvisions (MobileProvisionPlatform platform, bool includeExpired = false, bool unique = false, List failures = null) { - var index = OpenIndex (MobileProvision.ProfileDirectory, IndexFileName); + var index = OpenIndex (MobileProvision.ProfileDirectories, IndexFileName); var extension = MobileProvision.GetFileExtension (platform); var dictionary = new Dictionary (); var list = new List (); @@ -447,14 +420,14 @@ public static IList GetMobileProvisions (MobileProvisionPlatfor if (dictionary.TryGetValue (profile.Name, out idx)) { if (profile.CreationDate > list [idx].CreationDate) - list [idx] = MobileProvision.LoadFromFile (Path.Combine (MobileProvision.ProfileDirectory, profile.FileName)); + list [idx] = MobileProvision.LoadFromFile (profile.FileName); } else { - var provision = MobileProvision.LoadFromFile (Path.Combine (MobileProvision.ProfileDirectory, profile.FileName)); + var provision = MobileProvision.LoadFromFile (profile.FileName); dictionary.Add (profile.Name, list.Count); list.Add (provision); } } else { - var provision = MobileProvision.LoadFromFile (Path.Combine (MobileProvision.ProfileDirectory, profile.FileName)); + var provision = MobileProvision.LoadFromFile (profile.FileName); list.Add (provision); } } @@ -464,7 +437,7 @@ public static IList GetMobileProvisions (MobileProvisionPlatfor public static IList GetMobileProvisions (MobileProvisionPlatform platform, MobileProvisionDistributionType type, bool includeExpired = false, bool unique = false, List failures = null) { - var index = OpenIndex (MobileProvision.ProfileDirectory, IndexFileName); + var index = OpenIndex (MobileProvision.ProfileDirectories, IndexFileName); var extension = MobileProvision.GetFileExtension (platform); var dictionary = new Dictionary (); var list = new List (); @@ -504,14 +477,14 @@ public static IList GetMobileProvisions (MobileProvisionPlatfor if (dictionary.TryGetValue (profile.Name, out idx)) { if (profile.CreationDate > list [idx].CreationDate) - list [idx] = MobileProvision.LoadFromFile (Path.Combine (MobileProvision.ProfileDirectory, profile.FileName)); + list [idx] = MobileProvision.LoadFromFile (profile.FileName); } else { - var provision = MobileProvision.LoadFromFile (Path.Combine (MobileProvision.ProfileDirectory, profile.FileName)); + var provision = MobileProvision.LoadFromFile (profile.FileName); dictionary.Add (profile.Name, list.Count); list.Add (provision); } } else { - var provision = MobileProvision.LoadFromFile (Path.Combine (MobileProvision.ProfileDirectory, profile.FileName)); + var provision = MobileProvision.LoadFromFile (profile.FileName); list.Add (provision); } } @@ -521,7 +494,7 @@ public static IList GetMobileProvisions (MobileProvisionPlatfor public static IList GetMobileProvisions (MobileProvisionPlatform platform, MobileProvisionDistributionType type, IList developerCertificates, bool includeExpired = false, bool unique = false, List failures = null) { - var index = OpenIndex (MobileProvision.ProfileDirectory, IndexFileName); + var index = OpenIndex (MobileProvision.ProfileDirectories, IndexFileName); var extension = MobileProvision.GetFileExtension (platform); var dictionary = new Dictionary (); var thumbprints = new HashSet (); @@ -579,14 +552,14 @@ public static IList GetMobileProvisions (MobileProvisionPlatfor if (dictionary.TryGetValue (profile.Name, out idx)) { if (profile.CreationDate > list [idx].CreationDate) - list [idx] = MobileProvision.LoadFromFile (Path.Combine (MobileProvision.ProfileDirectory, profile.FileName)); + list [idx] = MobileProvision.LoadFromFile (profile.FileName); } else { - var provision = MobileProvision.LoadFromFile (Path.Combine (MobileProvision.ProfileDirectory, profile.FileName)); + var provision = MobileProvision.LoadFromFile (profile.FileName); dictionary.Add (profile.Name, list.Count); list.Add (provision); } } else { - var provision = MobileProvision.LoadFromFile (Path.Combine (MobileProvision.ProfileDirectory, profile.FileName)); + var provision = MobileProvision.LoadFromFile (profile.FileName); list.Add (provision); } break; @@ -598,7 +571,7 @@ public static IList GetMobileProvisions (MobileProvisionPlatfor public static IList GetMobileProvisions (MobileProvisionPlatform platform, string bundleIdentifier, MobileProvisionDistributionType type, bool includeExpired = false, bool unique = false, List failures = null) { - var index = OpenIndex (MobileProvision.ProfileDirectory, IndexFileName); + var index = OpenIndex (MobileProvision.ProfileDirectories, IndexFileName); var extension = MobileProvision.GetFileExtension (platform); var dictionary = new Dictionary (); var list = new List (); @@ -662,14 +635,14 @@ public static IList GetMobileProvisions (MobileProvisionPlatfor if (dictionary.TryGetValue (profile.Name, out idx)) { if (profile.CreationDate > list [idx].CreationDate) - list [idx] = MobileProvision.LoadFromFile (Path.Combine (MobileProvision.ProfileDirectory, profile.FileName)); + list [idx] = MobileProvision.LoadFromFile (profile.FileName); } else { - var provision = MobileProvision.LoadFromFile (Path.Combine (MobileProvision.ProfileDirectory, profile.FileName)); + var provision = MobileProvision.LoadFromFile (profile.FileName); dictionary.Add (profile.Name, list.Count); list.Add (provision); } } else { - var provision = MobileProvision.LoadFromFile (Path.Combine (MobileProvision.ProfileDirectory, profile.FileName)); + var provision = MobileProvision.LoadFromFile (profile.FileName); list.Add (provision); } } @@ -679,7 +652,7 @@ public static IList GetMobileProvisions (MobileProvisionPlatfor public static IList GetMobileProvisions (MobileProvisionPlatform platform, string bundleIdentifier, MobileProvisionDistributionType type, IList developerCertificates, bool includeExpired = false, bool unique = false, List failures = null) { - var index = OpenIndex (MobileProvision.ProfileDirectory, IndexFileName); + var index = OpenIndex (MobileProvision.ProfileDirectories, IndexFileName); var extension = MobileProvision.GetFileExtension (platform); var dictionary = new Dictionary (); var thumbprints = new HashSet (); @@ -761,14 +734,14 @@ public static IList GetMobileProvisions (MobileProvisionPlatfor if (dictionary.TryGetValue (profile.Name, out idx)) { if (profile.CreationDate > list [idx].CreationDate) - list [idx] = MobileProvision.LoadFromFile (Path.Combine (MobileProvision.ProfileDirectory, profile.FileName)); + list [idx] = MobileProvision.LoadFromFile (profile.FileName); } else { - var provision = MobileProvision.LoadFromFile (Path.Combine (MobileProvision.ProfileDirectory, profile.FileName)); + var provision = MobileProvision.LoadFromFile (profile.FileName); dictionary.Add (profile.Name, list.Count); list.Add (provision); } } else { - var provision = MobileProvision.LoadFromFile (Path.Combine (MobileProvision.ProfileDirectory, profile.FileName)); + var provision = MobileProvision.LoadFromFile (profile.FileName); list.Add (provision); } break;