Skip to content

Adding support for generating library product schemes #2768

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

Conversation

jeffctown
Copy link
Contributor

@jeffctown jeffctown commented May 29, 2020

Generating Library Product Schemes

This pull request adds a swift package generate-xcodeproj option --enable-library-schemes that will allow creation of schemes for library products during the scheme generation step of generating an Xcode project.

Motivation

I'm very excited to see binary dependency support was added to swift package manager! Once that is released, certain swift packages maintainers may want to start providing xcframework assets when they release their swift packages so that consumers can integrate with swift package manager's brand new feature.

This pull request aims to add more options to scheme generation while generating an Xcode project, so that it is easier to use xcodebuild to create xcframeworks from swift packages.

Use Case

The following is a common CI workflow that I can see some package maintainers desiring for their package repos:

Build Trigger: Push a new release or beta Tag to a git repo
Build Steps:
  1. Clone the package's repo
  2. swift package generate-xcodeproj
  3. Use xcodebuild to Archive your project using several destinations to get an archived framework for each apple SDK your Package supports.

Example using iOS and watchOS + Simulators:

xcodebuild archive -project "$PROJECT_NAME.xcodeproj" -scheme "$FRAMEWORK_NAME" 
            -destination "generic/platform=iOS" 
            -archivePath "archives/$PROJECT_NAME-iOS" 
            SKIP_INSTALL=NO BUILD_LIBRARY_FOR_DISTRIBUTION=YES   
xcodebuild archive -project "$PROJECT_NAME.xcodeproj" -scheme "$FRAMEWORK_NAME" 
            -destination "generic/platform=iOS Simulator" 
            -archivePath "archives/$PROJECT_NAME-iOS Simulator" 
            SKIP_INSTALL=NO BUILD_LIBRARY_FOR_DISTRIBUTION=YES   
xcodebuild archive -project "$PROJECT_NAME.xcodeproj" -scheme "$FRAMEWORK_NAME" 
            -destination "generic/platform=watchOS" 
            -archivePath "archives/$PROJECT_NAME-watchOS" 
            SKIP_INSTALL=NO BUILD_LIBRARY_FOR_DISTRIBUTION=YES   
xcodebuild archive -project "$PROJECT_NAME.xcodeproj" -scheme "$FRAMEWORK_NAME" 
            -destination "generic/platform=watchOS Simulator" 
            -archivePath "archives/$PROJECT_NAME-watchOS Simulator" 
            SKIP_INSTALL=NO BUILD_LIBRARY_FOR_DISTRIBUTION=YES   
  1. Use xcodebuild to Combine the Frameworks from the previous step into an xcframework
  2. Create a GitHub release for the new Tag
  3. Attach the xcframework binary asset to the new GitHub release

What's the Problem?

During scheme generation (in step 2 above), the current expected schemes generated are:

Notice that there will be no schemes generated for library product targets.

This sounds reasonable at first glance, but it's a very common practice within the swift command line tooling community to split the source for your tool into multiples modules — pushing the majority of your code into one or several frameworks and then having a thin executable client that wraps your frameworks.

A few examples of tools that I believe do this are SwiftLint, Carthage, SwiftFormat, swift-format, and Publish.

Quoting John Sundell in his article about creating command line tools, there are good reasons for doing this:

  • This will make testing a lot easier, and will also (and this is really cool) enable your command line tool to also be used as a dependency in other tools.

The problem I'm hoping to fix arises during archiving (step 3 above). If you:

  • Use swift package manager to generate the Xcode project for your package
  • Your package contains an executable product alongside library products

Expected Result: There should be a scheme that was created during Xcode project generation that will allow me to build the library products in preparation for creating an xcframework.

Actual Result: There will be no generated scheme that will build the library products in preparation for creating an xcframework. You have to manually create a scheme to use.

What Other Changeless Options Are There?

  1. Archive using the executable scheme or the master scheme. That won't work since both of these schemes include the executable product.

If a user tries to archive their executable or master scheme against iOS, watchOS, or tvOS, they receive the following expected errors:

❌  error: unable to resolve product type 'com.apple.product-type.tool' for platform 'iphoneos' (in target 'MyProj' from project 'MyProj')
❌  error: unable to resolve product type 'com.apple.product-type.tool' for platform 'watchos' (in target 'MyProj' from project 'MyProj')
❌  error: unable to resolve product type 'com.apple.product-type.tool' for platform 'tvos' (in target 'MyProj' from project 'MyProj')
  1. Commit your Xcode project along with manually created schemes for each library product you want to be able to archive.
Generating your Xcode project means you will never have a project.pbxproj merge conflict, which is a **huge** benefit of using swift package manager! It would be better if swift package manager could generate the library product schemes for us, so that we don't have to add an Xcode project or any schemes to version control.
  1. Try to find an xcodebuild archive command that can archive a target without a scheme (use -target instead of -scheme).

I don't think this will work either. The man page for xcodebuild states that a scheme is requirement of archiving.

archive Archive a scheme from the build root (SYMROOT). This requires specifying a scheme.

Proposed Solution

I kept backwards compatibility in mind for this change, to ensure that we don't affect the way that scheme generator is currently handled.

Acceptance Criteria:

  • Running: swift package generate-xcodeproj produces the same exact schemes as it did before this change.
  • Running: swift package generate-xcodeproj --enable-library-schemes will add additional schemes for any library products.
  • Any new library product schemes generated using the --enable-library-schemes option will also contain test targets.

Alternative Solutions Considered

Enable Library Product scheme generation by default

  • I think this would be a sensible alternative. Since I'm still relatively new here to contributing here I opted for the safer route of keeping default scheme generation as-is and adding the new behavior as an option that you have to enable. If the community feels that this alternative is a better direction, I am happy to update this PR to this instead.

@neonichu
Copy link
Contributor

Thank you for your PR, I haven't had a chance to look more deeply at it, but wanted to comment on something from your motivation section.

Once that is released, some package maintainers will want to provide binary dependency xcframeworks when they release thier swift packages. That way, the consumers of their package get to decide for themselves how they want to integrate your package: via source or binary.

Supporting this use case was actually a non-goal of the accepted binary dependencies proposal, as outlined in the "Alternatives considered" of it and as such won't be a good fit to support this.

@jeffctown
Copy link
Contributor Author

jeffctown commented Jun 1, 2020

@neonichu Thanks for providing that feedback. I think I see what you are talking about if you are referring to the General Approach section.

I updated the motivation section, because I think it was a bit misleading the way it was written. This change is basically about the schemes generated when swift package manager generates an Xcode project when you have an executable and library product in the same swift package. There is no scheme generated that allows you to archive the library product, so you have to manually create one. I think this could be a problem for vendored binaries also.

@jeffctown
Copy link
Contributor Author

I decided to just commit a scheme instead. Closing this.

@jeffctown jeffctown closed this Jul 15, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants