diff --git a/exportoptionsgenerator/certificates.go b/exportoptionsgenerator/certificates.go
new file mode 100644
index 00000000..d916ce54
--- /dev/null
+++ b/exportoptionsgenerator/certificates.go
@@ -0,0 +1,21 @@
+package exportoptionsgenerator
+
+import "github.com/bitrise-io/go-xcode/certificateutil"
+
+// CodesignIdentityProvider can list certificate infos.
+type CodesignIdentityProvider interface {
+ ListCodesignIdentities() ([]certificateutil.CertificateInfoModel, error)
+}
+
+// LocalCodesignIdentityProvider ...
+type LocalCodesignIdentityProvider struct{}
+
+// ListCodesignIdentities ...
+func (p LocalCodesignIdentityProvider) ListCodesignIdentities() ([]certificateutil.CertificateInfoModel, error) {
+ certs, err := certificateutil.InstalledCodesigningCertificateInfos()
+ if err != nil {
+ return nil, err
+ }
+ certInfo := certificateutil.FilterValidCertificateInfos(certs)
+ return append(certInfo.ValidCertificates, certInfo.DuplicatedCertificates...), nil
+}
diff --git a/exportoptionsgenerator/exportoptionsgenerator.go b/exportoptionsgenerator/exportoptionsgenerator.go
index 108d3e57..28d2c652 100644
--- a/exportoptionsgenerator/exportoptionsgenerator.go
+++ b/exportoptionsgenerator/exportoptionsgenerator.go
@@ -2,15 +2,10 @@ package exportoptionsgenerator
import (
"fmt"
- "io"
- "net/http"
"os"
- "path/filepath"
- "github.com/bitrise-io/go-utils/pathutil"
"github.com/bitrise-io/go-utils/sliceutil"
"github.com/bitrise-io/go-utils/v2/log"
- "github.com/bitrise-io/go-xcode/certificateutil"
"github.com/bitrise-io/go-xcode/export"
"github.com/bitrise-io/go-xcode/exportoptions"
"github.com/bitrise-io/go-xcode/plistutil"
@@ -20,10 +15,9 @@ import (
"github.com/bitrise-io/go-xcode/xcodeproject/xcscheme"
)
-// const for AppClipProductType and manualSigningStyle
const (
+ // AppClipProductType ...
AppClipProductType = "com.apple.product-type.application.on-demand-install-capable"
- manualSigningStyle = "manual"
)
// ExportOptionsGenerator generates an exportOptions.plist file.
@@ -53,14 +47,57 @@ func New(xcodeProj *xcodeproj.XcodeProj, scheme *xcscheme.Scheme, configuration
}
// GenerateApplicationExportOptions generates exportOptions for an application export.
-func (g ExportOptionsGenerator) GenerateApplicationExportOptions(exportMethod exportoptions.Method, containerEnvironment string, teamID string, uploadBitcode bool, compileBitcode bool, xcodeManaged bool,
- xcodeMajorVersion int64) (exportoptions.ExportOptions, error) {
+func (g ExportOptionsGenerator) GenerateApplicationExportOptions(
+ exportMethod exportoptions.Method,
+ containerEnvironment string,
+ teamID string,
+ uploadBitcode bool,
+ compileBitcode bool,
+ archivedWithXcodeManagedProfiles bool,
+ codeSigningStyle exportoptions.SigningStyle,
+ xcodeMajorVersion int64,
+) (exportoptions.ExportOptions, error) {
+ mainTargetBundleID, entitlementsByBundleID, err := g.applicationTargetsAndEntitlements(exportMethod)
+ if err != nil {
+ return nil, err
+ }
+
+ iCloudContainerEnvironment, err := determineIcloudContainerEnvironment(containerEnvironment, entitlementsByBundleID, exportMethod, xcodeMajorVersion)
+ if err != nil {
+ return nil, err
+ }
+
+ exportOpts := generateBaseExportOptions(exportMethod, uploadBitcode, compileBitcode, iCloudContainerEnvironment)
+
+ if xcodeMajorVersion >= 12 {
+ exportOpts = addDistributionBundleIdentifierFromXcode12(exportOpts, mainTargetBundleID)
+ }
+
+ if xcodeMajorVersion >= 13 {
+ exportOpts = disableManagedBuildNumberFromXcode13(exportOpts)
+ }
- g.logger.TDebugf("Generating application export options for: %s", exportMethod)
+ if codeSigningStyle == exportoptions.SigningStyleAutomatic {
+ exportOpts = addTeamID(exportOpts, teamID)
+ } else {
+ codeSignGroup, err := g.determineCodesignGroup(entitlementsByBundleID, exportMethod, teamID, archivedWithXcodeManagedProfiles)
+ if err != nil {
+ return nil, err
+ }
+ if codeSignGroup == nil {
+ return exportOpts, nil
+ }
+
+ exportOpts = addManualSigningFields(exportOpts, codeSignGroup, archivedWithXcodeManagedProfiles, g.logger)
+ }
+
+ return exportOpts, nil
+}
+func (g ExportOptionsGenerator) applicationTargetsAndEntitlements(exportMethod exportoptions.Method) (string, map[string]plistutil.PlistData, error) {
mainTarget, err := ArchivableApplicationTarget(g.xcodeProj, g.scheme)
if err != nil {
- return nil, err
+ return "", nil, err
}
dependentTargets := filterApplicationBundleTargets(
@@ -74,12 +111,12 @@ func (g ExportOptionsGenerator) GenerateApplicationExportOptions(exportMethod ex
for i, target := range targets {
bundleID, err := g.targetInfoProvider.TargetBundleID(target.Name, g.configuration)
if err != nil {
- return nil, fmt.Errorf("failed to get target (%s) bundle id: %s", target.Name, err)
+ return "", nil, fmt.Errorf("failed to get target (%s) bundle id: %s", target.Name, err)
}
entitlements, err := g.targetInfoProvider.TargetCodeSignEntitlements(target.Name, g.configuration)
if err != nil && !serialized.IsKeyNotFoundError(err) {
- return nil, fmt.Errorf("failed to get target (%s) bundle id: %s", target.Name, err)
+ return "", nil, fmt.Errorf("failed to get target (%s) bundle id: %s", target.Name, err)
}
entitlementsByBundleID[bundleID] = plistutil.PlistData(entitlements)
@@ -89,177 +126,7 @@ func (g ExportOptionsGenerator) GenerateApplicationExportOptions(exportMethod ex
}
}
- g.logger.TDebugf("Generated application export options plist for: %s", exportMethod)
-
- return g.generateExportOptions(exportMethod, containerEnvironment, teamID, uploadBitcode, compileBitcode,
- xcodeManaged, entitlementsByBundleID, xcodeMajorVersion, mainTargetBundleID)
-}
-
-// TargetInfoProvider can determine a target's bundle id and codesign entitlements.
-type TargetInfoProvider interface {
- TargetBundleID(target, configuration string) (string, error)
- TargetCodeSignEntitlements(target, configuration string) (serialized.Object, error)
-}
-
-// XcodebuildTargetInfoProvider implements TargetInfoProvider.
-type XcodebuildTargetInfoProvider struct {
- xcodeProj *xcodeproj.XcodeProj
-}
-
-// TargetBundleID ...
-func (b XcodebuildTargetInfoProvider) TargetBundleID(target, configuration string) (string, error) {
- return b.xcodeProj.TargetBundleID(target, configuration)
-}
-
-// TargetCodeSignEntitlements ...
-func (b XcodebuildTargetInfoProvider) TargetCodeSignEntitlements(target, configuration string) (serialized.Object, error) {
- return b.xcodeProj.TargetCodeSignEntitlements(target, configuration)
-}
-
-// ArchivableApplicationTarget locate archivable app target from a given project and scheme
-func ArchivableApplicationTarget(xcodeProj *xcodeproj.XcodeProj, scheme *xcscheme.Scheme) (*xcodeproj.Target, error) {
- archiveEntry, ok := scheme.AppBuildActionEntry()
- if !ok {
- return nil, fmt.Errorf("archivable entry not found in project: %s for scheme: %s", xcodeProj.Path, scheme.Name)
- }
-
- mainTarget, ok := xcodeProj.Proj.Target(archiveEntry.BuildableReference.BlueprintIdentifier)
- if !ok {
- return nil, fmt.Errorf("target not found: %s", archiveEntry.BuildableReference.BlueprintIdentifier)
- }
-
- return &mainTarget, nil
-}
-
-func filterApplicationBundleTargets(targets []xcodeproj.Target, exportMethod exportoptions.Method) (filteredTargets []xcodeproj.Target) {
- fmt.Printf("Filtering %v application bundle targets", len(targets))
-
- for _, target := range targets {
- if !target.IsExecutableProduct() {
- continue
- }
-
- // App store exports contain App Clip too. App Clip provisioning profile has to be included in export options:
- // ..
- // provisioningProfiles
- //
- // io.bundle.id
- // Development Application Profile
- // io.bundle.id.AppClipID
- // Development App Clip Profile
- //
- // ..,
- if exportMethod != exportoptions.MethodAppStore && target.IsAppClipProduct() {
- continue
- }
-
- filteredTargets = append(filteredTargets, target)
- }
-
- fmt.Printf("Found %v application bundle targets", len(filteredTargets))
-
- return
-}
-
-// projectUsesCloudKit determines whether the project uses any CloudKit capability or not.
-func projectUsesCloudKit(bundleIDEntitlementsMap map[string]plistutil.PlistData) bool {
- fmt.Printf("Checking if project uses CloudKit")
-
- for _, entitlements := range bundleIDEntitlementsMap {
- if entitlements == nil {
- continue
- }
-
- services, ok := entitlements.GetStringArray("com.apple.developer.icloud-services")
- if !ok {
- continue
- }
-
- if sliceutil.IsStringInSlice("CloudKit", services) || sliceutil.IsStringInSlice("CloudDocuments", services) {
- fmt.Printf("Project uses CloudKit")
-
- return true
- }
- }
- return false
-}
-
-// determineIcloudContainerEnvironment calculates the value of iCloudContainerEnvironment.
-func determineIcloudContainerEnvironment(desiredIcloudContainerEnvironment string, bundleIDEntitlementsMap map[string]plistutil.PlistData, exportMethod exportoptions.Method, xcodeMajorVersion int64) (string, error) {
- // iCloudContainerEnvironment: If the app is using CloudKit, this configures the "com.apple.developer.icloud-container-environment" entitlement.
- // Available options vary depending on the type of provisioning profile used, but may include: Development and Production.
- usesCloudKit := projectUsesCloudKit(bundleIDEntitlementsMap)
- if !usesCloudKit {
- return "", nil
- }
-
- // From Xcode 9 iCloudContainerEnvironment is required for every export method, before that version only for non app-store exports.
- if xcodeMajorVersion < 9 && exportMethod == exportoptions.MethodAppStore {
- return "", nil
- }
-
- if exportMethod == exportoptions.MethodAppStore {
- return "Production", nil
- }
-
- if desiredIcloudContainerEnvironment == "" {
- return "", fmt.Errorf("Your project uses CloudKit but \"iCloud container environment\" input not specified.\n"+
- "Export method is: %s (For app-store export method Production container environment is implied.)", exportMethod)
- }
-
- return desiredIcloudContainerEnvironment, nil
-}
-
-// generateBaseExportOptions creates a default exportOptions introudced in Xcode 7.
-func generateBaseExportOptions(exportMethod exportoptions.Method, cfgUploadBitcode, cfgCompileBitcode bool, iCloudContainerEnvironment string) exportoptions.ExportOptions {
- if exportMethod == exportoptions.MethodAppStore {
- appStoreOptions := exportoptions.NewAppStoreOptions()
- appStoreOptions.UploadBitcode = cfgUploadBitcode
- if iCloudContainerEnvironment != "" {
- appStoreOptions.ICloudContainerEnvironment = exportoptions.ICloudContainerEnvironment(iCloudContainerEnvironment)
- }
- return appStoreOptions
- }
-
- nonAppStoreOptions := exportoptions.NewNonAppStoreOptions(exportMethod)
- nonAppStoreOptions.CompileBitcode = cfgCompileBitcode
-
- if iCloudContainerEnvironment != "" {
- nonAppStoreOptions.ICloudContainerEnvironment = exportoptions.ICloudContainerEnvironment(iCloudContainerEnvironment)
- }
-
- return nonAppStoreOptions
-}
-
-// CodesignIdentityProvider can list certificate infos.
-type CodesignIdentityProvider interface {
- ListCodesignIdentities() ([]certificateutil.CertificateInfoModel, error)
-}
-
-// LocalCodesignIdentityProvider ...
-type LocalCodesignIdentityProvider struct{}
-
-// ListCodesignIdentities ...
-func (p LocalCodesignIdentityProvider) ListCodesignIdentities() ([]certificateutil.CertificateInfoModel, error) {
- certs, err := certificateutil.InstalledCodesigningCertificateInfos()
- if err != nil {
- return nil, err
- }
- certInfo := certificateutil.FilterValidCertificateInfos(certs)
- return append(certInfo.ValidCertificates, certInfo.DuplicatedCertificates...), nil
-}
-
-// ProvisioningProfileProvider can list profile infos.
-type ProvisioningProfileProvider interface {
- ListProvisioningProfiles() ([]profileutil.ProvisioningProfileInfoModel, error)
-}
-
-// LocalProvisioningProfileProvider ...
-type LocalProvisioningProfileProvider struct{}
-
-// ListProvisioningProfiles ...
-func (p LocalProvisioningProfileProvider) ListProvisioningProfiles() ([]profileutil.ProvisioningProfileInfoModel, error) {
- return profileutil.InstalledProvisioningProfileInfos(profileutil.ProfileTypeIos)
+ return mainTargetBundleID, entitlementsByBundleID, nil
}
// determineCodesignGroup finds the best codesign group (certificate + profiles)
@@ -283,7 +150,7 @@ func (g ExportOptionsGenerator) determineCodesignGroup(bundleIDEntitlementsMap m
certs, err := g.certificateProvider.ListCodesignIdentities()
if err != nil {
- return nil, fmt.Errorf("Failed to get installed certificates, error: %s", err)
+ return nil, fmt.Errorf("failed to get installed certificates: %w", err)
}
g.logger.Debugf("Installed certificates:")
@@ -293,7 +160,7 @@ func (g ExportOptionsGenerator) determineCodesignGroup(bundleIDEntitlementsMap m
profs, err := g.profileProvider.ListProvisioningProfiles()
if err != nil {
- return nil, fmt.Errorf("Failed to get installed provisioning profiles, error: %s", err)
+ return nil, fmt.Errorf("failed to get installed provisioning profiles: %w", err)
}
g.logger.Debugf("Installed profiles:")
@@ -358,7 +225,7 @@ func (g ExportOptionsGenerator) determineCodesignGroup(bundleIDEntitlementsMap m
defaultProfileURL := os.Getenv("BITRISE_DEFAULT_PROVISION_URL")
if teamID == "" && defaultProfileURL != "" {
- if defaultProfile, err := g.GetDefaultProvisioningProfile(); err == nil {
+ if defaultProfile, err := g.profileProvider.GetDefaultProvisioningProfile(); err == nil {
g.logger.Debugf("\ndefault profile: %v\n", defaultProfile)
filteredCodeSignGroups := export.FilterSelectableCodeSignGroups(codeSignGroups,
export.CreateExcludeProfileNameSelectableCodeSignGroupFilter(defaultProfile.Name))
@@ -408,6 +275,76 @@ func (g ExportOptionsGenerator) determineCodesignGroup(bundleIDEntitlementsMap m
return &iosCodeSignGroups[0], nil
}
+// determineIcloudContainerEnvironment calculates the value of iCloudContainerEnvironment.
+func determineIcloudContainerEnvironment(desiredIcloudContainerEnvironment string, bundleIDEntitlementsMap map[string]plistutil.PlistData, exportMethod exportoptions.Method, xcodeMajorVersion int64) (string, error) {
+ // iCloudContainerEnvironment: If the app is using CloudKit, this configures the "com.apple.developer.icloud-container-environment" entitlement.
+ // Available options vary depending on the type of provisioning profile used, but may include: Development and Production.
+ usesCloudKit := projectUsesCloudKit(bundleIDEntitlementsMap)
+ if !usesCloudKit {
+ return "", nil
+ }
+
+ // From Xcode 9 iCloudContainerEnvironment is required for every export method, before that version only for non app-store exports.
+ if xcodeMajorVersion < 9 && exportMethod == exportoptions.MethodAppStore {
+ return "", nil
+ }
+
+ if exportMethod == exportoptions.MethodAppStore {
+ return "Production", nil
+ }
+
+ if desiredIcloudContainerEnvironment == "" {
+ return "", fmt.Errorf("Your project uses CloudKit but \"iCloud container environment\" input not specified.\n"+
+ "Export method is: %s (For app-store export method Production container environment is implied.)", exportMethod)
+ }
+
+ return desiredIcloudContainerEnvironment, nil
+}
+
+// projectUsesCloudKit determines whether the project uses any CloudKit capability or not.
+func projectUsesCloudKit(bundleIDEntitlementsMap map[string]plistutil.PlistData) bool {
+ fmt.Printf("Checking if project uses CloudKit")
+
+ for _, entitlements := range bundleIDEntitlementsMap {
+ if entitlements == nil {
+ continue
+ }
+
+ services, ok := entitlements.GetStringArray("com.apple.developer.icloud-services")
+ if !ok {
+ continue
+ }
+
+ if sliceutil.IsStringInSlice("CloudKit", services) || sliceutil.IsStringInSlice("CloudDocuments", services) {
+ fmt.Printf("Project uses CloudKit")
+
+ return true
+ }
+ }
+ return false
+}
+
+// generateBaseExportOptions creates a default exportOptions introduced in Xcode 7.
+func generateBaseExportOptions(exportMethod exportoptions.Method, cfgUploadBitcode, cfgCompileBitcode bool, iCloudContainerEnvironment string) exportoptions.ExportOptions {
+ if exportMethod == exportoptions.MethodAppStore {
+ appStoreOptions := exportoptions.NewAppStoreOptions()
+ appStoreOptions.UploadBitcode = cfgUploadBitcode
+ if iCloudContainerEnvironment != "" {
+ appStoreOptions.ICloudContainerEnvironment = exportoptions.ICloudContainerEnvironment(iCloudContainerEnvironment)
+ }
+ return appStoreOptions
+ }
+
+ nonAppStoreOptions := exportoptions.NewNonAppStoreOptions(exportMethod)
+ nonAppStoreOptions.CompileBitcode = cfgCompileBitcode
+
+ if iCloudContainerEnvironment != "" {
+ nonAppStoreOptions.ICloudContainerEnvironment = exportoptions.ICloudContainerEnvironment(iCloudContainerEnvironment)
+ }
+
+ return nonAppStoreOptions
+}
+
func addDistributionBundleIdentifierFromXcode12(exportOpts exportoptions.ExportOptions, distributionBundleIdentifier string) exportoptions.ExportOptions {
switch options := exportOpts.(type) {
case exportoptions.AppStoreOptionsModel:
@@ -432,66 +369,47 @@ func disableManagedBuildNumberFromXcode13(exportOpts exportoptions.ExportOptions
return exportOpts
}
-// generateExportOptions generates an exportOptions based on the provided conditions.
-func (g ExportOptionsGenerator) generateExportOptions(exportMethod exportoptions.Method, containerEnvironment string, teamID string, uploadBitcode bool, compileBitcode bool, xcodeManaged bool,
- bundleIDEntitlementsMap map[string]plistutil.PlistData, xcodeMajorVersion int64, distributionBundleIdentifier string) (exportoptions.ExportOptions, error) {
- g.logger.TDebugf("Generating export options")
-
- iCloudContainerEnvironment, err := determineIcloudContainerEnvironment(containerEnvironment, bundleIDEntitlementsMap, exportMethod, xcodeMajorVersion)
- if err != nil {
- return nil, err
- }
-
- g.logger.Printf("Adding bundle id")
-
- exportOpts := generateBaseExportOptions(exportMethod, uploadBitcode, compileBitcode, iCloudContainerEnvironment)
- if xcodeMajorVersion >= 12 {
- exportOpts = addDistributionBundleIdentifierFromXcode12(exportOpts, distributionBundleIdentifier)
- }
- if xcodeMajorVersion >= 13 {
- exportOpts = disableManagedBuildNumberFromXcode13(exportOpts)
- }
-
- g.logger.TDebugf("Determining code signing group")
-
- codeSignGroup, err := g.determineCodesignGroup(bundleIDEntitlementsMap, exportMethod, teamID, xcodeManaged)
- if err != nil {
- return nil, err
- }
- if codeSignGroup == nil {
- return exportOpts, nil
+func addTeamID(exportOpts exportoptions.ExportOptions, teamID string) exportoptions.ExportOptions {
+ switch options := exportOpts.(type) {
+ case exportoptions.AppStoreOptionsModel:
+ options.TeamID = teamID
+ return options
+ case exportoptions.NonAppStoreOptionsModel:
+ options.TeamID = teamID
+ return options
}
+ return exportOpts
+}
+func addManualSigningFields(exportOpts exportoptions.ExportOptions, codeSignGroup *export.IosCodeSignGroup, archivedWithXcodeManagedProfiles bool, logger log.Logger) exportoptions.ExportOptions {
exportCodeSignStyle := ""
exportProfileMapping := map[string]string{}
- g.logger.TDebugf("Determining code signing style")
-
for bundleID, profileInfo := range codeSignGroup.BundleIDProfileMap() {
exportProfileMapping[bundleID] = profileInfo.Name
isXcodeManaged := profileutil.IsXcodeManaged(profileInfo.Name)
if isXcodeManaged {
if exportCodeSignStyle != "" && exportCodeSignStyle != "automatic" {
- g.logger.Errorf("Both Xcode managed and NON Xcode managed profiles in code signing group")
+ logger.Errorf("Both Xcode managed and NON Xcode managed profiles in code signing group")
}
exportCodeSignStyle = "automatic"
} else {
- if exportCodeSignStyle != "" && exportCodeSignStyle != manualSigningStyle {
- g.logger.Errorf("Both Xcode managed and NON Xcode managed profiles in code signing group")
+ if exportCodeSignStyle != "" && exportCodeSignStyle != string(exportoptions.SigningStyleManual) {
+ logger.Errorf("Both Xcode managed and NON Xcode managed profiles in code signing group")
}
- exportCodeSignStyle = manualSigningStyle
+ exportCodeSignStyle = string(exportoptions.SigningStyleManual)
}
}
- shouldSetManualSigning := xcodeManaged && exportCodeSignStyle == manualSigningStyle
+ shouldSetManualSigning := archivedWithXcodeManagedProfiles && exportCodeSignStyle == string(exportoptions.SigningStyleManual)
if shouldSetManualSigning {
- g.logger.Warnf("App was signed with Xcode managed profile when archiving,")
- g.logger.Warnf("ipa export uses manual code signing.")
- g.logger.Warnf(`Setting "signingStyle" to "manual".`)
+ logger.Warnf("App was signed with Xcode managed profile when archiving,")
+ logger.Warnf("ipa export uses manual code signing.")
+ logger.Warnf(`Setting "signingStyle" to "manual".`)
}
- g.logger.TDebugf("Determined code signing style")
+ logger.TDebugf("Determined code signing style")
switch options := exportOpts.(type) {
case exportoptions.AppStoreOptionsModel:
@@ -500,7 +418,7 @@ func (g ExportOptionsGenerator) generateExportOptions(exportMethod exportoptions
options.TeamID = codeSignGroup.Certificate().TeamID
if shouldSetManualSigning {
- options.SigningStyle = manualSigningStyle
+ options.SigningStyle = exportoptions.SigningStyleManual
}
exportOpts = options
case exportoptions.NonAppStoreOptionsModel:
@@ -509,55 +427,10 @@ func (g ExportOptionsGenerator) generateExportOptions(exportMethod exportoptions
options.TeamID = codeSignGroup.Certificate().TeamID
if shouldSetManualSigning {
- options.SigningStyle = manualSigningStyle
+ options.SigningStyle = exportoptions.SigningStyleManual
}
exportOpts = options
}
- return exportOpts, nil
-}
-
-// GetDefaultProvisioningProfile ...
-func (g ExportOptionsGenerator) GetDefaultProvisioningProfile() (profileutil.ProvisioningProfileInfoModel, error) {
- defaultProfileURL := os.Getenv("BITRISE_DEFAULT_PROVISION_URL")
- if defaultProfileURL == "" {
- return profileutil.ProvisioningProfileInfoModel{}, nil
- }
-
- tmpDir, err := pathutil.NormalizedOSTempDirPath("tmp_default_profile")
- if err != nil {
- return profileutil.ProvisioningProfileInfoModel{}, err
- }
-
- tmpDst := filepath.Join(tmpDir, "default.mobileprovision")
- tmpDstFile, err := os.Create(tmpDst)
- if err != nil {
- return profileutil.ProvisioningProfileInfoModel{}, err
- }
- defer func() {
- if err := tmpDstFile.Close(); err != nil {
- g.logger.Errorf("Failed to close file (%s), error: %s", tmpDst, err)
- }
- }()
-
- response, err := http.Get(defaultProfileURL)
- if err != nil {
- return profileutil.ProvisioningProfileInfoModel{}, err
- }
- defer func() {
- if err := response.Body.Close(); err != nil {
- g.logger.Errorf("Failed to close response body, error: %s", err)
- }
- }()
-
- if _, err := io.Copy(tmpDstFile, response.Body); err != nil {
- return profileutil.ProvisioningProfileInfoModel{}, err
- }
-
- defaultProfile, err := profileutil.NewProvisioningProfileInfoFromFile(tmpDst)
- if err != nil {
- return profileutil.ProvisioningProfileInfoModel{}, err
- }
-
- return defaultProfile, nil
+ return exportOpts
}
diff --git a/exportoptionsgenerator/exportoptionsgenerator_test.go b/exportoptionsgenerator/exportoptionsgenerator_test.go
index dd0eaf56..19914071 100644
--- a/exportoptionsgenerator/exportoptionsgenerator_test.go
+++ b/exportoptionsgenerator/exportoptionsgenerator_test.go
@@ -15,7 +15,7 @@ import (
)
const (
- expectedDevelopementXcode11ExportOptions = `
+ expectedDevelopmentXcode11ExportOptions = `
@@ -34,7 +34,7 @@ const (
TEAM123
`
- expectedDevelopementExportOptions = `
+ expectedDevelopmentExportOptions = `
@@ -120,7 +120,7 @@ const (
TEAM123
`
- expectedNoProfilesDevelopementXcode11ExportOptions = `
+ expectedNoProfilesDevelopmentXcode11ExportOptions = `
@@ -130,7 +130,7 @@ const (
development
`
- expectedNoProfilesXcode13AppStorExportOptions = `
+ expectedNoProfilesXcode13AppStoreExportOptions = `
@@ -156,6 +156,133 @@ const (
`
)
+func TestExportOptionsGenerator_GenerateApplicationExportOptions_ForAutomaticSigningStyle(t *testing.T) {
+ // Arrange
+ const (
+ bundleID = "io.bundle.id"
+ teamID = "TEAM123"
+ )
+
+ logger := log.NewLogger()
+ logger.EnableDebugLog(true)
+
+ tests := []struct {
+ name string
+ generatorFactory func() ExportOptionsGenerator
+ exportMethod exportoptions.Method
+ containerEnvironment string
+ xcodeVersion int64
+ want string
+ wantErr bool
+ }{
+ {
+ name: "Default development exportOptions",
+ exportMethod: exportoptions.MethodDevelopment,
+ xcodeVersion: 15,
+ generatorFactory: func() ExportOptionsGenerator {
+ applicationTarget := givenApplicationTarget(nil)
+ xcodeProj := givenXcodeproj([]xcodeproj.Target{applicationTarget})
+ scheme := givenScheme(applicationTarget)
+
+ g := New(&xcodeProj, &scheme, "", logger)
+ g.targetInfoProvider = MockTargetInfoProvider{
+ bundleID: map[string]string{"Application": bundleID},
+ }
+
+ return g
+ },
+ want: `
+
+
+
+ distributionBundleIdentifier
+ io.bundle.id
+ method
+ development
+ teamID
+ TEAM123
+
+`,
+ },
+ {
+ name: "Default app store exportOptions",
+ exportMethod: exportoptions.MethodAppStore,
+ xcodeVersion: 15,
+ generatorFactory: func() ExportOptionsGenerator {
+ applicationTarget := givenApplicationTarget(nil)
+ xcodeProj := givenXcodeproj([]xcodeproj.Target{applicationTarget})
+ scheme := givenScheme(applicationTarget)
+
+ g := New(&xcodeProj, &scheme, "", logger)
+ g.targetInfoProvider = MockTargetInfoProvider{
+ bundleID: map[string]string{"Application": bundleID},
+ }
+
+ return g
+ },
+ want: `
+
+
+
+ manageAppVersionAndBuildNumber
+
+ method
+ app-store
+ teamID
+ TEAM123
+
+`,
+ },
+ {
+ name: "When the app uses iCloud services",
+ exportMethod: exportoptions.MethodDevelopment,
+ containerEnvironment: string(exportoptions.ICloudContainerEnvironmentProduction),
+ xcodeVersion: 15,
+ generatorFactory: func() ExportOptionsGenerator {
+ applicationTarget := givenApplicationTarget(nil)
+ xcodeProj := givenXcodeproj([]xcodeproj.Target{applicationTarget})
+ scheme := givenScheme(applicationTarget)
+
+ g := New(&xcodeProj, &scheme, "", logger)
+ g.targetInfoProvider = MockTargetInfoProvider{
+ bundleID: map[string]string{"Application": bundleID},
+ codesignEntitlements: map[string]serialized.Object{"Application": map[string]interface{}{"com.apple.developer.icloud-services": []string{"CloudKit"}}},
+ }
+
+ return g
+ },
+ want: `
+
+
+
+ distributionBundleIdentifier
+ io.bundle.id
+ iCloudContainerEnvironment
+ Production
+ method
+ development
+ teamID
+ TEAM123
+
+`,
+ },
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ // Act
+ gotOpts, err := tt.generatorFactory().GenerateApplicationExportOptions(tt.exportMethod, tt.containerEnvironment, teamID, true, true, false, exportoptions.SigningStyleAutomatic, tt.xcodeVersion)
+
+ // Assert
+ require.NoError(t, err)
+
+ got, err := gotOpts.String()
+ require.NoError(t, err)
+ fmt.Println(got)
+ require.Equal(t, tt.want, got)
+ })
+ }
+}
+
func TestExportOptionsGenerator_GenerateApplicationExportOptions(t *testing.T) {
const (
bundleID = "io.bundle.id"
@@ -176,13 +303,13 @@ func TestExportOptionsGenerator_GenerateApplicationExportOptions(t *testing.T) {
name: "Development Xcode 11",
exportMethod: exportoptions.MethodDevelopment,
xcodeVersion: 11,
- want: expectedDevelopementXcode11ExportOptions,
+ want: expectedDevelopmentXcode11ExportOptions,
},
{
name: "Development Xcode > 12",
exportMethod: exportoptions.MethodDevelopment,
xcodeVersion: 13,
- want: expectedDevelopementExportOptions,
+ want: expectedDevelopmentExportOptions,
},
{
name: "Ad-hoc",
@@ -245,7 +372,7 @@ func TestExportOptionsGenerator_GenerateApplicationExportOptions(t *testing.T) {
}
// Act
- gotOpts, err := g.GenerateApplicationExportOptions(tt.exportMethod, "Production", teamID, true, true, false, tt.xcodeVersion)
+ gotOpts, err := g.GenerateApplicationExportOptions(tt.exportMethod, "Production", teamID, true, true, false, exportoptions.SigningStyleManual, tt.xcodeVersion)
// Assert
require.NoError(t, err)
@@ -278,7 +405,7 @@ func TestExportOptionsGenerator_GenerateApplicationExportOptions_WhenNoProfileFo
name: "When no profiles found, Xcode 13, then manageAppVersionAndBuildNumber is included",
exportMethod: exportoptions.MethodAppStore,
xcodeVersion: 13,
- want: expectedNoProfilesXcode13AppStorExportOptions,
+ want: expectedNoProfilesXcode13AppStoreExportOptions,
},
{
name: "When no profiles found, Xcode > 12, distributionBundleIdentifier included",
@@ -290,7 +417,7 @@ func TestExportOptionsGenerator_GenerateApplicationExportOptions_WhenNoProfileFo
name: "When no profiles found, Xcode 11",
exportMethod: exportoptions.MethodDevelopment,
xcodeVersion: 11,
- want: expectedNoProfilesDevelopementXcode11ExportOptions,
+ want: expectedNoProfilesDevelopmentXcode11ExportOptions,
},
}
for _, tt := range tests {
@@ -315,7 +442,7 @@ func TestExportOptionsGenerator_GenerateApplicationExportOptions_WhenNoProfileFo
}
// Act
- gotOpts, err := g.GenerateApplicationExportOptions(tt.exportMethod, "Production", teamID, true, true, false, tt.xcodeVersion)
+ gotOpts, err := g.GenerateApplicationExportOptions(tt.exportMethod, "Production", teamID, true, true, false, exportoptions.SigningStyleManual, tt.xcodeVersion)
// Assert
require.NoError(t, err)
@@ -344,6 +471,10 @@ func (p MockProvisioningProfileProvider) ListProvisioningProfiles() ([]profileut
return p.profileInfos, nil
}
+func (p MockProvisioningProfileProvider) GetDefaultProvisioningProfile() (profileutil.ProvisioningProfileInfoModel, error) {
+ return profileutil.ProvisioningProfileInfoModel{}, nil
+}
+
type MockTargetInfoProvider struct {
bundleID map[string]string
codesignEntitlements map[string]serialized.Object
diff --git a/exportoptionsgenerator/profiles.go b/exportoptionsgenerator/profiles.go
new file mode 100644
index 00000000..e9b3928e
--- /dev/null
+++ b/exportoptionsgenerator/profiles.go
@@ -0,0 +1,73 @@
+package exportoptionsgenerator
+
+import (
+ "io"
+ "net/http"
+ "os"
+ "path/filepath"
+
+ "github.com/bitrise-io/go-utils/pathutil"
+ "github.com/bitrise-io/go-utils/v2/log"
+ "github.com/bitrise-io/go-xcode/profileutil"
+)
+
+// ProvisioningProfileProvider can list profile infos.
+type ProvisioningProfileProvider interface {
+ ListProvisioningProfiles() ([]profileutil.ProvisioningProfileInfoModel, error)
+ GetDefaultProvisioningProfile() (profileutil.ProvisioningProfileInfoModel, error)
+}
+
+// LocalProvisioningProfileProvider ...
+type LocalProvisioningProfileProvider struct {
+ logger log.Logger
+}
+
+// ListProvisioningProfiles ...
+func (p LocalProvisioningProfileProvider) ListProvisioningProfiles() ([]profileutil.ProvisioningProfileInfoModel, error) {
+ return profileutil.InstalledProvisioningProfileInfos(profileutil.ProfileTypeIos)
+}
+
+// GetDefaultProvisioningProfile ...
+func (p LocalProvisioningProfileProvider) GetDefaultProvisioningProfile() (profileutil.ProvisioningProfileInfoModel, error) {
+ defaultProfileURL := os.Getenv("BITRISE_DEFAULT_PROVISION_URL")
+ if defaultProfileURL == "" {
+ return profileutil.ProvisioningProfileInfoModel{}, nil
+ }
+
+ tmpDir, err := pathutil.NormalizedOSTempDirPath("tmp_default_profile")
+ if err != nil {
+ return profileutil.ProvisioningProfileInfoModel{}, err
+ }
+
+ tmpDst := filepath.Join(tmpDir, "default.mobileprovision")
+ tmpDstFile, err := os.Create(tmpDst)
+ if err != nil {
+ return profileutil.ProvisioningProfileInfoModel{}, err
+ }
+ defer func() {
+ if err := tmpDstFile.Close(); err != nil {
+ p.logger.Warnf("Failed to close file (%s), error: %s", tmpDst, err)
+ }
+ }()
+
+ response, err := http.Get(defaultProfileURL)
+ if err != nil {
+ return profileutil.ProvisioningProfileInfoModel{}, err
+ }
+ defer func() {
+ if err := response.Body.Close(); err != nil {
+ p.logger.Warnf("Failed to close response body, error: %s", err)
+ }
+ }()
+
+ if _, err := io.Copy(tmpDstFile, response.Body); err != nil {
+ return profileutil.ProvisioningProfileInfoModel{}, err
+ }
+
+ defaultProfile, err := profileutil.NewProvisioningProfileInfoFromFile(tmpDst)
+ if err != nil {
+ return profileutil.ProvisioningProfileInfoModel{}, err
+ }
+
+ return defaultProfile, nil
+}
diff --git a/exportoptionsgenerator/targets.go b/exportoptionsgenerator/targets.go
new file mode 100644
index 00000000..fa2d9042
--- /dev/null
+++ b/exportoptionsgenerator/targets.go
@@ -0,0 +1,75 @@
+package exportoptionsgenerator
+
+import (
+ "fmt"
+ "github.com/bitrise-io/go-xcode/exportoptions"
+ "github.com/bitrise-io/go-xcode/xcodeproject/serialized"
+ "github.com/bitrise-io/go-xcode/xcodeproject/xcodeproj"
+ "github.com/bitrise-io/go-xcode/xcodeproject/xcscheme"
+)
+
+// TargetInfoProvider can determine a target's bundle id and codesign entitlements.
+type TargetInfoProvider interface {
+ TargetBundleID(target, configuration string) (string, error)
+ TargetCodeSignEntitlements(target, configuration string) (serialized.Object, error)
+}
+
+// XcodebuildTargetInfoProvider implements TargetInfoProvider.
+type XcodebuildTargetInfoProvider struct {
+ xcodeProj *xcodeproj.XcodeProj
+}
+
+// TargetBundleID ...
+func (b XcodebuildTargetInfoProvider) TargetBundleID(target, configuration string) (string, error) {
+ return b.xcodeProj.TargetBundleID(target, configuration)
+}
+
+// TargetCodeSignEntitlements ...
+func (b XcodebuildTargetInfoProvider) TargetCodeSignEntitlements(target, configuration string) (serialized.Object, error) {
+ return b.xcodeProj.TargetCodeSignEntitlements(target, configuration)
+}
+
+// ArchivableApplicationTarget locate archivable app target from a given project and scheme
+func ArchivableApplicationTarget(xcodeProj *xcodeproj.XcodeProj, scheme *xcscheme.Scheme) (*xcodeproj.Target, error) {
+ archiveEntry, ok := scheme.AppBuildActionEntry()
+ if !ok {
+ return nil, fmt.Errorf("archivable entry not found in project: %s for scheme: %s", xcodeProj.Path, scheme.Name)
+ }
+
+ mainTarget, ok := xcodeProj.Proj.Target(archiveEntry.BuildableReference.BlueprintIdentifier)
+ if !ok {
+ return nil, fmt.Errorf("target not found: %s", archiveEntry.BuildableReference.BlueprintIdentifier)
+ }
+
+ return &mainTarget, nil
+}
+
+func filterApplicationBundleTargets(targets []xcodeproj.Target, exportMethod exportoptions.Method) (filteredTargets []xcodeproj.Target) {
+ fmt.Printf("Filtering %v application bundle targets", len(targets))
+
+ for _, target := range targets {
+ if !target.IsExecutableProduct() {
+ continue
+ }
+
+ // App store exports contain App Clip too. App Clip provisioning profile has to be included in export options:
+ // ..
+ // provisioningProfiles
+ //
+ // io.bundle.id
+ // Development Application Profile
+ // io.bundle.id.AppClipID
+ // Development App Clip Profile
+ //
+ // ..,
+ if exportMethod != exportoptions.MethodAppStore && target.IsAppClipProduct() {
+ continue
+ }
+
+ filteredTargets = append(filteredTargets, target)
+ }
+
+ fmt.Printf("Found %v application bundle targets", len(filteredTargets))
+
+ return
+}
diff --git a/go.mod b/go.mod
index 29ffc8fb..b00c5057 100644
--- a/go.mod
+++ b/go.mod
@@ -7,7 +7,7 @@ require (
github.com/bitrise-io/go-steputils/v2 v2.0.0-alpha.18
github.com/bitrise-io/go-utils v1.0.12
github.com/bitrise-io/go-utils/v2 v2.0.0-alpha.23
- github.com/bitrise-io/go-xcode v1.0.18
+ github.com/bitrise-io/go-xcode v1.1.1
github.com/golang-jwt/jwt/v4 v4.5.0
github.com/google/go-querystring v1.1.0
github.com/hashicorp/go-retryablehttp v0.7.7
diff --git a/go.sum b/go.sum
index aeb45fbd..38f9a666 100644
--- a/go.sum
+++ b/go.sum
@@ -11,8 +11,8 @@ github.com/bitrise-io/go-utils v1.0.12 h1:iJV1ZpyvSA0NCte/N6x+aIQ9TrNr5sIBlcJBf0
github.com/bitrise-io/go-utils v1.0.12/go.mod h1:ZY1DI+fEpZuFpO9szgDeICM4QbqoWVt0RSY3tRI1heY=
github.com/bitrise-io/go-utils/v2 v2.0.0-alpha.23 h1:Dfh4nyZPuEtilBisidejqxBrkx9cWvbOUrpq8VEION0=
github.com/bitrise-io/go-utils/v2 v2.0.0-alpha.23/go.mod h1:3XUplo0dOWc3DqT2XA2SeHToDSg7+j1y1HTHibT2H68=
-github.com/bitrise-io/go-xcode v1.0.18 h1:guFywV/AwcZuexqIQkL1ixc3QThpbJvA4voa9MqvPto=
-github.com/bitrise-io/go-xcode v1.0.18/go.mod h1:9OwsvrhZ4A2JxHVoEY7CPcABAKA+OE7FQqFfBfvbFuY=
+github.com/bitrise-io/go-xcode v1.1.1 h1:Krfa8iYZZWdLBuH7AXbufFZwL+Pys7etqvd8+Ehdwt8=
+github.com/bitrise-io/go-xcode v1.1.1/go.mod h1:9OwsvrhZ4A2JxHVoEY7CPcABAKA+OE7FQqFfBfvbFuY=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=