diff --git a/step/platform.go b/step/platform.go index d664ae68..89b8e1cd 100644 --- a/step/platform.go +++ b/step/platform.go @@ -6,10 +6,12 @@ import ( "strings" "github.com/bitrise-io/go-utils/v2/log" + "github.com/bitrise-io/go-xcode/xcodebuild" "github.com/bitrise-io/go-xcode/xcodeproject/schemeint" "github.com/bitrise-io/go-xcode/xcodeproject/serialized" "github.com/bitrise-io/go-xcode/xcodeproject/xcodeproj" "github.com/bitrise-io/go-xcode/xcodeproject/xcscheme" + "github.com/bitrise-io/go-xcode/xcodeproject/xcworkspace" ) type Platform string @@ -23,6 +25,44 @@ const ( visionOS Platform = "visionOS" ) +// ArchivableProject represents either a workspace or a project that can be archived +type ArchivableProject interface { + BuildSettings(scheme, configuration string, customOptions ...string) (serialized.Object, error) + GetProject() (*xcodeproj.XcodeProj, error) +} + +// WorkspaceProject wraps a workspace and its main project +type WorkspaceProject struct { + Workspace xcworkspace.Workspace + XcodeProj *xcodeproj.XcodeProj +} + +func (w WorkspaceProject) BuildSettings(scheme, configuration string, customOptions ...string) (serialized.Object, error) { + return w.Workspace.SchemeBuildSettings(scheme, configuration, customOptions...) +} + +func (w WorkspaceProject) GetProject() (*xcodeproj.XcodeProj, error) { + return w.XcodeProj, nil +} + +// XcodeProjWrapper wraps a standalone project +type XcodeProjWrapper struct { + XcodeProj *xcodeproj.XcodeProj +} + +func (p XcodeProjWrapper) BuildSettings(scheme, configuration string, customOptions ...string) (serialized.Object, error) { + // For xcodeproj projects, use xcodebuild command directly with the project path + commandModel := xcodebuild.NewShowBuildSettingsCommand(p.XcodeProj.Path) + commandModel.SetScheme(scheme) + commandModel.SetConfiguration(configuration) + commandModel.SetCustomOptions(customOptions) + return commandModel.RunAndReturnSettings(true) +} + +func (p XcodeProjWrapper) GetProject() (*xcodeproj.XcodeProj, error) { + return p.XcodeProj, nil +} + func parsePlatform(platform string) (Platform, error) { switch strings.ToLower(platform) { case "detect": @@ -40,7 +80,7 @@ func parsePlatform(platform string) (Platform, error) { } } -func OpenArchivableProject(pth, schemeName, configurationName string) (*xcodeproj.XcodeProj, *xcscheme.Scheme, string, error) { +func OpenArchivableProject(pth, schemeName, configurationName string) (ArchivableProject, *xcscheme.Scheme, string, error) { scheme, schemeContainerDir, err := schemeint.Scheme(pth, schemeName) if err != nil { return nil, nil, "", fmt.Errorf("could not get scheme (%s) from path (%s): %s", schemeName, pth, err) @@ -67,33 +107,48 @@ func OpenArchivableProject(pth, schemeName, configurationName string) (*xcodepro if err != nil { return nil, nil, "", err } - return &xcodeProj, scheme, configurationName, nil + + // Check if the original path is a workspace + if strings.HasSuffix(pth, xcworkspace.XCWorkspaceExtension) { + workspace, err := xcworkspace.Open(pth) + if err != nil { + return nil, nil, "", fmt.Errorf("failed to open workspace: %s", err) + } + return WorkspaceProject{Workspace: workspace, XcodeProj: &xcodeProj}, scheme, configurationName, nil + } + + // Otherwise it's a standalone project + return XcodeProjWrapper{XcodeProj: &xcodeProj}, scheme, configurationName, nil } -type TargetBuildSettingsProvider interface { - TargetBuildSettings(xcodeProj *xcodeproj.XcodeProj, target, configuration string, customOptions ...string) (serialized.Object, error) +type BuildSettingsProvider interface { + BuildSettings(archivableProject ArchivableProject, schemeName, target, configuration string, customOptions ...string) (serialized.Object, error) } type XcodeBuild struct { } -func (x XcodeBuild) TargetBuildSettings(xcodeProj *xcodeproj.XcodeProj, target, configuration string, customOptions ...string) (serialized.Object, error) { - return xcodeProj.TargetBuildSettings(target, configuration, customOptions...) +func (x XcodeBuild) BuildSettings(archivableProject ArchivableProject, schemeName, target, configuration string, customOptions ...string) (serialized.Object, error) { + // Use the archivable project's scheme build settings method + return archivableProject.BuildSettings(schemeName, configuration, customOptions...) } func BuildableTargetPlatform( - xcodeProj *xcodeproj.XcodeProj, + archivableProject ArchivableProject, scheme *xcscheme.Scheme, configurationName string, additionalOptions []string, - provider TargetBuildSettingsProvider, + provider BuildSettingsProvider, logger log.Logger, ) (Platform, error) { - logger.Printf("Finding platform type") - archiveEntry, ok := scheme.AppBuildActionEntry() if !ok { - return "", fmt.Errorf("archivable entry not found in project: %s, scheme: %s", xcodeProj.Path, scheme.Name) + return "", fmt.Errorf("archivable entry not found in scheme: %s", scheme.Name) + } + + xcodeProj, err := archivableProject.GetProject() + if err != nil { + return "", fmt.Errorf("failed to get project: %s", err) } mainTarget, ok := xcodeProj.Proj.Target(archiveEntry.BuildableReference.BlueprintIdentifier) @@ -101,7 +156,7 @@ func BuildableTargetPlatform( return "", fmt.Errorf("target not found: %s", archiveEntry.BuildableReference.BlueprintIdentifier) } - settings, err := provider.TargetBuildSettings(xcodeProj, mainTarget.Name, configurationName, additionalOptions...) + settings, err := provider.BuildSettings(archivableProject, scheme.Name, mainTarget.Name, configurationName, additionalOptions...) if err != nil { return "", fmt.Errorf("failed to get target (%s) build settings: %s", mainTarget.Name, err) } diff --git a/step/platform_test.go b/step/platform_test.go index 3f496ad0..9def1b3c 100644 --- a/step/platform_test.go +++ b/step/platform_test.go @@ -15,9 +15,9 @@ type MockTargetBuildSettingsProvider struct { mock.Mock } -// TargetBuildSettings ... -func (m *MockTargetBuildSettingsProvider) TargetBuildSettings(xcodeProj *xcodeproj.XcodeProj, target, configuration string, customOptions ...string) (serialized.Object, error) { - args := m.Called(xcodeProj, target, configuration) +// BuildSettings ... +func (m *MockTargetBuildSettingsProvider) BuildSettings(archivableProject ArchivableProject, schemeName, target, configuration string, customOptions ...string) (serialized.Object, error) { + args := m.Called(archivableProject, schemeName, target, configuration) return args.Get(0).(serialized.Object), args.Error(1) } @@ -94,10 +94,11 @@ func TestBuildableTargetPlatform(t *testing.T) { t.Run(tt.name, func(t *testing.T) { provider := &MockTargetBuildSettingsProvider{} provider. - On("TargetBuildSettings", mock.AnythingOfType("*xcodeproj.XcodeProj"), mock.AnythingOfType("string"), mock.AnythingOfType("string")). + On("BuildSettings", mock.AnythingOfType("step.XcodeProjWrapper"), mock.AnythingOfType("string"), mock.AnythingOfType("string"), mock.AnythingOfType("string")). Return(tt.settings, nil) - got, err := BuildableTargetPlatform(tt.xcodeProj, tt.scheme, tt.configurationName, []string{}, provider, log.NewLogger()) + archivableProject := XcodeProjWrapper{XcodeProj: tt.xcodeProj} + got, err := BuildableTargetPlatform(archivableProject, tt.scheme, tt.configurationName, []string{}, provider, log.NewLogger()) if (err != nil) != tt.wantErr { t.Errorf("BuildableTargetPlatform() error = %v, wantErr %v", err, tt.wantErr) return diff --git a/step/step.go b/step/step.go index 84605697..f37fe491 100644 --- a/step/step.go +++ b/step/step.go @@ -387,7 +387,7 @@ func (s XcodebuildArchiver) Run(opts RunOpts) (RunResult, error) { cmdModel := xcodebuild.NewShowBuildSettingsCommand(opts.ProjectPath) cmdModel.SetScheme(opts.Scheme) cmdModel.SetConfiguration(opts.Configuration) - settings, err := cmdModel.RunAndReturnSettings() + settings, err := cmdModel.RunAndReturnSettings(true) if err != nil { return out, fmt.Errorf("failed to read build settings: %w", err) } @@ -818,7 +818,7 @@ func (s XcodebuildArchiver) xcodeArchive(opts xcodeArchiveOpts) (xcodeArchiveRes // Open Xcode project s.logger.TInfof("Opening xcode project at path: %s for scheme: %s", opts.ProjectPath, opts.Scheme) - xcodeProj, scheme, configuration, err := OpenArchivableProject(opts.ProjectPath, opts.Scheme, opts.Configuration) + archivableProject, scheme, configuration, err := OpenArchivableProject(opts.ProjectPath, opts.Scheme, opts.Configuration) if err != nil { return out, fmt.Errorf("failed to open project: %s: %s", opts.ProjectPath, err) } @@ -828,7 +828,7 @@ func (s XcodebuildArchiver) xcodeArchive(opts xcodeArchiveOpts) (xcodeArchiveRes if opts.DestinationPlatform == detectPlatform { s.logger.TInfof("Platform is set to 'automatic', detecting platform from the project.") s.logger.TWarnf("Define the platform step input manually to avoid this phase in the future.") - platform, err := BuildableTargetPlatform(xcodeProj, scheme, configuration, opts.AdditionalOptions, XcodeBuild{}, s.logger) + platform, err := BuildableTargetPlatform(archivableProject, scheme, configuration, opts.AdditionalOptions, XcodeBuild{}, s.logger) if err != nil { return out, fmt.Errorf("failed to read project platform: %s: %s", opts.ProjectPath, err) } @@ -837,6 +837,11 @@ func (s XcodebuildArchiver) xcodeArchive(opts xcodeArchiveOpts) (xcodeArchiveRes s.logger.TInfof("Reading main target") + // Get the underlying XcodeProj for exportoptionsgenerator + xcodeProj, err := archivableProject.GetProject() + if err != nil { + return out, fmt.Errorf("failed to get underlying project: %s", err) + } mainTarget, err := exportoptionsgenerator.ArchivableApplicationTarget(xcodeProj, scheme) if err != nil { return out, fmt.Errorf("failed to read main application target: %s", err) diff --git a/vendor/github.com/bitrise-io/go-xcode/v2/autocodesign/projectmanager/projecthelper.go b/vendor/github.com/bitrise-io/go-xcode/v2/autocodesign/projectmanager/projecthelper.go index 8f3a33ac..cc5e63eb 100644 --- a/vendor/github.com/bitrise-io/go-xcode/v2/autocodesign/projectmanager/projecthelper.go +++ b/vendor/github.com/bitrise-io/go-xcode/v2/autocodesign/projectmanager/projecthelper.go @@ -17,6 +17,7 @@ import ( "github.com/bitrise-io/go-xcode/xcodeproject/serialized" "github.com/bitrise-io/go-xcode/xcodeproject/xcodeproj" "github.com/bitrise-io/go-xcode/xcodeproject/xcscheme" + "github.com/bitrise-io/go-xcode/xcodeproject/xcworkspace" "howett.net/plist" ) @@ -26,7 +27,10 @@ type ProjectHelper struct { DependentTargets []xcodeproj.Target UITestTargets []xcodeproj.Target XcProj xcodeproj.XcodeProj + XcWorkspace *xcworkspace.Workspace // nil if working with standalone project + SchemeName string // The scheme name used Configuration string + BasePath string // The original project or workspace path buildSettingsCache map[string]map[string]serialized.Object // target/config/buildSettings(serialized.Object) } @@ -76,12 +80,25 @@ func NewProjectHelper(projOrWSPath, schemeName, configurationName string) (*Proj return nil, fmt.Errorf("no configuration provided nor default defined for the scheme's (%s) archive action", schemeName) } + // Check if we're working with a workspace + var workspace *xcworkspace.Workspace + if filepath.Ext(projOrWSPath) == ".xcworkspace" { + ws, err := xcworkspace.Open(projOrWSPath) + if err != nil { + return nil, fmt.Errorf("failed to open workspace: %s", err) + } + workspace = &ws + } + return &ProjectHelper{ MainTarget: mainTarget, DependentTargets: dependentTargets, UITestTargets: uiTestTargets, XcProj: xcproj, + XcWorkspace: workspace, + SchemeName: schemeName, Configuration: conf, + BasePath: projOrWSPath, }, nil } @@ -129,7 +146,7 @@ func (p *ProjectHelper) UITestTargetBundleIDs() ([]string, error) { // Platform get the platform (PLATFORM_DISPLAY_NAME) - iOS, tvOS, macOS func (p *ProjectHelper) Platform(configurationName string) (autocodesign.Platform, error) { - settings, err := p.targetBuildSettings(p.MainTarget.Name, configurationName) + settings, err := p.buildSettings(p.MainTarget.Name, configurationName) if err != nil { return "", fmt.Errorf("failed to fetch project (%s) build settings: %s", p.XcProj.Path, err) } @@ -202,7 +219,7 @@ func (p *ProjectHelper) ProjectTeamID(config string) (string, error) { } func (p *ProjectHelper) targetTeamID(targetName, config string) (string, error) { - settings, err := p.targetBuildSettings(targetName, config) + settings, err := p.buildSettings(targetName, config) if err != nil { return "", fmt.Errorf("failed to fetch Team ID from target settings (%s): %s", targetName, err) } @@ -215,20 +232,65 @@ func (p *ProjectHelper) targetTeamID(targetName, config string) (string, error) } -func (p *ProjectHelper) targetBuildSettings(name, conf string) (serialized.Object, error) { - targetCache, ok := p.buildSettingsCache[name] +// buildSettings returns target build settings using workspace or project +// For workspace: uses main SchemeBuildSettings for main target, dedicated SchemeBuildSettings for secondary targets +// For xcodeproj: uses TargetBuildSettings with target name +func (p *ProjectHelper) buildSettings(targetName, conf string) (serialized.Object, error) { + log.Debugf("🔍 buildSettings: fetching settings for target='%s', config='%s'", targetName, conf) + log.Debugf("🔍 buildSettings: MainTarget.Name='%s'", p.MainTarget.Name) + log.Debugf("🔍 buildSettings: Has workspace: %t", p.XcWorkspace != nil) + if p.XcWorkspace != nil { + log.Debugf("🔍 buildSettings: Workspace path='%s', Scheme='%s'", p.XcWorkspace.Path, p.SchemeName) + } + log.Debugf("🔍 buildSettings: Project path='%s'", p.XcProj.Path) + + targetCache, ok := p.buildSettingsCache[targetName] if ok { confCache, ok := targetCache[conf] if ok { + log.Debugf("✅ buildSettings: Using cached settings for target='%s'", targetName) return confCache, nil } } - settings, err := p.XcProj.TargetBuildSettings(name, conf) + var settings serialized.Object + var err error + + if p.XcWorkspace != nil { + if targetName == p.MainTarget.Name { + // For main target: use the main scheme's build settings + log.Debugf("📍 buildSettings: Using main scheme '%s' for MAIN target='%s' (workspace context)", p.SchemeName, targetName) + settings, err = p.XcWorkspace.SchemeBuildSettings(p.SchemeName, conf) + } else { + // For secondary targets: try SchemeBuildSettings first, fallback to TargetBuildSettings if it fails + log.Debugf("📍 buildSettings: Using SchemeBuildSettings for SECONDARY target='%s' (workspace context - keep first values)", targetName) + settings, err = p.XcWorkspace.SchemeBuildSettings(targetName, conf) + + // If scheme build settings fail for secondary target, fallback to project target build settings + if err != nil { + log.Warnf("âš ī¸ buildSettings: SchemeBuildSettings failed for secondary target='%s': %s", targetName, err) + log.Debugf("🔄 buildSettings: Falling back to project TargetBuildSettings for target='%s'", targetName) + settings, err = p.XcProj.TargetBuildSettings(targetName, conf) + if err != nil { + log.Errorf("❌ buildSettings: Fallback TargetBuildSettings also failed for target='%s': %s", targetName, err) + } else { + log.Debugf("✅ buildSettings: Fallback TargetBuildSettings succeeded for target='%s'", targetName) + } + } + } + } else { + // Use project TargetBuildSettings for standalone projects + log.Debugf("📍 buildSettings: Using project TargetBuildSettings for STANDALONE project target='%s'", targetName) + settings, err = p.XcProj.TargetBuildSettings(targetName, conf) + } + if err != nil { + log.Errorf("❌ buildSettings: Failed to fetch settings for target='%s': %s", targetName, err) return nil, err } + log.Debugf("✅ buildSettings: Successfully fetched settings for target='%s'", targetName) + if targetCache == nil { targetCache = map[string]serialized.Object{} } @@ -237,7 +299,7 @@ func (p *ProjectHelper) targetBuildSettings(name, conf string) (serialized.Objec if p.buildSettingsCache == nil { p.buildSettingsCache = map[string]map[string]serialized.Object{} } - p.buildSettingsCache[name] = targetCache + p.buildSettingsCache[targetName] = targetCache return settings, nil } @@ -248,68 +310,169 @@ func (p *ProjectHelper) targetBuildSettings(name, conf string) (serialized.Objec // The CFBundleIdentifier's value is not resolved in the Info.plist, so it will try to resolve it by the resolveBundleID() // It returns the target bundle ID func (p *ProjectHelper) TargetBundleID(name, conf string) (string, error) { - settings, err := p.targetBuildSettings(name, conf) + log.Debugf("🆔 TargetBundleID: START - Resolving bundle ID for target='%s', config='%s'", name, conf) + log.Debugf("🆔 TargetBundleID: MainTarget.Name='%s'", p.MainTarget.Name) + log.Debugf("🆔 TargetBundleID: Has workspace: %t", p.XcWorkspace != nil) + if p.XcWorkspace != nil { + log.Debugf("🆔 TargetBundleID: Workspace path='%s', Scheme='%s'", p.XcWorkspace.Path, p.SchemeName) + } + log.Debugf("🆔 TargetBundleID: Project path='%s'", p.XcProj.Path) + log.Debugf("🆔 TargetBundleID: BasePath='%s'", p.BasePath) + + log.Debugf("📋 TargetBundleID: Fetching build settings for target='%s'", name) + settings, err := p.buildSettings(name, conf) if err != nil { + log.Errorf("❌ TargetBundleID: Failed to fetch target (%s) settings: %s", name, err) return "", fmt.Errorf("failed to fetch target (%s) settings: %s", name, err) } + log.Debugf("✅ TargetBundleID: Successfully fetched build settings for target='%s'", name) + log.Debugf("🔍 TargetBundleID: Looking for PRODUCT_BUNDLE_IDENTIFIER in build settings...") bundleID, err := settings.String("PRODUCT_BUNDLE_IDENTIFIER") if err != nil && !serialized.IsKeyNotFoundError(err) { + log.Errorf("❌ TargetBundleID: Failed to parse PRODUCT_BUNDLE_IDENTIFIER: %s", err) return "", fmt.Errorf("failed to parse target (%s) build settings attribute PRODUCT_BUNDLE_IDENTIFIER: %s", name, err) } if bundleID != "" { + log.Debugf("✅ TargetBundleID: Found PRODUCT_BUNDLE_IDENTIFIER='%s' for target='%s'", bundleID, name) + log.Debugf("🆔 TargetBundleID: END - Returning bundle ID from build settings: '%s'", bundleID) return bundleID, nil } + log.Debugf("â„šī¸ TargetBundleID: PRODUCT_BUNDLE_IDENTIFIER not found in build settings, checking Info.plist...") log.Debugf("PRODUCT_BUNDLE_IDENTIFIER env not found in 'xcodebuild -showBuildSettings -project %s -target %s -configuration %s command's output, checking the Info.plist file's CFBundleIdentifier property...", p.XcProj.Path, name, conf) + log.Debugf("📄 TargetBundleID: Looking for INFOPLIST_FILE in build settings...") infoPlistPath, err := settings.String("INFOPLIST_FILE") if err != nil { + log.Errorf("❌ TargetBundleID: Failed to find INFOPLIST_FILE: %s", err) return "", fmt.Errorf("failed to find Info.plist file: %s", err) } - infoPlistPath = path.Join(path.Dir(p.XcProj.Path), infoPlistPath) + log.Debugf("✅ TargetBundleID: Found INFOPLIST_FILE='%s'", infoPlistPath) + + // Use the original base path (workspace or project) to resolve relative paths + var basePath string + if filepath.Ext(p.BasePath) == ".xcworkspace" { + basePath = filepath.Dir(p.BasePath) + log.Debugf("📁 TargetBundleID: Using workspace base path='%s'", basePath) + } else { + basePath = filepath.Dir(p.XcProj.Path) + log.Debugf("📁 TargetBundleID: Using project base path='%s'", basePath) + } + + absoluteInfoPlistPath := path.Join(basePath, infoPlistPath) + log.Debugf("📍 TargetBundleID: Resolved absolute Info.plist path='%s'", absoluteInfoPlistPath) + infoPlistPath = absoluteInfoPlistPath if infoPlistPath == "" { + log.Errorf("❌ TargetBundleID: Empty Info.plist path after resolution") return "", fmt.Errorf("failed to to determine bundle id: xcodebuild -showBuildSettings does not contains PRODUCT_BUNDLE_IDENTIFIER nor INFOPLIST_FILE' unless info_plist_path") } + log.Debugf("📖 TargetBundleID: Reading Info.plist file: '%s'", infoPlistPath) b, err := fileutil.ReadBytesFromFile(infoPlistPath) if err != nil { + log.Errorf("❌ TargetBundleID: Failed to read Info.plist file '%s': %s", infoPlistPath, err) return "", fmt.Errorf("failed to read Info.plist: %s", err) } + log.Debugf("✅ TargetBundleID: Successfully read %d bytes from Info.plist", len(b)) + log.Debugf("🔧 TargetBundleID: Parsing Info.plist content...") var options map[string]interface{} if _, err := plist.Unmarshal(b, &options); err != nil { + log.Errorf("❌ TargetBundleID: Failed to unmarshal Info.plist: %s", err) return "", fmt.Errorf("failed to unmarshal Info.plist: %s ", err) } + log.Debugf("✅ TargetBundleID: Successfully parsed Info.plist with %d keys", len(options)) + log.Debugf("🔍 TargetBundleID: Looking for CFBundleIdentifier in Info.plist...") bundleID, ok := options["CFBundleIdentifier"].(string) if !ok || bundleID == "" { + log.Errorf("❌ TargetBundleID: CFBundleIdentifier not found or empty in Info.plist") + log.Debugf("🔍 TargetBundleID: Available keys in Info.plist: %v", func() []string { + keys := make([]string, 0, len(options)) + for k := range options { + keys = append(keys, k) + } + return keys + }()) return "", fmt.Errorf("failed to parse CFBundleIdentifier from the Info.plist") } + log.Debugf("✅ TargetBundleID: Found CFBundleIdentifier='%s' in Info.plist", bundleID) + + log.Debugf("✅ TargetBundleID: Found CFBundleIdentifier='%s' in Info.plist", bundleID) + log.Debugf("🔍 TargetBundleID: Checking if bundle ID contains variables...") if !strings.Contains(bundleID, "$") { + log.Debugf("✅ TargetBundleID: Bundle ID contains no variables, returning as-is: '%s'", bundleID) + log.Debugf("🆔 TargetBundleID: END - Returning bundle ID from Info.plist: '%s'", bundleID) return bundleID, nil } + log.Debugf("🔧 TargetBundleID: Bundle ID contains variables, need to expand: '%s'", bundleID) log.Debugf("CFBundleIdentifier defined with variable: %s, trying to resolve it...", bundleID) + log.Debugf("🔧 TargetBundleID: Expanding variables in bundle ID...") resolved, err := expandTargetSetting(bundleID, settings) if err != nil { + log.Errorf("❌ TargetBundleID: Failed to resolve bundle ID variables: %s", err) return "", fmt.Errorf("failed to resolve bundle ID: %s", err) } + log.Debugf("✅ TargetBundleID: Successfully resolved bundle ID: '%s' -> '%s'", bundleID, resolved) log.Debugf("resolved CFBundleIdentifier: %s", resolved) + log.Debugf("🆔 TargetBundleID: END - Returning resolved bundle ID: '%s'", resolved) return resolved, nil } func (p *ProjectHelper) targetEntitlements(name, config, bundleID string) (autocodesign.Entitlements, error) { - entitlements, err := p.XcProj.TargetCodeSignEntitlements(name, config) + log.Debugf("🔍 targetEntitlements: fetching entitlements for target='%s', config='%s', bundleID='%s'", name, config, bundleID) + log.Debugf("🔍 targetEntitlements: MainTarget.Name='%s'", p.MainTarget.Name) + log.Debugf("🔍 targetEntitlements: Has workspace: %t", p.XcWorkspace != nil) + + var entitlements serialized.Object + var err error + + if p.XcWorkspace != nil { + if name == p.MainTarget.Name { + // For main target: use the main scheme's entitlements + log.Debugf("📍 targetEntitlements: Using main scheme '%s' for MAIN target='%s' (workspace context)", p.SchemeName, name) + entitlements, err = p.XcWorkspace.SchemeCodeSignEntitlements(p.SchemeName, config) + } else { + // For secondary targets: try SchemeCodeSignEntitlements first, fallback to TargetCodeSignEntitlements if it fails + log.Debugf("📍 targetEntitlements: Using target-specific entitlements for SECONDARY target='%s' (workspace context)", name) + entitlements, err = p.XcWorkspace.SchemeCodeSignEntitlements(name, config) + + // If scheme entitlements fail for secondary target, fallback to project target entitlements + if err != nil { + log.Warnf("âš ī¸ targetEntitlements: SchemeCodeSignEntitlements failed for secondary target='%s': %s", name, err) + log.Debugf("🔄 targetEntitlements: Falling back to project TargetCodeSignEntitlements for target='%s'", name) + entitlements, err = p.XcProj.TargetCodeSignEntitlements(name, config) + if err != nil { + log.Errorf("❌ targetEntitlements: Fallback TargetCodeSignEntitlements also failed for target='%s': %s", name, err) + } else { + log.Debugf("✅ targetEntitlements: Fallback TargetCodeSignEntitlements succeeded for target='%s'", name) + } + } + } + } else { + // Use project TargetCodeSignEntitlements for standalone projects + log.Debugf("📍 targetEntitlements: Using project TargetCodeSignEntitlements for STANDALONE project target='%s'", name) + entitlements, err = p.XcProj.TargetCodeSignEntitlements(name, config) + } + if err != nil && !serialized.IsKeyNotFoundError(err) { + log.Errorf("❌ targetEntitlements: Failed to fetch entitlements for target='%s': %s", name, err) return nil, err } + if err != nil && serialized.IsKeyNotFoundError(err) { + log.Debugf("â„šī¸ targetEntitlements: No entitlements found for target='%s' (this is normal for some targets)", name) + } else { + log.Debugf("✅ targetEntitlements: Successfully fetched entitlements for target='%s'", name) + } + return resolveEntitlementVariables(autocodesign.Entitlements(entitlements), bundleID) } @@ -317,7 +480,7 @@ func (p *ProjectHelper) targetEntitlements(name, config, bundleID string) (autoc // Note: it only checks the main Target based on the given Scheme and Configuration func (p *ProjectHelper) IsSigningManagedAutomatically() (bool, error) { targetName := p.MainTarget.Name - settings, err := p.targetBuildSettings(targetName, p.Configuration) + settings, err := p.buildSettings(targetName, p.Configuration) if err != nil { return false, fmt.Errorf("failed to fetch code signing info from target (%s) settings: %s", targetName, err) } diff --git a/vendor/github.com/bitrise-io/go-xcode/v2/autocodesign/projectmanager/projectmanager.go b/vendor/github.com/bitrise-io/go-xcode/v2/autocodesign/projectmanager/projectmanager.go index 8676396d..09733a6d 100644 --- a/vendor/github.com/bitrise-io/go-xcode/v2/autocodesign/projectmanager/projectmanager.go +++ b/vendor/github.com/bitrise-io/go-xcode/v2/autocodesign/projectmanager/projectmanager.go @@ -127,86 +127,168 @@ func (p Project) GetAppLayout(uiTestTargets bool) (autocodesign.AppLayout, error // ForceCodesignAssets ... func (p Project) ForceCodesignAssets(distribution autocodesign.DistributionType, codesignAssetsByDistributionType map[autocodesign.DistributionType]autocodesign.AppCodesignAssets) error { + log.Debugf("🔍 [FORCE CODESIGN] Starting ForceCodesignAssets with distribution: %s", distribution) + log.Debugf("🔍 [FORCE CODESIGN] Available distribution types in codesignAssetsByDistributionType:") + for distType := range codesignAssetsByDistributionType { + log.Debugf("🔍 [FORCE CODESIGN] - %s", distType) + } + archivableTargets := p.projHelper.ArchivableTargets() var archivableTargetsCounter = 0 + log.Debugf("🔍 [FORCE CODESIGN] Found %d archivable targets:", len(archivableTargets)) + for i, target := range archivableTargets { + log.Debugf("🔍 [FORCE CODESIGN] %d. %s", i+1, target.Name) + } + fmt.Println() log.TInfof("Apply Bitrise managed codesigning on the executable targets (up to: %d targets)", len(archivableTargets)) for _, target := range archivableTargets { fmt.Println() log.Infof(" Target: %s", target.Name) + log.Debugf("🔍 [FORCE CODESIGN] Processing target: %s", target.Name) forceCodesignDistribution := distribution + log.Debugf("🔍 [FORCE CODESIGN] Initial distribution for target %s: %s", target.Name, forceCodesignDistribution) + if _, isDevelopmentAvailable := codesignAssetsByDistributionType[autocodesign.Development]; isDevelopmentAvailable { forceCodesignDistribution = autocodesign.Development + log.Debugf("🔍 [FORCE CODESIGN] Development assets available, switching to Development distribution for target %s", target.Name) + } else { + log.Debugf("🔍 [FORCE CODESIGN] No Development assets available for target %s", target.Name) } + log.Debugf("🔍 [FORCE CODESIGN] Final distribution for target %s: %s", target.Name, forceCodesignDistribution) + codesignAssets, ok := codesignAssetsByDistributionType[forceCodesignDistribution] if !ok { + log.Debugf("❌ [FORCE CODESIGN] No codesign settings found for distribution type %s for target %s", forceCodesignDistribution, target.Name) return fmt.Errorf("no codesign settings ensured for distribution type %s", forceCodesignDistribution) } + log.Debugf("🔍 [FORCE CODESIGN] Found codesign assets for distribution %s for target %s", forceCodesignDistribution, target.Name) + teamID := codesignAssets.Certificate.TeamID + log.Debugf("🔍 [FORCE CODESIGN] Team ID for target %s: %s", target.Name, teamID) targetBundleID, err := p.projHelper.TargetBundleID(target.Name, p.projHelper.Configuration) if err != nil { + log.Debugf("❌ [FORCE CODESIGN] Failed to get bundle ID for target %s: %v", target.Name, err) return err } + log.Debugf("🔍 [FORCE CODESIGN] Bundle ID for target %s: %s", target.Name, targetBundleID) + + log.Debugf("🔍 [FORCE CODESIGN] Available profiles in ArchivableTargetProfilesByBundleID for target %s:", target.Name) + for bundleID, profile := range codesignAssets.ArchivableTargetProfilesByBundleID { + log.Debugf("🔍 [FORCE CODESIGN] Bundle ID: %s -> Profile: %s", bundleID, profile.Attributes().Name) + } + profile, ok := codesignAssets.ArchivableTargetProfilesByBundleID[targetBundleID] if !ok { + log.Debugf("❌ [FORCE CODESIGN] No profile found for bundle ID %s for target %s", targetBundleID, target.Name) + log.Debugf("❌ [FORCE CODESIGN] Available bundle IDs in profiles:") + for availableBundleID := range codesignAssets.ArchivableTargetProfilesByBundleID { + log.Debugf("❌ [FORCE CODESIGN] - %s", availableBundleID) + } return fmt.Errorf("no profile ensured for the bundleID %s", targetBundleID) } + log.Debugf("✅ [FORCE CODESIGN] Found profile for target %s (bundle ID %s): %s", target.Name, targetBundleID, profile.Attributes().Name) log.Printf(" development Team: %s(%s)", codesignAssets.Certificate.TeamName, teamID) log.Printf(" provisioning Profile: %s", profile.Attributes().Name) log.Printf(" certificate: %s", codesignAssets.Certificate.CommonName) + log.Debugf("🔍 [FORCE CODESIGN] About to apply code sign settings for target %s:", target.Name) + log.Debugf("🔍 [FORCE CODESIGN] Configuration: %s", p.projHelper.Configuration) + log.Debugf("🔍 [FORCE CODESIGN] Target: %s", target.Name) + log.Debugf("🔍 [FORCE CODESIGN] Team ID: %s", teamID) + log.Debugf("🔍 [FORCE CODESIGN] Certificate SHA1: %s", codesignAssets.Certificate.SHA1Fingerprint) + log.Debugf("🔍 [FORCE CODESIGN] Profile UUID: %s", profile.Attributes().UUID) + if err := p.projHelper.XcProj.ForceCodeSign(p.projHelper.Configuration, target.Name, teamID, codesignAssets.Certificate.SHA1Fingerprint, profile.Attributes().UUID); err != nil { + log.Debugf("❌ [FORCE CODESIGN] Failed to apply code sign settings for target %s: %v", target.Name, err) return fmt.Errorf("failed to apply code sign settings for target (%s): %s", target.Name, err) } + log.Debugf("✅ [FORCE CODESIGN] Successfully applied code sign settings for target %s", target.Name) archivableTargetsCounter++ } log.TInfof("Applied Bitrise managed codesigning on up to %s targets", archivableTargetsCounter) + log.Debugf("🔍 [FORCE CODESIGN] Completed processing %d archivable targets", archivableTargetsCounter) devCodesignAssets, isDevelopmentAvailable := codesignAssetsByDistributionType[autocodesign.Development] + log.Debugf("🔍 [FORCE CODESIGN] Checking for UITest targets. Development available: %t", isDevelopmentAvailable) + + if isDevelopmentAvailable { + log.Debugf("🔍 [FORCE CODESIGN] UITest target profiles count: %d", len(devCodesignAssets.UITestTargetProfilesByBundleID)) + log.Debugf("🔍 [FORCE CODESIGN] UITest targets count: %d", len(p.projHelper.UITestTargets)) + } + if isDevelopmentAvailable && len(devCodesignAssets.UITestTargetProfilesByBundleID) != 0 { fmt.Println() log.TInfof("Apply Bitrise managed codesigning on the UITest targets (%d)", len(p.projHelper.UITestTargets)) + log.Debugf("🔍 [FORCE CODESIGN] Starting UITest targets processing") for _, uiTestTarget := range p.projHelper.UITestTargets { fmt.Println() log.Infof(" Target: %s", uiTestTarget.Name) + log.Debugf("🔍 [FORCE CODESIGN] Processing UITest target: %s", uiTestTarget.Name) teamID := devCodesignAssets.Certificate.TeamID + log.Debugf("🔍 [FORCE CODESIGN] Team ID for UITest target %s: %s", uiTestTarget.Name, teamID) targetBundleID, err := p.projHelper.TargetBundleID(uiTestTarget.Name, p.projHelper.Configuration) if err != nil { + log.Debugf("❌ [FORCE CODESIGN] Failed to get bundle ID for UITest target %s: %v", uiTestTarget.Name, err) return err } + log.Debugf("🔍 [FORCE CODESIGN] Bundle ID for UITest target %s: %s", uiTestTarget.Name, targetBundleID) + + log.Debugf("🔍 [FORCE CODESIGN] Available UITest profiles:") + for bundleID, profile := range devCodesignAssets.UITestTargetProfilesByBundleID { + log.Debugf("🔍 [FORCE CODESIGN] Bundle ID: %s -> Profile: %s", bundleID, profile.Attributes().Name) + } + profile, ok := devCodesignAssets.UITestTargetProfilesByBundleID[targetBundleID] if !ok { + log.Debugf("❌ [FORCE CODESIGN] No UITest profile found for bundle ID %s for target %s", targetBundleID, uiTestTarget.Name) return fmt.Errorf("no profile ensured for the bundleID %s", targetBundleID) } + log.Debugf("✅ [FORCE CODESIGN] Found UITest profile for target %s (bundle ID %s): %s", uiTestTarget.Name, targetBundleID, profile.Attributes().Name) log.Printf(" development Team: %s(%s)", devCodesignAssets.Certificate.TeamName, teamID) log.Printf(" provisioning Profile: %s", profile.Attributes().Name) log.Printf(" certificate: %s", devCodesignAssets.Certificate.CommonName) + log.Debugf("🔍 [FORCE CODESIGN] Processing %d build configurations for UITest target %s", len(uiTestTarget.BuildConfigurationList.BuildConfigurations), uiTestTarget.Name) for _, c := range uiTestTarget.BuildConfigurationList.BuildConfigurations { + log.Debugf("🔍 [FORCE CODESIGN] Applying code sign for UITest target %s, configuration %s", uiTestTarget.Name, c.Name) if err := p.projHelper.XcProj.ForceCodeSign(c.Name, uiTestTarget.Name, teamID, devCodesignAssets.Certificate.SHA1Fingerprint, profile.Attributes().UUID); err != nil { + log.Debugf("❌ [FORCE CODESIGN] Failed to apply code sign settings for UITest target %s, config %s: %v", uiTestTarget.Name, c.Name, err) return fmt.Errorf("failed to apply code sign settings for target (%s): %s", uiTestTarget.Name, err) } + log.Debugf("✅ [FORCE CODESIGN] Successfully applied code sign for UITest target %s, configuration %s", uiTestTarget.Name, c.Name) } } + log.Debugf("🔍 [FORCE CODESIGN] Completed UITest targets processing") + } else { + if !isDevelopmentAvailable { + log.Debugf("🔍 [FORCE CODESIGN] Skipping UITest targets: Development assets not available") + } else { + log.Debugf("🔍 [FORCE CODESIGN] Skipping UITest targets: No UITest profiles available") + } } + log.Debugf("🔍 [FORCE CODESIGN] About to save Xcode project...") if err := p.projHelper.XcProj.Save(); err != nil { + log.Debugf("❌ [FORCE CODESIGN] Failed to save project: %v", err) return fmt.Errorf("failed to save project: %s", err) } + log.Debugf("✅ [FORCE CODESIGN] Successfully saved Xcode project") log.Debugf("Xcode project saved.") + log.Debugf("🔍 [FORCE CODESIGN] ForceCodesignAssets completed successfully") return nil } diff --git a/vendor/github.com/bitrise-io/go-xcode/xcodebuild/show_build_settings.go b/vendor/github.com/bitrise-io/go-xcode/xcodebuild/show_build_settings.go index 31cda3bb..37eaa92a 100644 --- a/vendor/github.com/bitrise-io/go-xcode/xcodebuild/show_build_settings.go +++ b/vendor/github.com/bitrise-io/go-xcode/xcodebuild/show_build_settings.go @@ -96,7 +96,11 @@ func (c ShowBuildSettingsCommandModel) PrintableCmd() string { return command.PrintableCommandArgs(false, cmdSlice) } -func parseBuildSettings(out string) (serialized.Object, error) { +// parseBuildSettings parses xcodebuild -showBuildSettings output into a map of build settings. +// The overwrite parameter controls whether duplicate keys should be overwritten: +// - overwrite=false: preserves the first occurrence of each key (optimal for multi-target schemes) +// - overwrite=true: overwrites with the last occurrence of each key (traditional behavior) +func parseBuildSettings(out string, overwrite bool) (serialized.Object, error) { settings := serialized.Object{} reader := bufio.NewReader(strings.NewReader(out)) @@ -122,7 +126,16 @@ func parseBuildSettings(out string) (serialized.Object, error) { value := strings.TrimSpace(strings.Join(split[1:], "=")) value = strings.Trim(value, `"`) - settings[key] = value + // Set the value based on overwrite policy + if overwrite { + // Always overwrite (traditional behavior) + settings[key] = value + } else { + // Only set the value if the key doesn't already exist (keep first occurrence) + if _, exists := settings[key]; !exists { + settings[key] = value + } + } } buffer.Reset() @@ -132,8 +145,11 @@ func parseBuildSettings(out string) (serialized.Object, error) { return settings, nil } -// RunAndReturnSettings ... -func (c ShowBuildSettingsCommandModel) RunAndReturnSettings() (serialized.Object, error) { +// RunAndReturnSettings reads and parses build settings +// The overwrite parameter controls whether duplicate keys should be overwritten: +// - overwrite=false: preserves the first occurrence of each key (optimal for multi-target schemes) +// - overwrite=true: overwrites with the last occurrence of each key (traditional behavior) +func (c ShowBuildSettingsCommandModel) RunAndReturnSettings(overwrite bool) (serialized.Object, error) { var cmd = c.Command() log.TPrintf("Reading build settings...") @@ -149,5 +165,5 @@ func (c ShowBuildSettingsCommandModel) RunAndReturnSettings() (serialized.Object log.TPrintf("Read target settings.") - return parseBuildSettings(out) + return parseBuildSettings(out, overwrite) } diff --git a/vendor/github.com/bitrise-io/go-xcode/xcodeproject/xcodeproj/xcodeproj.go b/vendor/github.com/bitrise-io/go-xcode/xcodeproject/xcodeproj/xcodeproj.go index a6e9f336..cbb39155 100644 --- a/vendor/github.com/bitrise-io/go-xcode/xcodeproject/xcodeproj/xcodeproj.go +++ b/vendor/github.com/bitrise-io/go-xcode/xcodeproject/xcodeproj/xcodeproj.go @@ -324,7 +324,7 @@ func (p XcodeProj) TargetBuildSettings(target, configuration string, customOptio commandModel.SetTarget(target) commandModel.SetConfiguration(configuration) commandModel.SetCustomOptions(customOptions) - return commandModel.RunAndReturnSettings() + return commandModel.RunAndReturnSettings(true) } // Open ... diff --git a/vendor/github.com/bitrise-io/go-xcode/xcodeproject/xcworkspace/xcworkspace.go b/vendor/github.com/bitrise-io/go-xcode/xcodeproject/xcworkspace/xcworkspace.go index b8842e04..4dc3a4f1 100644 --- a/vendor/github.com/bitrise-io/go-xcode/xcodeproject/xcworkspace/xcworkspace.go +++ b/vendor/github.com/bitrise-io/go-xcode/xcodeproject/xcworkspace/xcworkspace.go @@ -55,13 +55,40 @@ func (w Workspace) SchemeBuildSettings(scheme, configuration string, customOptio commandModel.SetConfiguration(configuration) commandModel.SetCustomOptions(customOptions) - object, err := commandModel.RunAndReturnSettings() + object, err := commandModel.RunAndReturnSettings(false) log.TDebugf("Fetched %s scheme build settings", scheme) return object, err } +// SchemeCodeSignEntitlements returns the code sign entitlements for a scheme and configuration +func (w Workspace) SchemeCodeSignEntitlements(scheme, configuration string) (serialized.Object, error) { + // Get build settings to find the entitlements file path + buildSettings, err := w.SchemeBuildSettings(scheme, configuration) + if err != nil { + return nil, err + } + + // Get the CODE_SIGN_ENTITLEMENTS path + entitlementsPath, err := buildSettings.String("CODE_SIGN_ENTITLEMENTS") + if err != nil { + return nil, err + } + + // Resolve the absolute path relative to workspace directory + absolutePath := filepath.Join(filepath.Dir(w.Path), entitlementsPath) + + // Read and parse the entitlements file + entitlements, _, err := xcodeproj.ReadPlistFile(absolutePath) + if err != nil { + return nil, err + } + + log.TDebugf("Fetched %s scheme code sign entitlements", scheme) + return entitlements, nil +} + // FileLocations ... func (w Workspace) FileLocations() ([]string, error) { var fileLocations []string