Skip to content

Commit a3bc821

Browse files
authored
Support new export methods (#266)
Adds support for new export methods available since Xcode 15.3. - Updated xcodeversion.Reader to read minor xcode version
1 parent fa1fb49 commit a3bc821

File tree

12 files changed

+291
-82
lines changed

12 files changed

+291
-82
lines changed

destination/device_finder_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ func Test_deviceFinder_FindDevice(t *testing.T) {
9898
},
9999
{
100100
name: "latest for an earler Xcode version",
101-
xcodeVersion: xcodeversion.Version{MajorVersion: 13},
101+
xcodeVersion: xcodeversion.Version{Major: 13},
102102
wantedDevice: Simulator{
103103
Platform: "iOS Simulator",
104104
OS: "latest",

destination/xcode_runtime_support.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,12 @@ func isRuntimeSupportedByXcode(runtimePlatform string, runtimeVersion *version.V
2626
},
2727
}
2828

29-
if len(runtimeVersion.Segments64()) == 0 || xcodeVersion.MajorVersion == 0 {
29+
if len(runtimeVersion.Segments64()) == 0 || xcodeVersion.Major == 0 {
3030
return true
3131
}
3232
runtimeMajorVersion := runtimeVersion.Segments64()[0]
3333

34-
platformToLatestSupportedVersion, ok := xcodeVersionToSupportedRuntimes[xcodeVersion.MajorVersion]
34+
platformToLatestSupportedVersion, ok := xcodeVersionToSupportedRuntimes[xcodeVersion.Major]
3535
if !ok {
3636
return true
3737
}

destination/xcode_runtime_support_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,28 +20,28 @@ func Test_isRuntimeSupportedByXcode(t *testing.T) {
2020
name: "iOS 16 on Xcode 15",
2121
runtimePlatform: "iOS",
2222
runtimeVersion: version.Must(version.NewVersion("16.4")),
23-
xcodeVersion: xcodeversion.Version{MajorVersion: 15},
23+
xcodeVersion: xcodeversion.Version{Major: 15},
2424
want: true,
2525
},
2626
{
2727
name: "iOS 16 on unknown Xcode version",
2828
runtimePlatform: "iOS",
2929
runtimeVersion: version.Must(version.NewVersion("16.4")),
30-
xcodeVersion: xcodeversion.Version{MajorVersion: 3}, // unknown version
30+
xcodeVersion: xcodeversion.Version{Major: 3}, // unknown version
3131
want: true,
3232
},
3333
{
3434
name: "tvOS 17 on Xcode 14",
3535
runtimePlatform: "tvOS",
3636
runtimeVersion: version.Must(version.NewVersion("17")),
37-
xcodeVersion: xcodeversion.Version{MajorVersion: 14},
37+
xcodeVersion: xcodeversion.Version{Major: 14},
3838
want: false,
3939
},
4040
{
4141
name: "unknown platform",
4242
runtimePlatform: "walletOS",
4343
runtimeVersion: version.Must(version.NewVersion("1")),
44-
xcodeVersion: xcodeversion.Version{MajorVersion: 15},
44+
xcodeVersion: xcodeversion.Version{Major: 15},
4545
want: true,
4646
},
4747
}

exportoptionsgenerator/exportoptionsgenerator.go

Lines changed: 26 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"github.com/bitrise-io/go-xcode/exportoptions"
1111
"github.com/bitrise-io/go-xcode/plistutil"
1212
"github.com/bitrise-io/go-xcode/profileutil"
13+
"github.com/bitrise-io/go-xcode/v2/xcodeversion"
1314
"github.com/bitrise-io/go-xcode/xcodeproject/serialized"
1415
"github.com/bitrise-io/go-xcode/xcodeproject/xcodeproj"
1516
"github.com/bitrise-io/go-xcode/xcodeproject/xcscheme"
@@ -26,18 +27,20 @@ type ExportOptionsGenerator struct {
2627
scheme *xcscheme.Scheme
2728
configuration string
2829

30+
xcodeVersionReader xcodeversion.Reader
2931
certificateProvider CodesignIdentityProvider
3032
profileProvider ProvisioningProfileProvider
3133
targetInfoProvider TargetInfoProvider
3234
logger log.Logger
3335
}
3436

3537
// New constructs a new ExportOptionsGenerator.
36-
func New(xcodeProj *xcodeproj.XcodeProj, scheme *xcscheme.Scheme, configuration string, logger log.Logger) ExportOptionsGenerator {
38+
func New(xcodeProj *xcodeproj.XcodeProj, scheme *xcscheme.Scheme, configuration string, xcodeVersionReader xcodeversion.Reader, logger log.Logger) ExportOptionsGenerator {
3739
g := ExportOptionsGenerator{
38-
xcodeProj: xcodeProj,
39-
scheme: scheme,
40-
configuration: configuration,
40+
xcodeProj: xcodeProj,
41+
scheme: scheme,
42+
configuration: configuration,
43+
xcodeVersionReader: xcodeVersionReader,
4144
}
4245
g.certificateProvider = LocalCodesignIdentityProvider{}
4346
g.profileProvider = LocalProvisioningProfileProvider{}
@@ -55,26 +58,30 @@ func (g ExportOptionsGenerator) GenerateApplicationExportOptions(
5558
compileBitcode bool,
5659
archivedWithXcodeManagedProfiles bool,
5760
codeSigningStyle exportoptions.SigningStyle,
58-
xcodeMajorVersion int64,
5961
testFlightInternalTestingOnly bool,
6062
) (exportoptions.ExportOptions, error) {
63+
xcodeVersion, err := g.xcodeVersionReader.GetVersion()
64+
if err != nil {
65+
return nil, fmt.Errorf("failed to get Xcode version: %w", err)
66+
}
67+
6168
mainTargetBundleID, entitlementsByBundleID, err := g.applicationTargetsAndEntitlements(exportMethod)
6269
if err != nil {
6370
return nil, err
6471
}
6572

66-
iCloudContainerEnvironment, err := determineIcloudContainerEnvironment(containerEnvironment, entitlementsByBundleID, exportMethod, xcodeMajorVersion)
73+
iCloudContainerEnvironment, err := determineIcloudContainerEnvironment(containerEnvironment, entitlementsByBundleID, exportMethod, xcodeVersion.Major)
6774
if err != nil {
6875
return nil, err
6976
}
7077

71-
exportOpts := generateBaseExportOptions(exportMethod, uploadBitcode, compileBitcode, iCloudContainerEnvironment)
78+
exportOpts := generateBaseExportOptions(exportMethod, xcodeVersion, uploadBitcode, compileBitcode, iCloudContainerEnvironment)
7279

73-
if xcodeMajorVersion >= 12 {
80+
if xcodeVersion.Major >= 12 {
7481
exportOpts = addDistributionBundleIdentifierFromXcode12(exportOpts, mainTargetBundleID)
7582
}
7683

77-
if xcodeMajorVersion >= 13 {
84+
if xcodeVersion.Major >= 13 {
7885
exportOpts = disableManagedBuildNumberFromXcode13(exportOpts)
7986
}
8087

@@ -92,7 +99,7 @@ func (g ExportOptionsGenerator) GenerateApplicationExportOptions(
9299
exportOpts = addManualSigningFields(exportOpts, codeSignGroup, archivedWithXcodeManagedProfiles, g.logger)
93100
}
94101

95-
if xcodeMajorVersion >= 15 {
102+
if xcodeVersion.Major >= 15 {
96103
if testFlightInternalTestingOnly {
97104
exportOpts = addTestFlightInternalTestingOnly(exportOpts, testFlightInternalTestingOnly)
98105
}
@@ -292,11 +299,11 @@ func determineIcloudContainerEnvironment(desiredIcloudContainerEnvironment strin
292299
}
293300

294301
// From Xcode 9 iCloudContainerEnvironment is required for every export method, before that version only for non app-store exports.
295-
if xcodeMajorVersion < 9 && exportMethod == exportoptions.MethodAppStore {
302+
if xcodeMajorVersion < 9 && exportMethod.IsAppStore() {
296303
return "", nil
297304
}
298305

299-
if exportMethod == exportoptions.MethodAppStore {
306+
if exportMethod.IsAppStore() {
300307
return "Production", nil
301308
}
302309

@@ -332,9 +339,13 @@ func projectUsesCloudKit(bundleIDEntitlementsMap map[string]plistutil.PlistData)
332339
}
333340

334341
// generateBaseExportOptions creates a default exportOptions introduced in Xcode 7.
335-
func generateBaseExportOptions(exportMethod exportoptions.Method, cfgUploadBitcode, cfgCompileBitcode bool, iCloudContainerEnvironment string) exportoptions.ExportOptions {
336-
if exportMethod == exportoptions.MethodAppStore {
337-
appStoreOptions := exportoptions.NewAppStoreOptions()
342+
func generateBaseExportOptions(exportMethod exportoptions.Method, xcodeVersion xcodeversion.Version, cfgUploadBitcode, cfgCompileBitcode bool, iCloudContainerEnvironment string) exportoptions.ExportOptions {
343+
if xcodeVersion.IsGreaterThanOrEqualTo(15, 3) {
344+
exportMethod = exportoptions.UpgradeToXcode15_3MethodName(exportMethod)
345+
}
346+
347+
if exportMethod.IsAppStore() {
348+
appStoreOptions := exportoptions.NewAppStoreConnectOptions(exportMethod)
338349
appStoreOptions.UploadBitcode = cfgUploadBitcode
339350
if iCloudContainerEnvironment != "" {
340351
appStoreOptions.ICloudContainerEnvironment = exportoptions.ICloudContainerEnvironment(iCloudContainerEnvironment)

exportoptionsgenerator/exportoptionsgenerator_test.go

Lines changed: 61 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import (
88
"github.com/bitrise-io/go-xcode/certificateutil"
99
"github.com/bitrise-io/go-xcode/exportoptions"
1010
"github.com/bitrise-io/go-xcode/profileutil"
11+
"github.com/bitrise-io/go-xcode/v2/exportoptionsgenerator/mocks"
12+
"github.com/bitrise-io/go-xcode/v2/xcodeversion"
1113
"github.com/bitrise-io/go-xcode/xcodeproject/serialized"
1214
"github.com/bitrise-io/go-xcode/xcodeproject/xcodeproj"
1315
"github.com/bitrise-io/go-xcode/xcodeproject/xcscheme"
@@ -129,6 +131,18 @@ const (
129131
<key>method</key>
130132
<string>development</string>
131133
</dict>
134+
</plist>`
135+
expectedNoProfilesDevelopmentXcode16ExportOptions = `<?xml version="1.0" encoding="UTF-8"?>
136+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
137+
<plist version="1.0">
138+
<dict>
139+
<key>distributionBundleIdentifier</key>
140+
<string>io.bundle.id</string>
141+
<key>iCloudContainerEnvironment</key>
142+
<string>Production</string>
143+
<key>method</key>
144+
<string>debugging</string>
145+
</dict>
132146
</plist>`
133147
expectedNoProfilesXcode13AppStoreExportOptions = `<?xml version="1.0" encoding="UTF-8"?>
134148
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
@@ -141,6 +155,18 @@ const (
141155
<key>method</key>
142156
<string>app-store</string>
143157
</dict>
158+
</plist>`
159+
expectedNoProfilesXcode16AppStoreExportOptions = `<?xml version="1.0" encoding="UTF-8"?>
160+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
161+
<plist version="1.0">
162+
<dict>
163+
<key>iCloudContainerEnvironment</key>
164+
<string>Production</string>
165+
<key>manageAppVersionAndBuildNumber</key>
166+
<false/>
167+
<key>method</key>
168+
<string>app-store-connect</string>
169+
</dict>
144170
</plist>`
145171
expectedNoProfilesAdHocExportOptions = `<?xml version="1.0" encoding="UTF-8"?>
146172
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
@@ -156,6 +182,12 @@ const (
156182
</plist>`
157183
)
158184

185+
func newXcodeVersionReader(t *testing.T, major int64) xcodeversion.Reader {
186+
reader := mocks.NewXcodeVersionReader(t)
187+
reader.On("GetVersion").Return(xcodeversion.Version{Major: major}, nil)
188+
return reader
189+
}
190+
159191
func TestExportOptionsGenerator_GenerateApplicationExportOptions_ForAutomaticSigningStyle(t *testing.T) {
160192
// Arrange
161193
const (
@@ -179,13 +211,13 @@ func TestExportOptionsGenerator_GenerateApplicationExportOptions_ForAutomaticSig
179211
{
180212
name: "Default development exportOptions",
181213
exportMethod: exportoptions.MethodDevelopment,
182-
xcodeVersion: 15,
183214
generatorFactory: func() ExportOptionsGenerator {
184215
applicationTarget := givenApplicationTarget(nil)
185216
xcodeProj := givenXcodeproj([]xcodeproj.Target{applicationTarget})
186217
scheme := givenScheme(applicationTarget)
218+
xcodeVersionReader := newXcodeVersionReader(t, 15)
187219

188-
g := New(&xcodeProj, &scheme, "", logger)
220+
g := New(&xcodeProj, &scheme, "", xcodeVersionReader, logger)
189221
g.targetInfoProvider = MockTargetInfoProvider{
190222
bundleID: map[string]string{"Application": bundleID},
191223
}
@@ -208,13 +240,13 @@ func TestExportOptionsGenerator_GenerateApplicationExportOptions_ForAutomaticSig
208240
{
209241
name: "Default app store exportOptions",
210242
exportMethod: exportoptions.MethodAppStore,
211-
xcodeVersion: 15,
212243
generatorFactory: func() ExportOptionsGenerator {
213244
applicationTarget := givenApplicationTarget(nil)
214245
xcodeProj := givenXcodeproj([]xcodeproj.Target{applicationTarget})
215246
scheme := givenScheme(applicationTarget)
247+
xcodeVersionReader := newXcodeVersionReader(t, 15)
216248

217-
g := New(&xcodeProj, &scheme, "", logger)
249+
g := New(&xcodeProj, &scheme, "", xcodeVersionReader, logger)
218250
g.targetInfoProvider = MockTargetInfoProvider{
219251
bundleID: map[string]string{"Application": bundleID},
220252
}
@@ -238,13 +270,13 @@ func TestExportOptionsGenerator_GenerateApplicationExportOptions_ForAutomaticSig
238270
name: "When the app uses iCloud services",
239271
exportMethod: exportoptions.MethodDevelopment,
240272
containerEnvironment: string(exportoptions.ICloudContainerEnvironmentProduction),
241-
xcodeVersion: 15,
242273
generatorFactory: func() ExportOptionsGenerator {
243274
applicationTarget := givenApplicationTarget(nil)
244275
xcodeProj := givenXcodeproj([]xcodeproj.Target{applicationTarget})
245276
scheme := givenScheme(applicationTarget)
277+
xcodeVersionReader := newXcodeVersionReader(t, 15)
246278

247-
g := New(&xcodeProj, &scheme, "", logger)
279+
g := New(&xcodeProj, &scheme, "", xcodeVersionReader, logger)
248280
g.targetInfoProvider = MockTargetInfoProvider{
249281
bundleID: map[string]string{"Application": bundleID},
250282
codesignEntitlements: map[string]serialized.Object{"Application": map[string]interface{}{"com.apple.developer.icloud-services": []string{"CloudKit"}}},
@@ -270,14 +302,14 @@ func TestExportOptionsGenerator_GenerateApplicationExportOptions_ForAutomaticSig
270302
{
271303
name: "When exporting for TestFlight internal testing only",
272304
exportMethod: exportoptions.MethodAppStore,
273-
xcodeVersion: 15,
274305
testFlightInternalTestingOnly: true,
275306
generatorFactory: func() ExportOptionsGenerator {
276307
applicationTarget := givenApplicationTarget(nil)
277308
xcodeProj := givenXcodeproj([]xcodeproj.Target{applicationTarget})
278309
scheme := givenScheme(applicationTarget)
310+
xcodeVersionReader := newXcodeVersionReader(t, 15)
279311

280-
g := New(&xcodeProj, &scheme, "", logger)
312+
g := New(&xcodeProj, &scheme, "", xcodeVersionReader, logger)
281313
g.targetInfoProvider = MockTargetInfoProvider{
282314
bundleID: map[string]string{"Application": bundleID},
283315
}
@@ -303,7 +335,7 @@ func TestExportOptionsGenerator_GenerateApplicationExportOptions_ForAutomaticSig
303335
for _, tt := range tests {
304336
t.Run(tt.name, func(t *testing.T) {
305337
// Act
306-
gotOpts, err := tt.generatorFactory().GenerateApplicationExportOptions(tt.exportMethod, tt.containerEnvironment, teamID, true, true, false, exportoptions.SigningStyleAutomatic, tt.xcodeVersion, tt.testFlightInternalTestingOnly)
338+
gotOpts, err := tt.generatorFactory().GenerateApplicationExportOptions(tt.exportMethod, tt.containerEnvironment, teamID, true, true, false, exportoptions.SigningStyleAutomatic, tt.testFlightInternalTestingOnly)
307339

308340
// Assert
309341
require.NoError(t, err)
@@ -372,7 +404,9 @@ func TestExportOptionsGenerator_GenerateApplicationExportOptions(t *testing.T) {
372404
scheme := givenScheme(applicationTarget)
373405
logger := log.NewLogger()
374406
logger.EnableDebugLog(true)
375-
g := New(&xcodeProj, &scheme, "", logger)
407+
xcodeVersionReader := newXcodeVersionReader(t, tt.xcodeVersion)
408+
409+
g := New(&xcodeProj, &scheme, "", xcodeVersionReader, logger)
376410
g.certificateProvider = MockCodesignIdentityProvider{
377411
[]certificateutil.CertificateInfoModel{certificate},
378412
}
@@ -405,7 +439,7 @@ func TestExportOptionsGenerator_GenerateApplicationExportOptions(t *testing.T) {
405439
}
406440

407441
// Act
408-
gotOpts, err := g.GenerateApplicationExportOptions(tt.exportMethod, "Production", teamID, true, true, false, exportoptions.SigningStyleManual, tt.xcodeVersion, true)
442+
gotOpts, err := g.GenerateApplicationExportOptions(tt.exportMethod, "Production", teamID, true, true, false, exportoptions.SigningStyleManual, true)
409443

410444
// Assert
411445
require.NoError(t, err)
@@ -434,6 +468,12 @@ func TestExportOptionsGenerator_GenerateApplicationExportOptions_WhenNoProfileFo
434468
want string
435469
wantErr bool
436470
}{
471+
{
472+
name: "When no profiles found, Xcode 16, using new export method name",
473+
exportMethod: exportoptions.MethodAppStore,
474+
xcodeVersion: 16,
475+
want: expectedNoProfilesXcode16AppStoreExportOptions,
476+
},
437477
{
438478
name: "When no profiles found, Xcode 13, then manageAppVersionAndBuildNumber is included",
439479
exportMethod: exportoptions.MethodAppStore,
@@ -446,6 +486,12 @@ func TestExportOptionsGenerator_GenerateApplicationExportOptions_WhenNoProfileFo
446486
xcodeVersion: 13,
447487
want: expectedNoProfilesAdHocExportOptions,
448488
},
489+
{
490+
name: "When no profiles found, Xcode 16, usess new export method name",
491+
exportMethod: exportoptions.MethodDevelopment,
492+
xcodeVersion: 16,
493+
want: expectedNoProfilesDevelopmentXcode16ExportOptions,
494+
},
449495
{
450496
name: "When no profiles found, Xcode 11",
451497
exportMethod: exportoptions.MethodDevelopment,
@@ -462,7 +508,9 @@ func TestExportOptionsGenerator_GenerateApplicationExportOptions_WhenNoProfileFo
462508
scheme := givenScheme(applicationTarget)
463509
logger := log.NewLogger()
464510
logger.EnableDebugLog(true)
465-
g := New(&xcodeProj, &scheme, "", logger)
511+
xcodeVersionReader := newXcodeVersionReader(t, tt.xcodeVersion)
512+
513+
g := New(&xcodeProj, &scheme, "", xcodeVersionReader, logger)
466514
g.certificateProvider = MockCodesignIdentityProvider{
467515
[]certificateutil.CertificateInfoModel{certificate},
468516
}
@@ -475,7 +523,7 @@ func TestExportOptionsGenerator_GenerateApplicationExportOptions_WhenNoProfileFo
475523
}
476524

477525
// Act
478-
gotOpts, err := g.GenerateApplicationExportOptions(tt.exportMethod, "Production", teamID, true, true, false, exportoptions.SigningStyleManual, tt.xcodeVersion, true)
526+
gotOpts, err := g.GenerateApplicationExportOptions(tt.exportMethod, "Production", teamID, true, true, false, exportoptions.SigningStyleManual, true)
479527

480528
// Assert
481529
require.NoError(t, err)

0 commit comments

Comments
 (0)