Skip to content

Commit bff4df0

Browse files
authored
Adding ArchiveTargetInfoProvider (#268)
- Added ArchiveInfo. This allows to remove some legacy duplicated code in the export-xcarchive Step. - Added ReadArchiveInfoFromXcodeproject, which preserves the existing functionality when using with xcode-archive. - Extracted some parameters of GenerateApplicationExportOptions into an Opts struct, as there were too many bool parameters that were easy to confuse. Added exportProduct and archiveInfo parameters. - Removed some overly verbose mocking from the tests.
1 parent a3bc821 commit bff4df0

File tree

8 files changed

+269
-307
lines changed

8 files changed

+269
-307
lines changed

codesign/archive.go

-37
This file was deleted.

codesign/codesign.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"github.com/bitrise-io/go-xcode/v2/autocodesign/devportalclient/appstoreconnect"
1212
"github.com/bitrise-io/go-xcode/v2/autocodesign/projectmanager"
1313
"github.com/bitrise-io/go-xcode/v2/devportalservice"
14+
"github.com/bitrise-io/go-xcode/v2/xcarchive"
1415
)
1516

1617
// AuthType ...
@@ -74,7 +75,7 @@ func NewManagerWithArchive(
7475
fallbackProfileDownloader autocodesign.ProfileProvider,
7576
assetInstaller autocodesign.AssetWriter,
7677
localCodeSignAssetManager autocodesign.LocalCodeSignAssetManager,
77-
archive Archive,
78+
archive xcarchive.IosArchive,
7879
logger log.Logger,
7980
) Manager {
8081
return Manager{
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package exportoptionsgenerator
2+
3+
import (
4+
"github.com/bitrise-io/go-xcode/xcarchive"
5+
)
6+
7+
// ExportProduct ...
8+
type ExportProduct string
9+
10+
const (
11+
// ExportProductApp ...
12+
ExportProductApp ExportProduct = "app"
13+
// ExportProductAppClip ...
14+
ExportProductAppClip ExportProduct = "app-clip"
15+
)
16+
17+
// ReadArchiveExportInfo ...
18+
func ReadArchiveExportInfo(archive xcarchive.IosArchive) (ArchiveInfo, error) {
19+
appClipBundleID := ""
20+
if archive.Application.ClipApplication != nil {
21+
appClipBundleID = archive.Application.ClipApplication.BundleIdentifier()
22+
}
23+
24+
return ArchiveInfo{
25+
AppBundleID: archive.Application.BundleIdentifier(),
26+
AppClipBundleID: appClipBundleID,
27+
EntitlementsByBundleID: archive.BundleIDEntitlementsMap(),
28+
}, nil
29+
}

exportoptionsgenerator/exportoptionsgenerator.go

+57-75
Original file line numberDiff line numberDiff line change
@@ -11,138 +11,120 @@ import (
1111
"github.com/bitrise-io/go-xcode/plistutil"
1212
"github.com/bitrise-io/go-xcode/profileutil"
1313
"github.com/bitrise-io/go-xcode/v2/xcodeversion"
14-
"github.com/bitrise-io/go-xcode/xcodeproject/serialized"
15-
"github.com/bitrise-io/go-xcode/xcodeproject/xcodeproj"
16-
"github.com/bitrise-io/go-xcode/xcodeproject/xcscheme"
1714
)
1815

1916
const (
2017
// AppClipProductType ...
2118
AppClipProductType = "com.apple.product-type.application.on-demand-install-capable"
2219
)
2320

21+
// Opts contains options for the exportOptions generator.
22+
type Opts struct {
23+
ContainerEnvironment string
24+
TeamID string
25+
UploadBitcode bool
26+
CompileBitcode bool
27+
ArchivedWithXcodeManagedProfiles bool
28+
TestFlightInternalTestingOnly bool
29+
ManageVersionAndBuildNumber bool
30+
}
31+
2432
// ExportOptionsGenerator generates an exportOptions.plist file.
2533
type ExportOptionsGenerator struct {
26-
xcodeProj *xcodeproj.XcodeProj
27-
scheme *xcscheme.Scheme
28-
configuration string
29-
3034
xcodeVersionReader xcodeversion.Reader
3135
certificateProvider CodesignIdentityProvider
3236
profileProvider ProvisioningProfileProvider
33-
targetInfoProvider TargetInfoProvider
3437
logger log.Logger
3538
}
3639

3740
// New constructs a new ExportOptionsGenerator.
38-
func New(xcodeProj *xcodeproj.XcodeProj, scheme *xcscheme.Scheme, configuration string, xcodeVersionReader xcodeversion.Reader, logger log.Logger) ExportOptionsGenerator {
39-
g := ExportOptionsGenerator{
40-
xcodeProj: xcodeProj,
41-
scheme: scheme,
42-
configuration: configuration,
43-
xcodeVersionReader: xcodeVersionReader,
44-
}
45-
g.certificateProvider = LocalCodesignIdentityProvider{}
46-
g.profileProvider = LocalProvisioningProfileProvider{}
47-
g.targetInfoProvider = XcodebuildTargetInfoProvider{xcodeProj: xcodeProj}
48-
g.logger = logger
49-
return g
41+
func New(xcodeVersionReader xcodeversion.Reader, logger log.Logger) ExportOptionsGenerator {
42+
return ExportOptionsGenerator{
43+
xcodeVersionReader: xcodeVersionReader,
44+
certificateProvider: LocalCodesignIdentityProvider{},
45+
profileProvider: LocalProvisioningProfileProvider{},
46+
logger: logger,
47+
}
5048
}
5149

5250
// GenerateApplicationExportOptions generates exportOptions for an application export.
5351
func (g ExportOptionsGenerator) GenerateApplicationExportOptions(
52+
exportedProduct ExportProduct,
53+
archiveInfo ArchiveInfo,
5454
exportMethod exportoptions.Method,
55-
containerEnvironment string,
56-
teamID string,
57-
uploadBitcode bool,
58-
compileBitcode bool,
59-
archivedWithXcodeManagedProfiles bool,
6055
codeSigningStyle exportoptions.SigningStyle,
61-
testFlightInternalTestingOnly bool,
56+
opts Opts,
6257
) (exportoptions.ExportOptions, error) {
6358
xcodeVersion, err := g.xcodeVersionReader.GetVersion()
6459
if err != nil {
6560
return nil, fmt.Errorf("failed to get Xcode version: %w", err)
6661
}
6762

68-
mainTargetBundleID, entitlementsByBundleID, err := g.applicationTargetsAndEntitlements(exportMethod)
69-
if err != nil {
70-
return nil, err
63+
// BundleIDs appear in the export options plist in:
64+
// - distributionBundleIdentifier: can be the main app or the app Clip bundle ID.
65+
// It is only valid for NON app-store-connect distribution. App Store export includes both app and app-clip in one go, others do not.
66+
// - provisioningProfiles dictionary:
67+
// When distributing an app-clip, its bundle ID needs to be in the provisioningProfiles dictionary, otherwise it needs to be removed.
68+
productToDistributeBundleID := archiveInfo.AppBundleID
69+
if exportedProduct == ExportProductAppClip {
70+
if archiveInfo.AppClipBundleID == "" {
71+
return nil, fmt.Errorf("xcarchive does not contain an App Clip, cannot export an App Clip")
72+
}
73+
74+
if exportMethod.IsAppStore() {
75+
g.logger.Warnf("Selected app-clip for distribution, but distribution method is the App Store.\n" +
76+
"Exported .app will contain both the app and the app-clip for App Store exports.\n")
77+
}
78+
productToDistributeBundleID = archiveInfo.AppClipBundleID
7179
}
7280

73-
iCloudContainerEnvironment, err := determineIcloudContainerEnvironment(containerEnvironment, entitlementsByBundleID, exportMethod, xcodeVersion.Major)
81+
if exportedProduct != ExportProductAppClip {
82+
for bundleID := range archiveInfo.EntitlementsByBundleID {
83+
if bundleID == archiveInfo.AppClipBundleID && !exportMethod.IsAppStore() {
84+
g.logger.Debugf("Filtering out App Clip target, as non App Store distribution is used: %s", bundleID)
85+
delete(archiveInfo.EntitlementsByBundleID, bundleID)
86+
}
87+
}
88+
}
89+
90+
iCloudContainerEnvironment, err := determineIcloudContainerEnvironment(opts.ContainerEnvironment, archiveInfo.EntitlementsByBundleID, exportMethod, xcodeVersion.Major)
7491
if err != nil {
7592
return nil, err
7693
}
7794

78-
exportOpts := generateBaseExportOptions(exportMethod, xcodeVersion, uploadBitcode, compileBitcode, iCloudContainerEnvironment)
95+
exportOpts := generateBaseExportOptions(exportMethod, xcodeVersion, opts.UploadBitcode, opts.CompileBitcode, iCloudContainerEnvironment)
7996

8097
if xcodeVersion.Major >= 12 {
81-
exportOpts = addDistributionBundleIdentifierFromXcode12(exportOpts, mainTargetBundleID)
98+
exportOpts = addDistributionBundleIdentifierFromXcode12(exportOpts, productToDistributeBundleID)
8299
}
83100

84101
if xcodeVersion.Major >= 13 {
85-
exportOpts = disableManagedBuildNumberFromXcode13(exportOpts)
102+
exportOpts = addManagedBuildNumberFromXcode13(exportOpts, opts.ManageVersionAndBuildNumber)
86103
}
87104

88105
if codeSigningStyle == exportoptions.SigningStyleAutomatic {
89-
exportOpts = addTeamID(exportOpts, teamID)
106+
exportOpts = addTeamID(exportOpts, opts.TeamID)
90107
} else {
91-
codeSignGroup, err := g.determineCodesignGroup(entitlementsByBundleID, exportMethod, teamID, archivedWithXcodeManagedProfiles)
108+
codeSignGroup, err := g.determineCodesignGroup(archiveInfo.EntitlementsByBundleID, exportMethod, opts.TeamID, opts.ArchivedWithXcodeManagedProfiles)
92109
if err != nil {
93110
return nil, err
94111
}
95112
if codeSignGroup == nil {
96113
return exportOpts, nil
97114
}
98115

99-
exportOpts = addManualSigningFields(exportOpts, codeSignGroup, archivedWithXcodeManagedProfiles, g.logger)
116+
exportOpts = addManualSigningFields(exportOpts, codeSignGroup, opts.ArchivedWithXcodeManagedProfiles, g.logger)
100117
}
101118

102119
if xcodeVersion.Major >= 15 {
103-
if testFlightInternalTestingOnly {
104-
exportOpts = addTestFlightInternalTestingOnly(exportOpts, testFlightInternalTestingOnly)
120+
if opts.TestFlightInternalTestingOnly {
121+
exportOpts = addTestFlightInternalTestingOnly(exportOpts, opts.TestFlightInternalTestingOnly)
105122
}
106123
}
107124

108125
return exportOpts, nil
109126
}
110127

111-
func (g ExportOptionsGenerator) applicationTargetsAndEntitlements(exportMethod exportoptions.Method) (string, map[string]plistutil.PlistData, error) {
112-
mainTarget, err := ArchivableApplicationTarget(g.xcodeProj, g.scheme)
113-
if err != nil {
114-
return "", nil, err
115-
}
116-
117-
dependentTargets := filterApplicationBundleTargets(
118-
g.xcodeProj.DependentTargetsOfTarget(*mainTarget),
119-
exportMethod,
120-
)
121-
targets := append([]xcodeproj.Target{*mainTarget}, dependentTargets...)
122-
123-
var mainTargetBundleID string
124-
entitlementsByBundleID := map[string]plistutil.PlistData{}
125-
for i, target := range targets {
126-
bundleID, err := g.targetInfoProvider.TargetBundleID(target.Name, g.configuration)
127-
if err != nil {
128-
return "", nil, fmt.Errorf("failed to get target (%s) bundle id: %s", target.Name, err)
129-
}
130-
131-
entitlements, err := g.targetInfoProvider.TargetCodeSignEntitlements(target.Name, g.configuration)
132-
if err != nil && !serialized.IsKeyNotFoundError(err) {
133-
return "", nil, fmt.Errorf("failed to get target (%s) bundle id: %s", target.Name, err)
134-
}
135-
136-
entitlementsByBundleID[bundleID] = plistutil.PlistData(entitlements)
137-
138-
if i == 0 {
139-
mainTargetBundleID = bundleID
140-
}
141-
}
142-
143-
return mainTargetBundleID, entitlementsByBundleID, nil
144-
}
145-
146128
// determineCodesignGroup finds the best codesign group (certificate + profiles)
147129
// based on the installed Provisioning Profiles and Codesign Certificates.
148130
func (g ExportOptionsGenerator) determineCodesignGroup(bundleIDEntitlementsMap map[string]plistutil.PlistData, exportMethod exportoptions.Method, teamID string, xcodeManaged bool) (*export.IosCodeSignGroup, error) {
@@ -376,10 +358,10 @@ func addDistributionBundleIdentifierFromXcode12(exportOpts exportoptions.ExportO
376358
return nil
377359
}
378360

379-
func disableManagedBuildNumberFromXcode13(exportOpts exportoptions.ExportOptions) exportoptions.ExportOptions {
361+
func addManagedBuildNumberFromXcode13(exportOpts exportoptions.ExportOptions, isManageAppVersion bool) exportoptions.ExportOptions {
380362
switch options := exportOpts.(type) {
381363
case exportoptions.AppStoreOptionsModel:
382-
options.ManageAppVersion = false // Only available for app-store exports
364+
options.ManageAppVersion = isManageAppVersion // Only available for app-store exports
383365

384366
return options
385367
}

0 commit comments

Comments
 (0)