Skip to content

Commit 399bba6

Browse files
Merge pull request #2 from brycepauken/product-build-version
Switch to using ProductBuildVersion for 15.3+ Compatibility Checks
2 parents d1393b2 + b650d59 commit 399bba6

4 files changed

Lines changed: 41 additions & 32 deletions

File tree

README.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,21 +31,21 @@ Just like Xcode's original plugin system, this loader checks plugin bundles for
3131

3232
For Xcode 14.0 - 15.2, plugins can add [`DVTPlugInCompatibilityUUIDs`](https://gist.github.com/minsko/9124ee24b9422fb8ea6b8d00815783ba) to their `Info.plist` files to specify which versions of Xcode they're compatible with.
3333

34-
For Xcode 15.3+, plugins should instead specify `DTXcodeBuildCompatibleVersions` in their `Info.plist` file, based on Xcode's build number (like `15E5194e`). The lowercase letter suffix is optional and should generally be dropped, unless compatibility with specific builds is needed.
34+
For Xcode 15.3+, plugins should instead specify `CompatibleProductBuildVersions` in their `Info.plist` file, based on Xcode's build version (like `15E204a`).
3535

3636
These two compatibility values can exist side-by-side:
3737

3838
```xml
39-
<key>DTXcodeBuildCompatibleVersions</key>
39+
<key>CompatibleProductBuildVersions</key>
4040
<array>
41-
<string>15E5178</string>
42-
<string>15E5188</string>
41+
<string>15E204a</string> <!-- 15.3 -->
42+
<string>15E5178i</string> <!-- 15.3b1 -->
4343
</array>
4444
<key>DVTPlugInCompatibilityUUIDs</key>
4545
<array>
46-
<string>EFD92DF8-D0A2-4C92-B6E3-9B3CD7E8DC19</string>
47-
<string>8BAA96B4-5225-471B-B124-D32A349B8106</string>
48-
<string>7A3A18B7-4C08-46F0-A96A-AB686D315DF0</string>
46+
<string>EFD92DF8-D0A2-4C92-B6E3-9B3CD7E8DC19</string> <!-- 13.4 -->
47+
<string>7A3A18B7-4C08-46F0-A96A-AB686D315DF0</string> <!-- 13.2 -->
48+
<string>8BAA96B4-5225-471B-B124-D32A349B8106</string> <!-- 13.0 -->
4949
</array>
5050
```
5151

XcodePluginLoader.xcodeproj/project.pbxproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
020463A72B7535A000BF01DB /* ClassLoadObserver.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ClassLoadObserver.h; sourceTree = "<group>"; };
2424
020463A82B7535A000BF01DB /* ClassLoadObserver.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ClassLoadObserver.m; sourceTree = "<group>"; };
2525
020463AB2B753EF800BF01DB /* DVTPlugInManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DVTPlugInManager.h; sourceTree = "<group>"; };
26+
02AE2E362B9F46F4006AF5FD /* NSProcessInfo+PBXTSPlatformAdditions.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSProcessInfo+PBXTSPlatformAdditions.h"; sourceTree = "<group>"; };
2627
/* End PBXFileReference section */
2728

2829
/* Begin PBXFrameworksBuildPhase section */
@@ -68,6 +69,7 @@
6869
isa = PBXGroup;
6970
children = (
7071
020463AB2B753EF800BF01DB /* DVTPlugInManager.h */,
72+
02AE2E362B9F46F4006AF5FD /* NSProcessInfo+PBXTSPlatformAdditions.h */,
7173
020463A62B75355700BF01DB /* XcodePlugin.h */,
7274
);
7375
path = XcodeHeaders;
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#import <Foundation/Foundation.h>
2+
3+
/**
4+
Based on `NSProcessInfo(PBXTSPlatformAdditions)` category in Xcode 15.2
5+
*/
6+
@interface NSProcessInfo (PBXTSPlatformAdditions)
7+
8+
- (NSString *)xcodeProductBuildVersion;
9+
10+
@end

XcodePluginLoader/XcodePluginLoader.m

Lines changed: 22 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
#import "ClassLoadObserver.h"
88
#import "XcodeHeaders/DVTPlugInManager.h"
9+
#import "NSProcessInfo+PBXTSPlatformAdditions.h"
910
#import "XcodeHeaders/XcodePlugin.h"
1011

1112
/**
@@ -24,9 +25,12 @@ @implementation XcodePluginLoader
2425
- (void)start {
2526
NSLog(@"[XcodePluginLoader] Waiting for required classes to load");
2627

27-
// Wait for `DVTPlugInManager` to load before performing actual plugin loading
28+
// Wait for classes to load before performing actual plugin loading
2829
__weak typeof(self) weakSelf = self;
29-
self.classLoadObserver = [ClassLoadObserver observerForClasses:@[@"DVTPlugInManager"] completion:^{
30+
self.classLoadObserver = [ClassLoadObserver observerForClasses:@[
31+
@"PBXTSTask", // Needed for Xcode 15.3+ compatibility checks (as a proxy for NSProcessInfo(PBXTSPlatformAdditions) being loaded)
32+
@"DVTPlugInManager" // Needed for pre Xcode 15.3 compatibility checks
33+
] completion:^{
3034
[weakSelf loadPlugins];
3135
}];
3236
}
@@ -73,44 +77,37 @@ - (void)loadPlugins {
7377
return;
7478
}
7579

76-
// Run compatibility check using `DTXcodeBuildCompatibleVersions` (needed for Xcode 15.3+)
77-
NSString *xcodeBuildVersion = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"DTXcodeBuild"];
78-
NSArray<NSString *> *compatibleBuildVersions = [bundle objectForInfoDictionaryKey:@"DTXcodeBuildCompatibleVersions"];
80+
// Run compatibility check using `ProductBuildVersion` (needed for Xcode 15.3+).
81+
// Start by getting Xcode's `ProductBuildVersion`
82+
NSProcessInfo *processInfo = [NSProcessInfo processInfo];
83+
NSString *xcodeBuildVersion;
84+
if ([processInfo respondsToSelector:@selector(xcodeProductBuildVersion)]) {
85+
xcodeBuildVersion = [[NSProcessInfo processInfo] xcodeProductBuildVersion];
86+
}
87+
88+
// Then get all `CompatibleProductBuildVersions` values from the plugin and compare
89+
NSArray<NSString *> *compatibleBuildVersions = [bundle objectForInfoDictionaryKey:@"CompatibleProductBuildVersions"];
7990
BOOL hasCompatibleBuildVersion = NO;
80-
if (compatibleBuildVersions) {
91+
if (xcodeBuildVersion && compatibleBuildVersions) {
8192
if (![compatibleBuildVersions isKindOfClass:[NSArray class]]) {
82-
NSLog(@"[XcodePluginLoader] Skipping %@ (invalid DTXcodeBuildCompatibleVersions format)", potentialBundleName);
93+
NSLog(@"[XcodePluginLoader] Skipping %@ (invalid CompatibleProductBuildVersions format)", potentialBundleName);
8394
return;
8495
}
8596

86-
compatibleBuildVersions = [compatibleBuildVersions sortedArrayUsingSelector:@selector(length)];
97+
// Check for a match
8798
for (NSString *buildVersion in compatibleBuildVersions) {
88-
if ([xcodeBuildVersion isEqualToString:buildVersion]) {
99+
if ([buildVersion isEqualToString:xcodeBuildVersion]) {
89100
hasCompatibleBuildVersion = YES;
90-
NSLog(@"[XcodePluginLoader] %@ is compatible with DTXcodeBuild version %@", potentialBundleName, xcodeBuildVersion);
101+
NSLog(@"[XcodePluginLoader] %@ is compatible with ProductBuildVersion version %@", potentialBundleName, xcodeBuildVersion);
91102
break;
92103
}
93-
94-
// Apple sometimes releases multiple builds of Xcode using the same version number
95-
// (e.g., some people will have Xcode 13.3b1 with a build number of "15E5178i", others
96-
// may have "15E5178b"). Treat the lowercase suffix as optional
97-
if ([xcodeBuildVersion hasPrefix:buildVersion]) {
98-
NSString *unmatchedBuildVersionString = [xcodeBuildVersion substringFromIndex:[buildVersion length]];
99-
NSCharacterSet *nonLowercaseLetters = [[NSCharacterSet lowercaseLetterCharacterSet] invertedSet];
100-
NSRange rangeOfNonLowercaseLetters = [unmatchedBuildVersionString rangeOfCharacterFromSet:nonLowercaseLetters];
101-
if (rangeOfNonLowercaseLetters.location == NSNotFound) {
102-
hasCompatibleBuildVersion = YES;
103-
NSLog(@"[XcodePluginLoader] %@ is compatible because Xcode has a more-specific DTXcodeBuild version (Xcode version %@, compatible version: %@)", potentialBundleName, xcodeBuildVersion, buildVersion);
104-
break;
105-
}
106-
}
107104
}
108105
}
109106

110107
// Fall back to doing a compatibility check using `DVTPlugInCompatibilityUUIDs`
111108
// (matches Xcode's original plugin loader behavior, but is only available on Xcode 15.2 or older)
112109
if (!hasCompatibleBuildVersion) {
113-
NSLog(@"[XcodePluginLoader] No DTXcodeBuildCompatibleVersions in %@ matching version %@. Falling back to DVTPlugInCompatibilityUUIDs", potentialBundleName, xcodeBuildVersion);
110+
NSLog(@"[XcodePluginLoader] No CompatibleProductBuildVersions in %@ matching version %@. Falling back to DVTPlugInCompatibilityUUIDs", potentialBundleName, xcodeBuildVersion);
114111

115112
// Read the list of compatibility UUIDs specified by the plugin that we're loading
116113
NSArray<NSString *> *compatibilityUUIDs = [bundle objectForInfoDictionaryKey:@"DVTPlugInCompatibilityUUIDs"];

0 commit comments

Comments
 (0)