How to attach resources/images from two different folders and omit duplicates in target?

Hello,

I try to migrate old big project (Xcode owned + cocoapods + objective-c + swift + 200targets) to tuist.

and I have faced with a problem.
We have general folder with images “Default Images” and
each target has own special folder with images “Stations/A-App/Images”.

Images(which are present) from folder “Stations/A-App/Images” has to overwrite images from “Default Images”

so when I use next:

let A-AppTarget: ProjectDescription.Target = .target(
name: “A-App-iOS”,
destinations: .iOS,
product: .app,
productName: “My-A-App”,
bundleId: “io.tuist.targetX”,
infoPlist: .file(path: “MediaApps-iOS/App/Resources/Stations/A-App/Info.plist”),
sources: [“MediaApps-iOS/App/Sources/"],
resources: [
"MediaApps-iOS/App/Resources/Sounds/
”,
“MediaApps-iOS/App/Resources/Default Images/",
"MediaApps-iOS/App/Resources/Stations/A-App/

]
)

let B-AppTarget: ProjectDescription.Target = .target(
name: “B-App-iOS”,
destinations: .iOS,
product: .app,
productName: “My-B-App”,
bundleId: “io.tuist.targetX”,
infoPlist: .file(path: “MediaApps-iOS/App/Resources/Stations/B-App/Info.plist”),
sources: [“MediaApps-iOS/App/Sources/"],
resources: [
"MediaApps-iOS/App/Resources/Sounds/
”,
“MediaApps-iOS/App/Resources/Default Images/",
"MediaApps-iOS/App/Resources/Stations/B-App/

]
)

Xcode shows me errors like:

Multiple commands produce ‘/Users/user/Library/Developer/Xcode/DerivedData/MediaApps-Tuist-ameygklnbamyzsbkozvlmmtavafx/Build/Products/Debug-iphonesimulator/My-A-App.app/[email protected]

Questions:

  1. How can I merge files from two folders into one Target?
  2. how to provide priority of use file in generating .xcodeproj?

Hey @Vitalik :wave:

How can I merge files from two folders into one Target?

There are multiple ways how to solve this:

  • Create a common module to be shared across A-App and B-App. You likely already have a module that you use to share code across the two apps.
  • Create a resource bundle to share the resources across two targets.
  • Use Environment variables to configure the targets on the fly.

It depends on your specific needs, but if you are doing white-labeling, Environment variables would be my recommendation. With environment variables, you can specify what app to create at generation time:

TUIST_APP="B-App" tuist generate

Then in your manifests, you can modify the target based on the environment variable’s value:

let project = Project(
  name: Environment.app.getString(default: "A-App"),
  ...
)

Since Project.swift manifests are Swift files, you can get more elaborate as well:

// This could also be in ProjectDescriptionHelpers
enum App: String {
  case aApp, bApp
  
  init?(rawValue: String) {
     switch rawValue {
       case "A-App": .aApp
       case "B-App": .bApp
       default: .aApp
     }
   }
}

func project(app: App) -> Project {
  let name: String
  let settings: Settings

  switch app { ... }

  return Project(name: name, bundleId: "com.your-org.\(app.rawValue)" settings: settings)
}

// Project.swift
let project = .project(
  app: App(
     rawValue: Environment.app.getString(default: "A-App")
  )
)

Hopefully, this gives you a better idea how to resolve your problem :slightly_smiling_face:

how to provide priority of use file in generating .xcodeproj?

Not sure what exactly you mean but with the response above, you shouldn’t need any sort of “priority”

Hi Marek,

Thanks for your recommendations.

However, the proposed solutions don’t fully address the project’s specific challenges. Here are some clarifications and details on the issues:

  1. “Create a common module …”

We already have certain files under a shared “common module,” but a significant amount of other code still resides outside this module. While we plan to increase modularization using Tuist, this is intended for a future phase. Right now, the main goal is to transition to Tuist without altering too much of the existing code.

  1. “resource bundle to share the resources across two targets”

Creating a “resource bundle” to share resources across two targets isn’t feasible at this stage. It would require updating every location in the code where files are referenced, which is a large-scale change we want to avoid during the Tuist migration.

Our priority is to transition the current project to Tuist while avoiding any changes to the logic of how files are referenced or used. This limits our ability to restructure the way files are shared across targets.

Current Approach and Request for Priority Handling
We are trying to maintain a structure where base list of images are stored in a shared folder “Default Images”:
“MediaApps-iOS/App/Resources/Default Images/”

Each target also has its own folder for app-specific resources, for example:
“MediaApps-iOS/App/Resources/Stations/A-App/”

The goal is to ensure that when files with the same name exist in both folders, the Tuist-generated .xcodeproj prioritizes files from the app-specific folder (like A-App/) over the shared Default Images/. Ideally, I would like Tuist to recognize this situation and provide a mechanism to resolve it according to a set of filters or rules.

Possible Solutions I’m Exploring

  1. Resource Priority Handling
  • I’m considering using .glob() rules to exclude files in “Default Images” if a file with the same name exists in the app-specific folder.
    However, I’ve run into a problem here since Tuist does not provide functionality to access the file system or read the list of files in a folder . Without access to the file system, it’s difficult to programmatically define the exclusion rules in the manifest.

  • Another idea is to implement a filter or custom logic in the Tuist manifest, so when identical file names are detected, files from the specific app folder (like A-App/) take precedence.

    • The challenge here is that ResourceFileElement only provides ProjectDescription.PlatformCondition, and I couldn’t find a way to introduce a custom filter or comparator to prioritize files dynamically.
  1. Detection of Name Collisions
  • If Tuist could log or warn us when file name conflicts occur, that would be helpful for tracking issues during the migration process.

Questions:

  1. Do you have any recommendations for how to prioritize specific files when generating the .xcodeproj using Tuist?
  2. Also, do you know of any approaches to gain access to the file system or dynamically list files in a directory during Tuist’s manifest execution?

Yes, I agree we should detect this scenario and output a warning. If you could create a reproducible sample and create an issue, that’d be amazing. A contribution would be more than welcome.

No, and I would recommend moving away from a concept of “prioritization”. I’d suggest defining the project and the directory structure in such a way that conflicts don’t happen. For files that must be duplicated across two targets, I’d recommend putting them in a clearly defined directory that you can exclude based on environment variables as I suggested initially.

You can use FileManager as Project.swift is executed as regular Swift code. However, I’d recommend against doing that as that makes tuist generate non-deterministic. We cache Project.swift files and doing side effects like reading the file system breaks that caching.