Skip to content

Commit

Permalink
Add xcodebuild command destination flag (#147)
Browse files Browse the repository at this point in the history
  • Loading branch information
shams-ahmed committed Jan 28, 2022
1 parent 5ca9efb commit de280f5
Show file tree
Hide file tree
Showing 6 changed files with 242 additions and 1 deletion.
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) {
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

0 comments on commit de280f5

Please sign in to comment.