Skip to content

Commit d610089

Browse files
authored
Support custom xcconfig file path (#297)
Allow passing a file path to an existing xcconfig file, instead of providing the file contents directly. * `xcconfig_content` can now be a path to a xcconfig file, or left empty. This allows using a custom xcconfig file that already exists in the repository.
1 parent 16729ba commit d610089

File tree

11 files changed

+226
-62
lines changed

11 files changed

+226
-62
lines changed

README.md

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ To configure the Step:
2929

3030
Under **xcodebuild configuration**:
3131
1. **Build configuration**: Specify Xcode Build Configuration. The Step uses the provided Build Configuration's Build Settings to understand your project's code signing configuration. If not provided, the Archive action's default Build Configuration will be used.
32-
2. **Build settings (xconfig)**: Build settings to override the project's build settings. The build settings must be separated by a newline character (`\n`).
32+
2. **Build settings (xcconfig)**: Build settings to override the project's build settings. Can be the contents, file path or empty.
3333
3. **Perform clean action**: If this input is set, a `clean` xcodebuild action will be performed besides the `archive` action.
3434

3535
Under **Xcode build log formatting**:
@@ -67,6 +67,38 @@ Add this step directly to your workflow in the [Bitrise Workflow Editor](https:/
6767

6868
You can also run this step directly with [Bitrise CLI](https://github.com/bitrise-io/bitrise).
6969

70+
### Examples
71+
72+
Build a development IPA:
73+
```yaml
74+
- xcode-archive:
75+
inputs:
76+
- project_path: ./ios-sample/ios-sample.xcodeproj
77+
- scheme: ios-sample
78+
- distribution_method: development
79+
```
80+
81+
Build a development IPA with custom xcconfig content:
82+
```yaml
83+
- xcode-archive:
84+
inputs:
85+
- project_path: ./ios-sample/ios-sample.xcodeproj
86+
- scheme: ios-sample
87+
- distribution_method: development
88+
- xcconfig_content: |
89+
CODE_SIGN_IDENTITY = Apple Development
90+
```
91+
92+
Build a development IPA with custom xcconfig file path:
93+
```yaml
94+
- xcode-archive:
95+
inputs:
96+
- project_path: ./ios-sample/ios-sample.xcodeproj
97+
- scheme: ios-sample
98+
- distribution_method: development
99+
- xcconfig_content: ./ios-sample/ios-sample/Configurations/Dev.xcconfig
100+
```
101+
70102
## ⚙️ Configuration
71103
72104
<details>
@@ -78,7 +110,7 @@ You can also run this step directly with [Bitrise CLI](https://github.com/bitris
78110
| `scheme` | Xcode Scheme name. The input value sets xcodebuild's `-scheme` option. | required | `$BITRISE_SCHEME` |
79111
| `distribution_method` | Describes how Xcode should export the archive. | required | `development` |
80112
| `configuration` | Xcode Build Configuration. If not specified, the default Build Configuration will be used. The input value sets xcodebuild's `-configuration` option. | | |
81-
| `xcconfig_content` | Build settings to override the project's build settings. Build settings must be separated by newline character (`\n`). Example: ``` COMPILER_INDEX_STORE_ENABLE = NO ONLY_ACTIVE_ARCH[config=Debug][sdk=*][arch=*] = YES ``` The input value sets xcodebuild's `-xcconfig` option. | | `COMPILER_INDEX_STORE_ENABLE = NO` |
113+
| `xcconfig_content` | Build settings to override the project's build settings, using xcodebuild's `-xcconfig` option. If empty, no setting is changed. This is required when the `-xcconfig` additional option is used. When set it can be either: 1. Existing `.xcconfig` file path. Example: `./ios-sample/ios-sample/Configurations/Dev.xcconfig` 2. The contents of a newly created temporary `.xcconfig` file. (This is the default.) Build settings must be separated by newline character (`\n`). Example: ``` COMPILER_INDEX_STORE_ENABLE = NO ONLY_ACTIVE_ARCH[config=Debug][sdk=*][arch=*] = YES ``` | | `COMPILER_INDEX_STORE_ENABLE = NO` |
82114
| `perform_clean_action` | If this input is set, `clean` xcodebuild action will be performed besides the `archive` action. | required | `no` |
83115
| `xcodebuild_options` | Additional options to be added to the executed xcodebuild command. | | |
84116
| `log_formatter` | Defines how `xcodebuild` command's log is formatted. Available options: - `xcpretty`: The xcodebuild command's output will be prettified by xcpretty. - `xcodebuild`: Only the last 20 lines of raw xcodebuild output will be visible in the build log. The raw xcodebuild log will be exported in both cases. | required | `xcpretty` |
@@ -89,6 +121,7 @@ You can also run this step directly with [Bitrise CLI](https://github.com/bitris
89121
| `passphrase_list` | Passphrases for the provided code signing certificates. Specify as many passphrases as many Code signing certificate URL provided, separated by a pipe (`\|`) character. Certificates without a passphrase: for using a single certificate, leave this step input empty. For multiple certificates, use the separator as if there was a passphrase (examples: `pass\|`, `\|pass\|`, `\|`) | sensitive | `$BITRISE_CERTIFICATE_PASSPHRASE` |
90122
| `keychain_path` | Path to the Keychain where the code signing certificates will be installed. | required | `$HOME/Library/Keychains/login.keychain` |
91123
| `keychain_password` | Password for the provided Keychain. | required, sensitive | `$BITRISE_KEYCHAIN_PASSWORD` |
124+
| `fallback_provisioning_profile_url_list` | If set, provided provisioning profiles will be used on Automatic code signing error. URL of the provisioning profile to download. Multiple URLs can be specified, separated by a newline or pipe (`\|`) character. You can specify a local path as well, using the `file://` scheme. For example: `file://./BuildAnything.mobileprovision`. Can also provide a local directory that contains files with `.mobileprovision` extension. For example: `./profilesDirectory/` | sensitive | |
92125
| `export_development_team` | The Developer Portal team to use for this export Defaults to the team used to build the archive. Defining this is also required when Automatic Code Signing is set to `apple-id` and the connected account belongs to multiple teams. | | |
93126
| `compile_bitcode` | For __non-App Store__ exports, should Xcode re-compile the app from bitcode? | required | `yes` |
94127
| `upload_bitcode` | For __App Store__ exports, should the package include bitcode? | required | `yes` |

additional_options.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package main
2+
3+
import "github.com/bitrise-io/go-utils/sliceutil"
4+
5+
func generateAdditionalOptions(platform string, customOptions []string) []string {
6+
destination := "generic/platform=" + platform
7+
destinationOptions := []string{"-destination", destination}
8+
9+
var options []string
10+
if len(customOptions) != 0 {
11+
if !sliceutil.IsStringInSlice("-destination", customOptions) {
12+
options = append(options, destinationOptions...)
13+
}
14+
15+
options = append(options, customOptions...)
16+
} else {
17+
options = append(options, destinationOptions...)
18+
}
19+
20+
return options
21+
}

additional_options_test.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package main
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/require"
7+
)
8+
9+
func Test_generateAdditionalOptions(t *testing.T) {
10+
tests := []struct {
11+
name string
12+
platform string
13+
customOptions []string
14+
want []string
15+
}{
16+
{
17+
name: "no custom options",
18+
platform: "iOS",
19+
want: []string{"-destination", "generic/platform=iOS"},
20+
},
21+
{
22+
name: "custom opts",
23+
platform: "iOS",
24+
customOptions: []string{"-scmProvider", "system"},
25+
want: []string{"-destination", "generic/platform=iOS", "-scmProvider", "system"},
26+
},
27+
{
28+
name: "custom opts with destination",
29+
platform: "iOS",
30+
customOptions: []string{"-scmProvider", "system", "-destination", "generic/platform=iOS"},
31+
want: []string{"-scmProvider", "system", "-destination", "generic/platform=iOS"},
32+
},
33+
}
34+
for _, tt := range tests {
35+
t.Run(tt.name, func(t *testing.T) {
36+
got := generateAdditionalOptions(tt.platform, tt.customOptions)
37+
38+
require.Equal(t, tt.want, got)
39+
})
40+
}
41+
}

bitrise.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,3 +47,4 @@ workflows:
4747
- git::https://github.com/bitrise-steplib/steps-readme-generator.git@main:
4848
inputs:
4949
- contrib_section: docs/contribution.md
50+
- example_section: docs/examples.md

docs/examples.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
### Examples
2+
3+
Build a development IPA:
4+
```yaml
5+
- xcode-archive:
6+
inputs:
7+
- project_path: ./ios-sample/ios-sample.xcodeproj
8+
- scheme: ios-sample
9+
- distribution_method: development
10+
```
11+
12+
Build a development IPA with custom xcconfig content:
13+
```yaml
14+
- xcode-archive:
15+
inputs:
16+
- project_path: ./ios-sample/ios-sample.xcodeproj
17+
- scheme: ios-sample
18+
- distribution_method: development
19+
- xcconfig_content: |
20+
CODE_SIGN_IDENTITY = Apple Development
21+
```
22+
23+
Build a development IPA with custom xcconfig file path:
24+
```yaml
25+
- xcode-archive:
26+
inputs:
27+
- project_path: ./ios-sample/ios-sample.xcodeproj
28+
- scheme: ios-sample
29+
- distribution_method: development
30+
- xcconfig_content: ./ios-sample/ios-sample/Configurations/Dev.xcconfig
31+
```

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ require (
77
github.com/bitrise-io/go-utils v1.0.1
88
github.com/bitrise-io/go-utils/v2 v2.0.0-alpha.2
99
github.com/bitrise-io/go-xcode v1.0.6
10-
github.com/bitrise-io/go-xcode/v2 v2.0.0-alpha.14
10+
github.com/bitrise-io/go-xcode/v2 v2.0.0-alpha.15
1111
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
1212
github.com/stretchr/testify v1.7.0
1313
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ github.com/bitrise-io/go-utils/v2 v2.0.0-alpha.2 h1:w3fwgLLmxMOpYNa6W5aLtJZE8M8+
1111
github.com/bitrise-io/go-utils/v2 v2.0.0-alpha.2/go.mod h1:sy+Ir1X8P3tAAx/qU/r+hqDjHDcrMjIzDEvId1wqNc4=
1212
github.com/bitrise-io/go-xcode v1.0.6 h1:hSKwkDXUn9/gMk6HiJRUvurGWelfQEBWcO7JAvXi+y8=
1313
github.com/bitrise-io/go-xcode v1.0.6/go.mod h1:Y0Wu2dXm0MilJ/4D3+gPHaNMlUcP+1DjIPoLPykq7wY=
14-
github.com/bitrise-io/go-xcode/v2 v2.0.0-alpha.14 h1:Tfo5QuCZmb/BTC8QWEhPxuP02FzjnjEE19jTzQFag2o=
15-
github.com/bitrise-io/go-xcode/v2 v2.0.0-alpha.14/go.mod h1:IhG2l/bM8+809Jlwt4hgRzOkRfPmhEybfWMOJdEGnEU=
14+
github.com/bitrise-io/go-xcode/v2 v2.0.0-alpha.15 h1:swynA2yBvKWdMEPDz/GFM55ELekauWIvtDCLWKScVhU=
15+
github.com/bitrise-io/go-xcode/v2 v2.0.0-alpha.15/go.mod h1:IhG2l/bM8+809Jlwt4hgRzOkRfPmhEybfWMOJdEGnEU=
1616
github.com/bitrise-io/pkcs12 v0.0.0-20211108084543-e52728e011c8 h1:kmvU8AxrNTxXsVPKepBHD8W+eCVmeaKyTkRuUJB2K38=
1717
github.com/bitrise-io/pkcs12 v0.0.0-20211108084543-e52728e011c8/go.mod h1:UiXKNs0essbC14a2TvGlnUKo9isP9m4guPrp8KJHJpU=
1818
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=

main.go

Lines changed: 39 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -113,8 +113,9 @@ type Inputs struct {
113113
// Config ...
114114
type Config struct {
115115
Inputs
116-
XcodeMajorVersion int
117-
CodesignManager *codesign.Manager // nil if automatic code signing is "off"
116+
XcodeMajorVersion int
117+
XcodebuildAdditionalOptions []string
118+
CodesignManager *codesign.Manager // nil if automatic code signing is "off"
118119
}
119120

120121
var envRepository = env.NewRepository()
@@ -199,6 +200,7 @@ type XcodeArchiveStep struct {
199200
xcodeVersionProvider xcodeVersionProvider
200201
stepInputParser stepconf.InputParser
201202
pathProvider pathutil.PathProvider
203+
pathChecker pathutil.PathChecker
202204
fileManager fileutil.FileManager
203205
}
204206

@@ -208,6 +210,7 @@ func NewXcodeArchiveStep() XcodeArchiveStep {
208210
xcodeVersionProvider: newXcodebuildXcodeVersionProvider(),
209211
stepInputParser: stepconf.NewInputParser(env.NewRepository()),
210212
pathProvider: pathutil.NewPathProvider(),
213+
pathChecker: pathutil.NewPathChecker(),
211214
fileManager: fileutil.NewFileManager(),
212215
}
213216
}
@@ -226,6 +229,20 @@ func (s XcodeArchiveStep) ProcessInputs() (Config, error) {
226229
logger.EnableDebugLog(config.VerboseLog)
227230
v1log.SetEnableDebugLog(config.VerboseLog) // For compatibility
228231

232+
var err error
233+
config.XcodebuildAdditionalOptions, err = shellquote.Split(inputs.XcodebuildOptions)
234+
if err != nil {
235+
return Config{}, fmt.Errorf("provided XcodebuildOptions (%s) are not valid CLI parameters: %s", inputs.XcodebuildOptions, err)
236+
}
237+
238+
if strings.TrimSpace(config.XcconfigContent) == "" {
239+
config.XcconfigContent = ""
240+
}
241+
if sliceutil.IsStringInSlice("-xcconfig", config.XcodebuildAdditionalOptions) &&
242+
config.XcconfigContent != "" {
243+
return Config{}, fmt.Errorf("`-xcconfig` option found in XcodebuildOptions (`xcodebuild_options`), please clear Build settings (xcconfig) (`xcconfig_content`) input as only one can be set")
244+
}
245+
229246
if config.ExportOptionsPlistContent != "" {
230247
var options map[string]interface{}
231248
if _, err := plist.Unmarshal([]byte(config.ExportOptionsPlistContent), &options); err != nil {
@@ -438,7 +455,7 @@ type xcodeArchiveOpts struct {
438455

439456
PerformCleanAction bool
440457
XcconfigContent string
441-
CustomOptions []string
458+
AdditionalOptions []string
442459

443460
CacheLevel string
444461
}
@@ -496,12 +513,14 @@ func (s XcodeArchiveStep) xcodeArchive(opts xcodeArchiveOpts) (xcodeArchiveOutpu
496513
archiveCmd.SetCustomBuildAction("clean")
497514
}
498515

499-
xcconfigWriter := xcconfig.NewWriter(s.pathProvider, s.fileManager)
500-
xcconfigPath, err := xcconfigWriter.Write(opts.XcconfigContent)
501-
if err != nil {
502-
return out, fmt.Errorf("failed to write xcconfig file contents: %w", err)
516+
if opts.XcconfigContent != "" {
517+
xcconfigWriter := xcconfig.NewWriter(s.pathProvider, s.fileManager, s.pathChecker)
518+
xcconfigPath, err := xcconfigWriter.Write(opts.XcconfigContent)
519+
if err != nil {
520+
return out, fmt.Errorf("failed to write xcconfig file contents: %w", err)
521+
}
522+
archiveCmd.SetXCConfigPath(xcconfigPath)
503523
}
504-
archiveCmd.SetXCConfigPath(xcconfigPath)
505524

506525
tmpDir, err := v1pathutil.NormalizedOSTempDirPath("xcodeArchive")
507526
if err != nil {
@@ -514,21 +533,8 @@ func (s XcodeArchiveStep) xcodeArchive(opts xcodeArchiveOpts) (xcodeArchiveOutpu
514533
archiveCmd.SetAuthentication(*opts.XcodeAuthOptions)
515534
}
516535

517-
destination := "generic/platform=" + string(platform)
518-
destinationOptions := []string{"-destination", destination}
519-
520-
options := []string{}
521-
if len(opts.CustomOptions) != 0 {
522-
if !sliceutil.IsStringInSlice("-destination", opts.CustomOptions) {
523-
options = append(options, destinationOptions...)
524-
}
525-
526-
options = append(options, opts.CustomOptions...)
527-
} else {
528-
options = append(options, destinationOptions...)
529-
}
530-
531-
archiveCmd.SetCustomOptions(options)
536+
additionalOptions := generateAdditionalOptions(string(platform), opts.AdditionalOptions)
537+
archiveCmd.SetCustomOptions(additionalOptions)
532538

533539
var swiftPackagesPath string
534540
if opts.XcodeMajorVersion >= 11 {
@@ -776,10 +782,10 @@ type RunOpts struct {
776782
CodesignManager *codesign.Manager
777783

778784
// Archive
779-
PerformCleanAction bool
780-
XcconfigContent string
781-
XcodebuildOptions string
782-
CacheLevel string
785+
PerformCleanAction bool
786+
XcconfigContent string
787+
XcodebuildAdditionalOptions []string
788+
CacheLevel string
783789

784790
// IPA Export
785791
CustomExportOptionsPlistContent string
@@ -810,17 +816,12 @@ func (s XcodeArchiveStep) Run(opts RunOpts) (RunOut, error) {
810816
authOptions *xcodebuild.AuthenticationParams
811817
)
812818

813-
customOptions, err := shellquote.Split(opts.XcodebuildOptions)
814-
if err != nil {
815-
return out, fmt.Errorf("provided XcodebuildOptions (%s) are not valid CLI parameters: %s", opts.XcodebuildOptions, err)
816-
}
817-
818819
logger.Println()
819820
if opts.XcodeMajorVersion >= 11 {
820821
// Resolve Swift package dependencies, so running -showBuildSettings later is faster later
821822
// Specifying a scheme is required for workspaces
822823
resolveDepsCmd := xcodebuild.NewResolvePackagesCommandModel(opts.ProjectPath, opts.Scheme, opts.Configuration)
823-
resolveDepsCmd.SetCustomOptions(customOptions)
824+
resolveDepsCmd.SetCustomOptions(opts.XcodebuildAdditionalOptions)
824825
if err := resolveDepsCmd.Run(); err != nil {
825826
logger.Warnf("%s", err)
826827
}
@@ -886,7 +887,7 @@ func (s XcodeArchiveStep) Run(opts RunOpts) (RunOut, error) {
886887

887888
PerformCleanAction: opts.PerformCleanAction,
888889
XcconfigContent: opts.XcconfigContent,
889-
CustomOptions: customOptions,
890+
AdditionalOptions: opts.XcodebuildAdditionalOptions,
890891
CacheLevel: opts.CacheLevel,
891892
}
892893
archiveOut, err := s.xcodeArchive(archiveOpts)
@@ -1171,10 +1172,10 @@ func RunStep() error {
11711172

11721173
CodesignManager: config.CodesignManager,
11731174

1174-
PerformCleanAction: config.PerformCleanAction,
1175-
XcconfigContent: config.XcconfigContent,
1176-
XcodebuildOptions: config.XcodebuildOptions,
1177-
CacheLevel: config.CacheLevel,
1175+
PerformCleanAction: config.PerformCleanAction,
1176+
XcconfigContent: config.XcconfigContent,
1177+
XcodebuildAdditionalOptions: config.XcodebuildAdditionalOptions,
1178+
CacheLevel: config.CacheLevel,
11781179

11791180
CustomExportOptionsPlistContent: config.ExportOptionsPlistContent,
11801181
ExportMethod: config.ExportMethod,

0 commit comments

Comments
 (0)