tomlokhorst / XcodeEdit

Swift library for reading and writing Xcode project files in OpenStep format
MIT License
206 stars 38 forks source link

How to Programmatically Change SPM Dependencies in Xcode Project? #47

Closed mackoj closed 4 months ago

mackoj commented 4 months ago

Hi @tomlokhorst, thanks for working on XcodeEdit.

I have a question and I hope you can help me. I am working on a piece of code with the goal of programmatically changing SPM dependencies. Specifically, I want to force a certain version of an SPM package in a project.

For example, app A is using dependency B, and I want to force a particular version of B because I am the maintainer of B. I want to test if the new version of B breaks app A.

Can you give me some pointers on how to do this properly? Should I add a requirement to the XCRemoteSwiftPackageReference object?

In the code snippet below, I am trying to modify the pbxproj file as expected.

I would be glad to contribute code to your project once it is working properly.

Thanks,

  func transformPbxproj(_ package: String, _ url: URL, _ version: PackageVersion) async throws {
    do {
      var folderXcodeProjectFile = url.deletingLastPathComponent()
      let proj = try XCProjectFile(xcodeprojURL: folderXcodeProjectFile)
      var output = proj
      for obj in proj.allObjects.objects.values {
        if let dep = obj as? XCRemoteSwiftPackageReference {
          if let repo = dep.repositoryURL, repo.path.contains(package) == false { continue }
          switch version {
// How to properly represent requirement ?
/*
        3758DBD6234CB17D0046933B /* XCRemoteSwiftPackageReference "RxSwift" */ = {
            isa = XCRemoteSwiftPackageReference;
            repositoryURL = "https://github.com/ReactiveX/RxSwift.git";
            requirement = {
                kind = exactVersion;
                version = 6.6.0;
            };
        };
*/
            case let .tag(value):
              let fields = Fields(dictionaryLiteral: ("kind", "exactVersion"), ("version", value))
              let pkgRef = try XCRemoteSwiftPackageReference(id: dep.id, fields: fields, allObjects: dep.allObjects)
              output.allObjects.objects[dep.id] = pkgRef
            case let .branch(value):
              let fields = Fields(dictionaryLiteral: ("kind", "branch"), ("branch", value))
              let pkgRef = try XCRemoteSwiftPackageReference(id: dep.id, fields: fields, allObjects: dep.allObjects)
              output.allObjects.objects[dep.id] = pkgRef
            case let .commit(value):
              let fields = Fields(dictionaryLiteral: ("kind", "revision"), ("revision", value))
              let pkgRef = try XCRemoteSwiftPackageReference(id: dep.id, fields: fields, allObjects: dep.allObjects)
              output.allObjects.objects[dep.id] = pkgRef
            case let .path(value, name):
              let fields = Fields(dictionaryLiteral: ("relativePath", value))
              let pkgRef = try XCLocalSwiftPackageReference(id: dep.id, fields: fields, allObjects: dep.allObjects)
              output.allObjects.objects[dep.id] = pkgRef
          }
        }
      }

      // Write out a new pbxproj file
      try output.write(to: folderXcodeProjectFile, format: PropertyListSerialization.PropertyListFormat.openStep)
    } catch {
      print("Error with file to open \(url)")
    }
  }
mackoj commented 4 months ago

I seems to have found a way to make it work.