Skip to content

Use xcworkspace instead of xcodeproj if path is xcworkpace #384

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 67 additions & 12 deletions step/platform.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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":
Expand All @@ -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)
Expand All @@ -67,41 +107,56 @@ 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)
if !ok {
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)
}
Expand Down
11 changes: 6 additions & 5 deletions step/platform_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}

Expand Down Expand Up @@ -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
Expand Down
11 changes: 8 additions & 3 deletions step/step.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down Expand Up @@ -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)
}
Expand All @@ -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)
}
Expand All @@ -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)
Expand Down
Loading