Code Monkey home page Code Monkey logo

Comments (11)

stevelandeyasana avatar stevelandeyasana commented on June 25, 2024

Bumping this, I'd love to fix this on my own but I really don't understand what's going on.

from xcodeproj.

fortmarek avatar fortmarek commented on June 25, 2024

Hey @stevelandeyasana 👋

I can try looking into this but would it be possible if you first wrote a unit / integration test for this in the codebase? That would be super helpful 🙏

from xcodeproj.

stevelandeyasana avatar stevelandeyasana commented on June 25, 2024

I'll probably be able to carve out some time this week to try to break it in a test.

from xcodeproj.

stevelandeyasana avatar stevelandeyasana commented on June 25, 2024

Well, this is spooky: I wrote a test that should do exactly what I'm doing in my larger codebase, but it works. I added this to PBXProjIntegrationTests.swift:

    func test_uuids_after_adding_to_build_phase() throws {
        let fixturePath = self.fixturePath().parent().parent()
        let xcp = try XcodeProj(path: self.fixturePath().parent())
        let project = xcp.pbxproj.projects.first(where: { $0.name == "Project" })!
        let iosGroup = project.mainGroup.children.first(where: { $0.path == "iOS" }) as! PBXGroup
        let appTarget = xcp.pbxproj.nativeTargets.first(where: { $0.name == "iOS" })!
        let sourcesPhase = appTarget.buildPhases.first(where: { $0.buildPhase == .sources }) as! PBXSourcesBuildPhase

        let newFilePath = Path(components: fixturePath.components + ["iOS", "FileNotInProject.swift"])

        let file = try iosGroup.addFile(at: newFilePath, sourceRoot: fixturePath)
        let buildFile = PBXBuildFile(file: file, product: nil, settings: [:])
        sourcesPhase.files?.append(buildFile)

        let encoder = PBXProjEncoder(outputSettings: PBXOutputSettings())
        let output: String = try encoder.encode(proj: xcp.pbxproj)
        XCTAssert(!output.contains("TEMP"))
    }

It passed. :-( I'll keep poking at it.

Differences I can think of vs my own project file:

  • More build targets
  • More build phases
  • I'm dynamically iterating over files in a directory instead of using a static path

from xcodeproj.

stevelandeyasana avatar stevelandeyasana commented on June 25, 2024

I tried attaching a debugger to see if I could see where ReferenceGenerator skips over my new files. I found that when it's iterating over the build file references, it's skipping at least one because it can't find the PBXBuildFile that goes with a PBXObjectReference. I'm still trying to work out what that means. These conditions do happen when working with my main project, and not with the integration test I wrote above.

    private func generateBuildPhaseReferences(_ buildPhase: PBXBuildPhase,
                                              identifiers: [String]) throws {
        var identifiers = identifiers
        if let name = buildPhase.name() {
            identifiers.append(name)
        }

        // Build phase
        fixReference(for: buildPhase, identifiers: identifiers)

        // Build files
        buildPhase.fileReferences?.forEach { buildFileReference in
            if !buildFileReference.temporary { return }

            // THIS LINE: buildFileReference.getObject() returns nil
            guard let buildFile: PBXBuildFile = buildFileReference.getObject() else { return }

from xcodeproj.

stevelandeyasana avatar stevelandeyasana commented on June 25, 2024

OK, this looks like a spooky memory management thing. If I make an [Any] array outside my loop, and append the PBXBuildFiles I create to that array, then it works. My example integration test doesn't have a loop, so it doesn't have the bug. When I add the loop to the integration test, it fails. I'll open a PR with the failing test, and fix my code using this workaround.

from xcodeproj.

stevelandeyasana avatar stevelandeyasana commented on June 25, 2024

Clearer explanation of what I learned: XcodeProj does not keep any strong internal references to PBXBuildFile, so unless the caller keeps its own references, newly created PBXBuildFile objects added to a build phase may be come nil before the project file is written.

I think this is really surprising behavior, but it does have a straightforward workaround.

from xcodeproj.

fortmarek avatar fortmarek commented on June 25, 2024

XcodeProj does not keep any strong internal references to PBXBuildFile, so unless the caller keeps its own references, newly created PBXBuildFile objects added to a build phase may be come nil before the project file is written.

Ouch, that's a pretty good find!

It does seem to be the case:

  • PBXObjectReference keeps a weak pointer to its objects (PBXBuildFile will be one of those)
  • PBXBuildPhase does not store PBXBuildFiles directly - instead uses the getter in PBXObjectReference

We'll need to keep a strong pointer somewhere.

I think we can rewrite:

    /// References to build files.
    var fileReferences: [PBXObjectReference]?

    /// Build files.
    public var files: [PBXBuildFile]? {
        get {
            fileReferences?.objects()
        }
        set {
            newValue?.forEach { $0.buildPhase = self }
            fileReferences = newValue?.references()
        }
    }

to:

    /// References to build files.
    var fileReferences: [PBXObjectReference]? {
          files?.references()
    }

    /// Build files.
    public var files: [PBXBuildFile]? {
        didSet {
            files?.forEach { $0.buildPhase = self }
        }
    }

This change will strongly hold the PBXBuildFiles and subsequently also the PBXObjectReferences since the former holds strongly the latter but not the other way around.

Would such a change make sense @stevelandeyasana? If so, let me know if you wanted to implement this yourself or I can go ahead. Either way, it would be great if you tested it in your project where you have first seen the issue.

from xcodeproj.

stevelandeyasana avatar stevelandeyasana commented on June 25, 2024

Unfortunately that isn't quite sufficient. The initializers directly assign to fileReferences, which you changed from a get/set to just a get property, and I'm not sure what's supposed to happen with the Decodable initializer. But otherwise that looks like a good solution.

from xcodeproj.

github-actions avatar github-actions commented on June 25, 2024

Hola 👋,

We want to inform you that the issue has been marked as stale. This means that there hasn't been any activity or updates on it for quite some time, and it's possible that it may no longer be relevant or actionable.
If you still believe that this issue is valid and requires attention, please provide an update or any additional information that can help us address it. Otherwise, we may consider closing it in the near future.
Thank you for your understanding.

from xcodeproj.

github-actions avatar github-actions commented on June 25, 2024

Hola 👋,

We want to inform you that we have decided to close this stale issue as there hasn't been any activity or response regarding it after marking it as stale.

We understand that circumstances may have changed or priorities may have shifted, and that's completely understandable. If you still believe that this issue needs to be addressed, please feel free to reopen it and provide any necessary updates or additional information.

We appreciate your understanding and look forward to your continued contributions to the project.

Thank you.

from xcodeproj.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.