Pitch: Include extra Package.swift files in Manifest project

Need/problem

Currently, the tuist edit command generates a project that includes only the Tuist/Package.swift file. While this is useful for editing the Tuist manifests, it does not support workflows where the Tuist/Package.swift file references other Package.swift files. As a result:

  • Developers lose Xcode’s code completion and navigation support for these additional Package.swift files.
  • Editing interconnected Swift packages becomes cumbersome, requiring either using a separate editor or opening the external Package.swift in a separate instance of Xcode, which leads to Xcode resolving the packages (wastefully) and producing a separate, irrelevant Package.resolved file.

For example, in a setup where Tuist/Package.swift depends on PackageA.swift and PackageB.swift, these additional package manifests are not included in the generated project. This limitation makes it a little confusing for other developers to follow how things are set up and where various external dependencies live.

Motivation

The motivation for this proposal is to streamline workflows involving multiple Swift package manifests and improve the developer experience by leveraging Xcode’s code completion and navigation features.

Detailed design

Proposed Solution

Enhance the tuist edit command to support the optional inclusion of additional Package.swift files in the generated project. This can be achieved by:

  1. Command-Line Argument
    Adding a new flag to tuist edit to specify additional Package.swift files:
    tuist edit --include-packages path/to/PackageA.swift path/to/PackageB.swift
    

This is not my preferred approach, since developers will need to do this often and would essentially require tooling in front of tuist to make seamless.

  1. Configuration-Based Inclusion using Tuist.swift

Something like this could work.

let tuist = Tuist(
    project: .tuist(
        additionalPackageManifests: ["PackageA.swift", "PackageB.swift"]
    )
)

Though my preferred approach would be for this to be automatic…

  1. Automatic Detection

Automatically detecting and including referenced Package.swift files from Tuist/Package.swift if feasible. For example, parsing the manifest to identify dependencies and including their manifests in the generated project.

This could be accomplished with swift package describing --type json and parsing the "dependencies" key, for instance:

  ...
  "dependencies" : [
    {
      "identity" : "components",
      "path" : "/Users/ben/myproj/components",
      "type" : "fileSystem"
    }
  ],

Using this we could infer that there is a Package.swift to include at components/Packages.swift

Note: recursive local packages would not be expected to be supported with this approach

Implementation Notes

  • Update the tuist edit logic to:
    • Include any referenced packages from Tuist/Package.swift
    • Add these package manifests to the generated Xcode project.
    • Ensure backward compatibility by making this feature configurable via Tuist.swift

Example Workflow

  1. A project has the following structure:

    ├── Tuist
    │   ├── Package.swift
    ├── PackageA
    │   ├── Package.swift
    ├── PackageB
        ├── Package.swift
    
  2. Run the enhanced tuist edit command:

    tuist edit
    
  3. The generated Xcode project includes Tuist/Package.swift, PackageA.swift, and PackageB.swift, providing full code completion and navigation support in Xcode.

Drawbacks

  • Implementation Complexity: Adding support for multiple Package.swift files increases the complexity of the tuist edit implementation.
  • A change in Tuist.swift would be a breaking change

Alternatives

  1. Manual Inclusion: Developers can manually open additional Package.swift files in separate Xcode projects. However, this is tedious and disrupts workflow continuity. Additionally it produces unnecessary Package resolutions and a Package.resolved that is more confusing.
  2. Embed all dependencies in Tuist/Package.swift – this is also not currently a valid approach, as these dependencies won’t be understood as external if they are listed in the products section, only dependencies.
  3. Do Nothing: Continue with the current behavior, leaving developers to work around the limitation. This negatively impacts productivity and developer experience.

Adoption strategy

This feature is non-breaking and entirely optional. Existing Tuist developers can continue using tuist edit as before without any changes. To adopt the feature, developers can:

How we teach this

  • Documentation: Update the tuist edit documentation to include a section about this behavior, updated Tuist.swift API documentation (if needed).

One major benefit of this approach is that you don’t need to know about it, it can “just work” and would be fairly obvious to users, similar to how a Project.swift gets picked up automatically.

This feature aligns with existing Tuist patterns and does not require significant reorganization of Tuist documentation or teaching materials.

Thanks for writing this up, @benscheirman!

I agree this is the preferred approach.

We know what local packages you are referring to when constructing the graph – so, the process for finding their paths should be similar to locating project manifests as we do here

I’m not sure this is actually needed – this wouldn’t be a breaking change, so I don’t currently see a reason to make this configurable as it would be a general improvement.

Before making this a default behavior, I’d like to confirm this wouldn’t have a negative performance effect on tuist edit – I believe finding the Package.swift manifests should be fast, we already do that during tuist generate, but let’s double check that when implementing this.

Other thing not discussed in this proposal that we should touch on is how to structure the extra packages in the generated project. This is the current behavior with the single Package.swift:

The most sensible is to probably follow the file system structure also in the generated Xcode project. We will end up with multiple Package.swift manifests, which could make the navigation using search a slightly more complicated, but this might not be that much of an issue as long as the user’s hierarchy is done sensibly.

The other aspect is which module the extra Package.swift manifests should belong to. I think it should be fine if all are a part of the Packages module, but this is to be confirmed.

Overall, I’m supportive of this proposal, even if it does mean some extra maintenance burden.

I’m a little confused by the screenshot here. Do you have a root Package.swift in this case? Does that already get picked up automatically?

My Tuist/Package.swift looks like this:

// swift-tools-version: 6.0
// swiftlint:disable
import PackageDescription

#if TUIST
    import ProjectDescription
    let packageSettings = PackageSettings(
        productTypes: [:]
    )
#endif

let package = Package(
    name: "MyDependencies",
    platforms: [
        .iOS(.v16)
    ],
    dependencies: [
        .package(path: "../components")
    ]
)

and inside of components/Package.swift I have a standard package with lots of products & targets.

I don’t think this would have much of an impact on the tuist edit command. Additionally this command is something you don’t often run ( compared to tuist generate).

The Package.swift for declaring external dependencies already gets picked up, yes.

Right, I think it should be performant enough, just something to keep in mind.