Skip to content
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

Add xcodebuild command destination flag #147

Merged
merged 12 commits into from
Jan 28, 2022
21 changes: 21 additions & 0 deletions cmd/xcode.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (

"github.com/bitrise-io/codesigndoc/codesign"
"github.com/bitrise-io/codesigndoc/codesigndoc"
"github.com/bitrise-io/codesigndoc/utility"
"github.com/bitrise-io/codesigndoc/xcode"
"github.com/bitrise-io/go-utils/colorstring"
"github.com/bitrise-io/go-utils/fileutil"
Expand All @@ -32,6 +33,7 @@ var (
paramXcodeProjectFilePath string
paramXcodeScheme string
paramXcodebuildSDK string
paramXcodeDestination string
)

func init() {
Expand All @@ -40,6 +42,7 @@ func init() {
xcodeCmd.Flags().StringVar(&paramXcodeProjectFilePath, "file", "", "Xcode Project/Workspace file path")
xcodeCmd.Flags().StringVar(&paramXcodeScheme, "scheme", "", "Xcode Scheme")
xcodeCmd.Flags().StringVar(&paramXcodebuildSDK, "xcodebuild-sdk", "", "xcodebuild -sdk param. If a value is specified for this flag it'll be passed to xcodebuild as the value of the -sdk flag. For more info about the values please see xcodebuild's -sdk flag docs. Example value: iphoneos")
xcodeCmd.Flags().StringVar(&paramXcodeDestination, "xcodebuild-destination", "", "The xcodebuild -destination option takes as its argument a destination specifier describing the device (or devices) to use as a destination i.e `generic/platform=iOS`. If a value is specified for this flag it'll be passed to xcodebuild.")
}

func absOutputDir() (string, error) {
Expand Down Expand Up @@ -108,6 +111,24 @@ func scanXcodeProject(_ *cobra.Command, _ []string) error {
xcodeCmd.SDK = paramXcodebuildSDK
}

if paramXcodeDestination != "" {
xcodeCmd.Destination = paramXcodeDestination
} else {
project, scheme, configuration, err := utility.OpenArchivableProject(xcodeCmd.ProjectFilePath, xcodeCmd.Scheme, "")
if err != nil {
return err
}

platform, err := utility.BuildableTargetPlatform(project, scheme, configuration, utility.XcodeBuild{})
if err == nil {
destination := "generic/platform=" + string(platform)

xcodeCmd.Destination = destination

fmt.Print("Setting xcodebuild -destination flag to: ", destination)
}
}

writeBuildLogs := func(xcodebuildOutput string) error {
if writeFiles == codesign.WriteFilesAlways || writeFiles == codesign.WriteFilesFallback && err != nil { // save the xcodebuild output into a debug log file
xcodebuildOutputFilePath := filepath.Join(absExportOutputDirPath, "xcodebuild-output.log")
Expand Down
20 changes: 20 additions & 0 deletions cmd/xcodeUITests.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (

"github.com/bitrise-io/codesigndoc/codesign"
"github.com/bitrise-io/codesigndoc/codesigndocuitests"
codesigndocutility "github.com/bitrise-io/codesigndoc/utility"
"github.com/bitrise-io/codesigndoc/xcodeuitest"
"github.com/bitrise-io/go-utils/colorstring"
"github.com/bitrise-io/go-utils/fileutil"
Expand All @@ -34,6 +35,7 @@ func init() {
xcodeUITestsCmd.Flags().StringVar(&paramXcodeProjectFilePath, "file", "", "Xcode Project/Workspace file path")
xcodeUITestsCmd.Flags().StringVar(&paramXcodeScheme, "scheme", "", "Xcode Scheme")
xcodeUITestsCmd.Flags().StringVar(&paramXcodebuildSDK, "xcodebuild-sdk", "", "xcodebuild -sdk param. If a value is specified for this flag it'll be passed to xcodebuild as the value of the -sdk flag. For more info about the values please see xcodebuild's -sdk flag docs. Example value: iphoneos")
xcodeUITestsCmd.Flags().StringVar(&paramXcodeDestination, "xcodebuild-destination", "", "The xcodebuild -destination option takes as its argument a destination specifier describing the device (or devices) to use as a destination i.e `generic/platform=iOS`. If a value is specified for this flag it'll be passed to xcodebuild.")
}

func scanXcodeUITestsProject(cmd *cobra.Command, args []string) error {
Expand Down Expand Up @@ -114,6 +116,24 @@ func scanXcodeUITestsProject(cmd *cobra.Command, args []string) error {
xcodeUITestsCmd.SDK = paramXcodebuildSDK
}

if paramXcodeDestination != "" {
xcodeUITestsCmd.Destination = paramXcodeDestination
} else {
project, scheme, configuration, err := codesigndocutility.OpenArchivableProject(xcodeUITestsCmd.ProjectFilePath, xcodeUITestsCmd.Scheme, "")
if err != nil {
return err
}

platform, err := codesigndocutility.BuildableTargetPlatform(project, scheme, configuration, codesigndocutility.XcodeBuild{})
if err == nil {
destination := "generic/platform=" + string(platform)

xcodeUITestsCmd.Destination = destination

fmt.Print("Setting xcodebuild -destination flag to: ", destination)
}
}

fmt.Println()
fmt.Println()
log.Printf("🔦 Running an Xcode build-for-testing, to get all the required code signing settings...")
Expand Down
133 changes: 133 additions & 0 deletions utility/utility.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
package utility

import (
"fmt"
"path/filepath"
"regexp"
"strings"

"github.com/bitrise-io/go-utils/log"
"github.com/bitrise-io/go-xcode/profileutil"
"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"
)

// ProfileExportFileNameNoPath creates a file name for the given profile with pattern: uuid.escaped_profile_name.[mobileprovision|provisionprofile]
Expand All @@ -22,3 +29,129 @@ func ProfileExportFileNameNoPath(info profileutil.ProvisioningProfileInfoModel)

return info.UUID + "." + safeTitle + extension
}

// Platform ...
type Platform string

const (
iOS Platform = "iOS"
osX Platform = "OS X"
tvOS Platform = "tvOS"
watchOS Platform = "watchOS"
)

// TargetBuildSettingsProvider ...
type TargetBuildSettingsProvider interface {
TargetBuildSettings(xcodeProj *xcodeproj.XcodeProj, target, configuration string, customOptions ...string) (serialized.Object, error)
}

// XcodeBuild ...
type XcodeBuild struct {
}

// TargetBuildSettings ...
func (x XcodeBuild) TargetBuildSettings(xcodeProj *xcodeproj.XcodeProj, target, configuration string, customOptions ...string) (serialized.Object, error) {
return xcodeProj.TargetBuildSettings(target, configuration, customOptions...)
}

// BuildableTargetPlatform ...
func BuildableTargetPlatform(
xcodeProj *xcodeproj.XcodeProj,
scheme *xcscheme.Scheme,
configurationName string,
provider TargetBuildSettingsProvider,
) (Platform, error) {
archiveEntry, ok := scheme.AppBuildActionEntry()
if !ok {
return "", fmt.Errorf("archivable entry not found in project: %s, scheme: %s", xcodeProj.Path, scheme.Name)
}

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)
if err != nil {
return "", fmt.Errorf("failed to get target (%s) build settings: %s", mainTarget.Name, err)
}

return getPlatform(settings)
}

func getPlatform(buildSettings serialized.Object) (Platform, error) {
/*
Xcode help:
Base SDK (SDKROOT)
The name or path of the base SDK being used during the build.
The product will be built against the headers and libraries located inside the indicated SDK.
This path will be prepended to all search paths, and will be passed through the environment to the compiler and linker.
Additional SDKs can be specified in the Additional SDKs (ADDITIONAL_SDKS) setting.

Examples:
- /Applications/Xcode.app/Contents/Developer/Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS.sdk
- /Applications/Xcode.app/Contents/Developer/Platforms/AppleTVSimulator.platform/Developer/SDKs/AppleTVSimulator13.4.sdk
- /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS13.4.sdk
- /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk
- /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.sdk
- /Applications/Xcode.app/Contents/Developer/Platforms/WatchOS.platform/Developer/SDKs/WatchOS.sdk
- /Applications/Xcode.app/Contents/Developer/Platforms/WatchSimulator.platform/Developer/SDKs/WatchSimulator.sdk
- iphoneos
- macosx
- appletvos
- watchos
*/
sdk, err := buildSettings.String("SDKROOT")
if err != nil {
return "", fmt.Errorf("failed to get SDKROOT: %s", err)
}

sdk = strings.ToLower(sdk)
if filepath.Ext(sdk) == ".sdk" {
sdk = filepath.Base(sdk)
}

switch {
case strings.HasPrefix(sdk, "iphoneos"):
return iOS, nil
case strings.HasPrefix(sdk, "macosx"):
return osX, nil
case strings.HasPrefix(sdk, "appletvos"):
return tvOS, nil
case strings.HasPrefix(sdk, "watchos"):
return watchOS, nil
default:
return "", fmt.Errorf("unkown SDKROOT: %s", sdk)
}
}

// OpenArchivableProject ...
func OpenArchivableProject(pth, schemeName, configurationName string) (*xcodeproj.XcodeProj, *xcscheme.Scheme, string, error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you do not need a separate OpenArchivableProject and OpenArchivableWorkspace.

schemeint.Scheme accepts both a workspace and project path, whatever is provided, it figures out the correct root project (which is connected to the scheme).

The issue with the current implementation is at the OpenArchivableWorkspace: In the case of a workspace project, a given scheme can be located both under the .xcworkspace or under one of the included .xcproject files, this is handled correctly by the schemeint.Scheme, but OpenArchivableWorkspace is not aware of the schemes under the .xcworkspace.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Screenshot 2022-01-28 at 10 10 57

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)
}
if configurationName == "" {
configurationName = scheme.ArchiveAction.BuildConfiguration
}

if configurationName == "" {
return nil, nil, "", fmt.Errorf("no configuration provided nor default defined for the scheme's (%s) archive action", schemeName)
}

archiveEntry, ok := scheme.AppBuildActionEntry()
if !ok {
return nil, nil, "", fmt.Errorf("archivable entry not found")
}

projectPth, err := archiveEntry.BuildableReference.ReferencedContainerAbsPath(filepath.Dir(schemeContainerDir))
if err != nil {
return nil, nil, "", err
}

xcodeProj, err := xcodeproj.Open(projectPth)
if err != nil {
return nil, nil, "", err
}
return &xcodeProj, scheme, configurationName, nil
}
36 changes: 36 additions & 0 deletions utility/utility_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package utility

import (
"testing"

"github.com/bitrise-io/go-xcode/xcodeproject/serialized"
"github.com/stretchr/testify/require"
)

func TestPlatformsMatching_iOS(t *testing.T) {
buildSettings := serialized.Object{}
buildSettings["SDKROOT"] = "iphoneos"

platform, err := getPlatform(buildSettings)

require.Equal(t, "iOS", string(platform))
require.Nil(t, err)
}

func TestPlatformsMatching_macOS(t *testing.T) {
buildSettings := serialized.Object{}
buildSettings["SDKROOT"] = "macosx"

platform, err := getPlatform(buildSettings)

require.Equal(t, "OS X", string(platform))
require.Nil(t, err)
}

func TestPlatformsMatching_fails(t *testing.T) {
buildSettings := serialized.Object{}
platform, err := getPlatform(buildSettings)

require.Empty(t, platform)
require.NotNil(t, err)
}
15 changes: 15 additions & 0 deletions xcode/xcodecmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,17 @@ type CommandModel struct {
// For more info about the possible values please see xcodebuild's docs about the -sdk flag.
// Only passed to xcodebuild if not empty!
SDK string

// DESTINATION: configure which device or Simulator will be used by the tool
// The supported platforms are:
// OS X, your Mac
// iOS, a connected iOS device
// iOS Simulator
// watchOS
// watchOS Simulator
// tvOS
// tvOS Simulator
Destination string
}

// GenerateArchive : generates the archive for subsequent "Scan"
Expand Down Expand Up @@ -83,6 +94,10 @@ func (xccmd CommandModel) transformToXcodebuildParams(xcodebuildActionArgs ...st
baseArgs = append(baseArgs, "-sdk", xccmd.SDK)
}

if xccmd.Destination != "" {
baseArgs = append(baseArgs, "-destination", xccmd.Destination)
}

if xccmd.CodeSignIdentity != "" {
baseArgs = append(baseArgs, `CODE_SIGN_IDENTITY=`+xccmd.CodeSignIdentity)
}
Expand Down
18 changes: 17 additions & 1 deletion xcodeuitest/xcodeuitestcmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,17 @@ type CommandModel struct {
// For more info about the possible values please see xcodebuild's docs about the -sdk flag.
// Only passed to xcodebuild if not empty!
SDK string

// DESTINATION: configure which device or Simulator will be used by the tool
// The supported platforms are:
// OS X, your Mac
// iOS, a connected iOS device
// iOS Simulator
// watchOS
// watchOS Simulator
// tvOS
// tvOS Simulator
Destination string
}

// RunBuildForTesting runs the build-for-tesing xcode command
Expand Down Expand Up @@ -82,12 +93,17 @@ func (xcuitestcmd CommandModel) transformToXcodebuildParams(xcodebuildActionArgs
baseArgs = append(baseArgs, "-sdk", xcuitestcmd.SDK)
}

if xcuitestcmd.Destination != "" {
baseArgs = append(baseArgs, "-destination", xcuitestcmd.Destination)
}

return append(baseArgs, xcodebuildActionArgs...), nil
}

// RunXcodebuildCommand TODO comment
func (xcuitestcmd CommandModel) RunXcodebuildCommand(xcodebuildActionArgs ...string) (string, error) {
xcodeCmdParamsToRun, err := xcuitestcmd.transformToXcodebuildParams(xcodebuildActionArgs...)

if err != nil {
return "", err
}
Expand Down Expand Up @@ -126,7 +142,7 @@ func (xcuitestcmd CommandModel) ScanSchemes() (schemes []xcscheme.Scheme, scheme
} else {
proj, err := xcodeproj.Open(xcuitestcmd.ProjectFilePath)
if err != nil {
return nil, nil, fmt.Errorf("Failed to open project (%s), error: %s", xcuitestcmd.ProjectFilePath, err)
return nil, nil, fmt.Errorf("failed to open project (%s), error: %s", xcuitestcmd.ProjectFilePath, err)
}

schemes, err = proj.Schemes()
Expand Down