Swift Package Manager and Xcode: Retaining Xcode Settings?

1.9k views Asked by At

I am developing a server in Swift and using the Swift Package Manager. And find it convenient when doing my development on my Mac OS system to generate a Xcode project to use Xcode as my IDE (i.e., From time to time, my package dependencies have to be updated. I've been using swift package generate-xcodeproj to do this. My problem comes in at this point-- I have created some settings in Xcode. E.g., I've set a DEBUG flag, and I have a .plist file that is in the Copy Files Phase. These get lost when I regenerate the Xcode project. It seems I cannot simply use swift package update because sometimes files change in the dependencies and these don't get propagated to the Xcode project.

What I'd like is a means to separately establish Xcode settings in a file outside of Xcode, that can be imported into Xcode when I do the swift package generate-xcodeproj. I have not seen a way to do this.

A related question is: When I do a swift build I'd like those same build settings to be used.

Suggestions?

3

There are 3 answers

4
Vadim Eisenberg On

I would use a script or a makefile to import your settings into the generated Xcode project, each time you regenerate it. You can use xcodeproj rubygem.

See an example script.

Regarding using your settings in swift build, can you give an example of such a setting? In general, a makefile can read your settings file and pass the corresponding parameters to swift build.

1
neoneye On

I can't help with Copy Files Phase.

However I have just been toying with conditional compilation, like this:

swift package generate-xcodeproj --xcconfig-overrides Sandbox.xcconfig

Sandbox.xcconfig

FLAG_SANDBOX = -DSANDBOX
OTHER_SWIFT_FLAGS = $(FLAG_SANDBOX)

This creates an Xcode project where SANDBOXis definded.

This can be used in swift code like this

#if SANDBOX
    print("sandbox")
#else
    print("production")
#endif
0
Chris Prince On

Based on @vadim's answer above, here's a xcodeproj rubygem solution to the first part of my question:

#!/usr/bin/ruby

# Tweak the .xcodeproj after creating with the swift package manager.

# Resources: 
# https://stackoverflow.com/questions/41527782/swift-package-manager-and-xcode-retaining-xcode-settings/41612477#41612477
# https://stackoverflow.com/questions/20072937/add-run-script-build-phase-to-xcode-project-from-podspec
# https://github.com/IBM-Swift/Kitura-Build/blob/master/build/fix_xcode_project.rb
# http://www.rubydoc.info/github/CocoaPods/Xcodeproj/Xcodeproj%2FProject%2FObject%2FAbstractTarget%3Anew_shell_script_build_phase
# http://www.rubydoc.info/github/CocoaPods/Xcodeproj/Xcodeproj/Project/Object/AbstractTarget
# https://gist.github.com/niklasberglund/129065e2612d00c811d0
# https://github.com/CocoaPods/Xcodeproj
# https://stackoverflow.com/questions/34367048/how-do-you-automate-do-copy-files-in-build-phases-using-a-cocoapods-post-insta?rq=1

require 'xcodeproj'

path_to_project = "Server.xcodeproj"
project = Xcodeproj::Project.open(path_to_project)

# 1) Add Copy Files Phase for Server.plist to the Products directory for Server target
target = project.targets.select { |target| target.name == 'Server' }.first
puts "Add Copy Files Phase to #{target}"
phase = target.new_copy_files_build_phase()

# Contrary to the docs (see http://www.rubydoc.info/github/CocoaPods/Xcodeproj/Xcodeproj/Project/Object/PBXCopyFilesBuildPhase) I believe this is not a path, but rather a code, e.g., 16 indicates to copy the file to the Products Directory.
phase.dst_subfolder_spec = "16"

fileRef = project.new(Xcodeproj::Project::Object::PBXFileReference)
fileRef.path = 'Server.plist'

phase.add_file_reference(fileRef)   

# 2) Add in script phase for testing target-- because I haven't figured out to get access to the Products directory at test-run time.
target = project.targets.select { |target| target.name == 'ServerTests' }.first
puts "Add Script Phase to #{target}"
phase = target.new_shell_script_build_phase()
phase.shell_script = "cp Server.plist /tmp"

# 3) Add in DEBUG flag

# A little overkill, but hopefully appending a DEBUG flag in the Debug configuration for each target doesn't hurt it.
project.targets.each do |target|
    puts "Appending DEBUG flag to #{target}"

    if target.build_settings('Debug')['OTHER_SWIFT_FLAGS'].nil?
        target.build_settings('Debug')['OTHER_SWIFT_FLAGS'] = ""
    end

    target.build_settings('Debug')['OTHER_SWIFT_FLAGS'] << '-DDEBUG'
end

project.save()