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

[CI-2849] Resolve env vars with xcodebuild #44

Merged
merged 2 commits into from
Apr 25, 2024
Merged
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
96 changes: 90 additions & 6 deletions step/step.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,25 @@ package step
import (
"fmt"
"path/filepath"
"regexp"
"strconv"
"strings"

"github.com/bitrise-io/go-steputils/v2/export"
"github.com/bitrise-io/go-steputils/v2/stepconf"
"github.com/bitrise-io/go-utils/pathutil"
"github.com/bitrise-io/go-utils/v2/command"
"github.com/bitrise-io/go-utils/v2/env"
"github.com/bitrise-io/go-utils/v2/log"
"github.com/bitrise-io/go-xcode/v2/autocodesign/projectmanager"
"github.com/bitrise-io/go-xcode/xcodeproject/xcodeproj"
)

const (
infoPlistFileKey = "INFOPLIST_FILE"
envVarRegex = `^.*\$\(.+\).*$`
Copy link
Contributor

Choose a reason for hiding this comment

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

FYI I was curious and checked if it handles nested env vars. I'm not sure if it's even handled by Xcode, but it works 😄
image

)

type Updater struct {
inputParser stepconf.InputParser
exporter export.Exporter
Expand Down Expand Up @@ -67,7 +77,7 @@ func (u Updater) Run(config Config) (Result, error) {
} else {
u.logger.Printf("The version numbers are stored in the plist file.")

err := updateVersionNumbersInInfoPlist(helper, config.Target, config.Configuration, buildVersion, config.BuildShortVersionString)
err := u.updateVersionNumbersInInfoPlist(helper, config.Scheme, config.Target, config.Configuration, buildVersion, config.BuildShortVersionString)
if err != nil {
return Result{}, err
}
Expand Down Expand Up @@ -124,34 +134,108 @@ func updateVersionNumbersInProject(helper *projectmanager.ProjectHelper, targetN
return nil
}

func updateVersionNumbersInInfoPlist(helper *projectmanager.ProjectHelper, targetName, configuration string, bundleVersion int64, shortVersion string) error {
func (u Updater) updateVersionNumbersInInfoPlist(helper *projectmanager.ProjectHelper, schemeName, targetName, configuration string, bundleVersion int64, shortVersion string) error {
buildConfig, err := buildConfiguration(helper, targetName, configuration)
if err != nil {
return err
}

infoPlistPath, err := buildConfig.BuildSettings.String("INFOPLIST_FILE")
infoPlistPath, err := buildConfig.BuildSettings.String(infoPlistFileKey)
if err != nil {
return err
}

absoluteInfoPlistPath := filepath.Join(filepath.Dir(helper.XcProj.Path), infoPlistPath)
// By default, the setting for the Info.plist file path is a relative path from the project file. Of course,
// developers can override this with something more custom to their setup. They can also use Xcode env vars as part
// of their path.
//
// An example from a SWAT ticket: `$(SRCROOT)/path/to/Info.plist`.
//
// The problem with this is that it is not a real path until the env var is resolved. And in this case, Xcode
// will define this env var, so we only know its value during an xcodebuild execution. So if we see an env var in
// the path, then we have to list the build settings with `xcodebuild -showBuildSettings` to get a valid path value.
if hasEnvVars(infoPlistPath) {
u.logger.Printf("Info.plist path contains env var: %s\n", infoPlistPath)
u.logger.Printf("Using xcodebuild to resolve it\n")

infoPlistPath, err = extractInfoPlistPathWithXcodebuild(helper.XcProj.Path, schemeName, targetName, configuration)
tothszabi marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
return err
}
}

infoPlist, format, _ := xcodeproj.ReadPlistFile(absoluteInfoPlistPath)
if pathutil.IsRelativePath(infoPlistPath) {
infoPlistPath = filepath.Join(filepath.Dir(helper.XcProj.Path), infoPlistPath)
}

infoPlist, format, _ := xcodeproj.ReadPlistFile(infoPlistPath)
infoPlist["CFBundleVersion"] = strconv.FormatInt(bundleVersion, 10)

if shortVersion != "" {
infoPlist["CFBundleShortVersionString"] = shortVersion
}

err = xcodeproj.WritePlistFile(absoluteInfoPlistPath, infoPlist, format)
err = xcodeproj.WritePlistFile(infoPlistPath, infoPlist, format)
if err != nil {
return err
}

return nil
}

func hasEnvVars(path string) bool {
re := regexp.MustCompile(envVarRegex)
containsEnvVar := re.Match([]byte(path))

return containsEnvVar
}

func extractInfoPlistPathWithXcodebuild(projectPath, scheme, target, configuration string) (string, error) {
args := []string{"-project", projectPath, "-scheme", scheme}

if target != "" {
args = append(args, "-target", target)
}

if configuration != "" {
args = append(args, "-configuration", configuration)
}

args = append(args, "-showBuildSettings")

cmd := command.NewFactory(env.NewRepository()).Create("xcodebuild", args, nil)
output, err := cmd.RunAndReturnTrimmedCombinedOutput()
if err != nil {
return "", err
}

path := infoPlistPathFromOutput(output)
if path == "" {
return "", fmt.Errorf("missing Info.plist file path")
}

return path, nil
}

func infoPlistPathFromOutput(output string) string {
lines := strings.Split(output, "\n")
for _, line := range lines {
split := strings.Split(line, " = ")

if len(split) < 2 {
continue
}

if strings.TrimSpace(split[0]) != infoPlistFileKey {
continue
}

return strings.TrimSpace(split[1])
}

return ""
}

func buildConfiguration(helper *projectmanager.ProjectHelper, targetName, configuration string) (*xcodeproj.BuildConfiguration, error) {
if targetName == "" {
targetName = helper.MainTarget.Name
Expand Down
4 changes: 2 additions & 2 deletions testdata/project/Example/Example.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -499,7 +499,7 @@
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = KUN9FDAF5L;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = Example/Info.plist;
INFOPLIST_FILE = "$(SRCROOT)/Example/Info.plist";
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen;
INFOPLIST_KEY_UIMainStoryboardFile = Main;
Expand Down Expand Up @@ -527,7 +527,7 @@
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = KUN9FDAF5L;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_FILE = Example/Info.plist;
INFOPLIST_FILE = "$(SRCROOT)/Example/Info.plist";
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen;
INFOPLIST_KEY_UIMainStoryboardFile = Main;
Expand Down