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
67 changes: 67 additions & 0 deletions cmd/xcode.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,15 @@ 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"
"github.com/bitrise-io/go-utils/log"
"github.com/bitrise-io/go-utils/pathutil"
"github.com/bitrise-io/go-xcode/xcodeproject/xcodeproj"
"github.com/bitrise-io/go-xcode/xcodeproject/xcscheme"
"github.com/bitrise-io/go-xcode/xcodeproject/xcworkspace"
"github.com/bitrise-io/goinp/goinp"
"github.com/spf13/cobra"
)
Expand All @@ -32,6 +36,7 @@ var (
paramXcodeProjectFilePath string
paramXcodeScheme string
paramXcodebuildSDK string
paramXcodeDestination string
)

func init() {
Expand All @@ -40,6 +45,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 -destination option takes as its argument a destination specifier describing the device (or devices) to use as a destination. If a value is specified for this flag it'll be passed to xcodebuild.")
shams-ahmed marked this conversation as resolved.
Show resolved Hide resolved
}

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

if paramXcodeDestination != "" {
xcodeCmd.DESTINATION = paramXcodeDestination
} else {
var project xcodeproj.XcodeProj
shams-ahmed marked this conversation as resolved.
Show resolved Hide resolved
var scheme xcscheme.Scheme

if xcodeproj.IsXcodeProj(xcodeCmd.ProjectFilePath) {
shams-ahmed marked this conversation as resolved.
Show resolved Hide resolved
proj, err := xcodeproj.Open(xcodeCmd.ProjectFilePath)
if err != nil {
return fmt.Errorf("Failed to open project (%s), error: %s", xcodeCmd.ProjectFilePath, err)
shams-ahmed marked this conversation as resolved.
Show resolved Hide resolved
}

projectScheme, _, err := proj.Scheme(xcodeCmd.Scheme)
shams-ahmed marked this conversation as resolved.
Show resolved Hide resolved

if err != nil {
return fmt.Errorf("failed to find scheme (%s) in project (%s), error: %s", xcodeCmd.Scheme, proj.Path, err)
}

project = proj
scheme = *projectScheme
} else {
workspace, err := xcworkspace.Open(xcodeCmd.ProjectFilePath)
if err != nil {
return err
}

projects, err := workspace.ProjectFileLocations()
if err != nil {
return err
}

for _, projectLocation := range projects {
if exist, err := pathutil.IsPathExists(projectLocation); err != nil {
return fmt.Errorf("failed to check if project exist at: %s, error: %s", projectLocation, err)
} else if !exist {
// at this point we are interested the schemes visible for the workspace
continue
}

possibleProject, err := xcodeproj.Open(projectLocation)
projectScheme, _, err := possibleProject.Scheme(xcodeCmd.Scheme)

if projectScheme != nil && err == nil {
project = possibleProject
scheme = *projectScheme

break
}
}
}

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

xcodeCmd.DESTINATION = destination

fmt.Print("Setting -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
66 changes: 66 additions & 0 deletions cmd/xcodeUITests.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,17 @@ 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"
"github.com/bitrise-io/go-utils/log"
"github.com/bitrise-io/go-utils/pathutil"
"github.com/bitrise-io/go-utils/stringutil"
"github.com/bitrise-io/go-xcode/utility"
"github.com/bitrise-io/go-xcode/xcodeproject/xcodeproj"
"github.com/bitrise-io/go-xcode/xcodeproject/xcscheme"
"github.com/bitrise-io/go-xcode/xcodeproject/xcworkspace"
"github.com/bitrise-io/goinp/goinp"
"github.com/spf13/cobra"
)
Expand All @@ -34,6 +39,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 -destination option takes as its argument a destination specifier describing the device (or devices) to use as a destination. If a value is specified for this flag it'll be passed to xcodebuild.")
shams-ahmed marked this conversation as resolved.
Show resolved Hide resolved
}

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

if paramXcodeDestination != "" {
xcodeUITestsCmd.DESTINATION = paramXcodeDestination
} else {
var project xcodeproj.XcodeProj
var scheme xcscheme.Scheme

if xcodeproj.IsXcodeProj(xcodeUITestsCmd.ProjectFilePath) {
proj, err := xcodeproj.Open(xcodeUITestsCmd.ProjectFilePath)
if err != nil {
return fmt.Errorf("Failed to open project (%s), error: %s", xcodeUITestsCmd.ProjectFilePath, err)
}

projectScheme, _, err := proj.Scheme(xcodeUITestsCmd.Scheme)
if err != nil {
return fmt.Errorf("failed to find scheme (%s) in project (%s), error: %s", xcodeUITestsCmd.Scheme, proj.Path, err)
}

project = proj
scheme = *projectScheme
} else {
workspace, err := xcworkspace.Open(xcodeUITestsCmd.ProjectFilePath)
if err != nil {
return err
}

projects, err := workspace.ProjectFileLocations()
if err != nil {
return err
}

for _, projectLocation := range projects {
if exist, err := pathutil.IsPathExists(projectLocation); err != nil {
return fmt.Errorf("failed to check if project exist at: %s, error: %s", projectLocation, err)
} else if !exist {
// at this point we are interested the schemes visible for the workspace
continue
}

possibleProject, err := xcodeproj.Open(projectLocation)
projectScheme, _, err := possibleProject.Scheme(xcodeUITestsCmd.Scheme)

if projectScheme != nil && err == nil {
project = possibleProject
scheme = *projectScheme

break
}
}
}

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

xcodeUITestsCmd.DESTINATION = destination

fmt.Print("Setting -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
101 changes: 101 additions & 0 deletions utility/utility.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
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/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 +28,98 @@ 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)
}
}
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
shams-ahmed marked this conversation as resolved.
Show resolved Hide resolved
}

// 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
Loading