Repository navigation
Description
Summary
Two related gaps in how xcode handles C and C++ sources, both on master at 354a163, which includes #199:
addSourceFiledoesn't know C or C++.FILETYPE_BY_EXTENSIONinlib/pbxFile.jshas noc,cpp,cc,cxxorhpp, and neitherGROUP_BY_FILETYPEnorENCODING_BY_FILETYPEknowssourcecode.c.corsourcecode.cpp.cpp. SoaddSourceFile('Plain.cpp', {}, group)writes the file reference withlastKnownFileType = unknownandfileEncoding = undefined, and comments its build filePlain.cpp in Resources, though it's added to the Sources build phase. This is whataddSourceFilerecords a.mmfile as typeunknown, in a build file named for Resources #198 reported for.mm, which feat: support objcpp source files ('mm' extensions) #199 fixed by mappingmmalone.removeSourceFilefinds the build phase entry by its comment. It deletes thePBXBuildFilewhosefileRef_commentis the file's name, but then looks in the Sources build phase for an entry whose comment islongComment(file),"<name> in <group>", with the group it computes for the file's type. For a C++ file that'sin Resources, so thePlain.cpp in Sourcesentry Xcode writes is left in the build phase, pointing at a build file that no longer exists. The same holds for any entry whose comment differs from the one the running version computes, such as a.mmadded before feat: support objcpp source files ('mm' extensions) #199 (commentedin Resources) and removed after it.
Steps to reproduce
- Download the gist https://gist.github.com/YevheniiKotyrlo/d95ec782d5b4a76269de87e5da337370 into an empty folder:
package.json, which depends onxcodefrom this repository at 354a163, andrepro.js. - Add the repository's test project beside them and install:
curl -sSfLO https://raw.githubusercontent.com/apache/cordova-node-xcode/354a1631d0a448531e4cc0aee8ca60c89262d1f1/test/parser/projects/full.pbxproj npm install
- Run
node repro.js full.pbxproj. It addsPlain.m,Plain.c,Plain.cpp,Plain.ccandPlain.cxxto theClassesgroup withaddSourceFileand prints the build file and file reference written for each. Then it rewrites each build file'sin Resourcescomment toin Sources, as Xcode comments a source in a target, removesAppDelegate.m(already in the project) andPlain.cppwithremoveSourceFile, and prints every build phase line still naming them.
Expected: Plain.c typed sourcecode.c.c, and Plain.cpp, Plain.cc and Plain.cxx typed sourcecode.cpp.cpp, each with fileEncoding = 4 and a build file in Sources, as Plain.m gets for .m. After the removal, nothing names Plain.cpp, as nothing names AppDelegate.m.
Actual: each C and C++ file is typed unknown with fileEncoding = undefined and a build file in Resources; after the removal, the Sources build phase keeps Plain.cpp in Sources, whose uuid is defined nowhere.
== 1. addSourceFile writes
C5FE06FC9FBA46658ACB3C78 /* Plain.m in Sources */ = {isa = PBXBuildFile; fileRef = 363B13F5832F4D92B1100D02 /* Plain.m */; };
363B13F5832F4D92B1100D02 /* Plain.m */ = {isa = PBXFileReference; name = "Plain.m"; path = "Plain.m"; sourceTree = "<group>"; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; explicitFileType = undefined; includeInIndex = 0; };
8F2E3FE4C1B74D0DB21B86C6 /* Plain.c in Resources */ = {isa = PBXBuildFile; fileRef = 7674D51FDAED4371A44A44A4 /* Plain.c */; };
7674D51FDAED4371A44A44A4 /* Plain.c */ = {isa = PBXFileReference; name = "Plain.c"; path = "Plain.c"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; };
CDBFE0E0BE964F059024A767 /* Plain.cpp in Resources */ = {isa = PBXBuildFile; fileRef = 9282A2464EEC4DCBBED1CB85 /* Plain.cpp */; };
9282A2464EEC4DCBBED1CB85 /* Plain.cpp */ = {isa = PBXFileReference; name = "Plain.cpp"; path = "Plain.cpp"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; };
73966221F0E449798EDDEF79 /* Plain.cc in Resources */ = {isa = PBXBuildFile; fileRef = 5A2F8BC90A9545B898F7F583 /* Plain.cc */; };
5A2F8BC90A9545B898F7F583 /* Plain.cc */ = {isa = PBXFileReference; name = "Plain.cc"; path = "Plain.cc"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; };
CACF95CFB35240C68D7F38E1 /* Plain.cxx in Resources */ = {isa = PBXBuildFile; fileRef = 53D0098FE6BF4A959BB42BE0 /* Plain.cxx */; };
53D0098FE6BF4A959BB42BE0 /* Plain.cxx */ = {isa = PBXFileReference; name = "Plain.cxx"; path = "Plain.cxx"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = unknown; explicitFileType = undefined; includeInIndex = 0; };
== 2. removeSourceFile, with each build file commented "in Sources" as Xcode writes it
AppDelegate.m: nothing names it
Plain.cpp: CDBFE0E0BE964F059024A767 /* Plain.cpp in Sources */, <- CDBFE0E0BE964F059024A767 is defined nowhere
The uuids are random, so they differ from run to run. Node 26.12.1 on Windows 11; xcode from master at 354a163. Whether Xcode then compiles these files by their extension, as it does an untyped .mm, I haven't checked.
Reading
lib/pbxFile.js at 354a163: detectType returns DEFAULT_FILETYPE ('unknown') for an extension FILETYPE_BY_EXTENSION lacks, detectGroup returns DEFAULT_GROUP ('Resources') for a file type GROUP_BY_FILETYPE lacks, and defaultEncoding returns nothing for a file type ENCODING_BY_FILETYPE lacks.
lib/pbxProject.js at 354a163: removeSourceFile calls removeFromPbxBuildFileSection, which deletes every PBXBuildFile whose fileRef_comment is the file's basename and records its uuid on file.uuid, then removeFromPbxSourcesBuildPhase, which splices the first entry whose comment === longComment(file). Matching the entry by the uuid removeFromPbxBuildFileSection just found would remove it whatever its comment says. removeFromPbxResourcesBuildPhase and removeFromPbxFrameworksBuildPhase match by comment the same way.
Activity
erisu commentedon Nov 2, 2026
Hey @YevheniiKotyrlo
I submitted a PR to add the following extensions using the same approach as the previous PR.
One thing I wanted to mention is that the
hppfiles are added using theaddHeaderFileAPI. This also adds them toResource, which matches how thehextension files are handled.Could you take a look and confirm?
YevheniiKotyrlo commentedon Nov 2, 2026
Thanks! #201 fixes the
addSourceFilehalf of #200 for me; theremoveSourceFilehalf is still there.With the reproduction from the issue against #201 at 2b1d000,
Plain.cgetslastKnownFileType = sourcecode.c.candPlain.cpp,Plain.ccandPlain.cxxgetsourcecode.cpp.cpp, each withfileEncoding = 4and a build filein Sources. ItsremoveSourceFilestep passes too, but only because the commentremoveSourceFilelooks for is now the one Xcode writes: it still finds the Sources entry by comment, so a.cppadded by 4.0.0 (commentedin Resources) and removed with #201 leaves that entry in the Sources build phase, pointing at a build file that no longer exists.Resolves #200would close that along with the rest.On
hpp: yes,addHeaderFilegives a.hppexactly what it gives a.h, a file reference withfileEncoding = 4; lastKnownFileType = sourcecode.cpp.hin the group and no build file, soResourcesnever reaches the project. The new case intest/addHeaderFile.jscallsaddSourceFile('file.hpp')rather thanaddHeaderFile, though, so it also puts afile.hpp in Resourcesbuild file in the Sources build phase.erisu commentedon Nov 4, 2026
I would probably suggest splitting this ticket into two separate tickets to make it easier to track each item independently.
Ticket 1: Supporting new file extensions
I believe this task has been completed by the PR I submitted earlier and would be considered a feature request.
I also updated the PR test case to correctly use
addHeaderFile, as you pointed out. Thanks for bringing that to my attention!Ticket 2:
removeSourceFile...I might need more context around this so I can try to better understand the issue.
To me, it sounds like you're reporting a bug within
removeSourceFile, correct?If it is a bug, I think it would be helpful to track it separately from the feature request.
Looking at the sample gist code you provided, I noticed one thing that might be contributing to the behavior you're reporting.
Based on the comment:
I am assuming you're using the
full.pbxprojfile for testing.On line 23 of your gist code, you're trying to locate the
PBXGroupofClasses:The project file does not have a
PBXGroupwith a propertynamewith the value ofClasses, but it does have one with the propertypathwith the value ofClasses:https://github.com/apache/cordova-node-xcode/blob/master/test/parser/projects/full.pbxproj#L127-L135
Since the lookup criteria is using
namerather thanpath, it would returnundefined. Updating the criteria to usepathshould allow it to retrieve the correct ID:I am wondering if this is causing the behavior you're observing.
Additionally, if your testing against the feature request PR, I don't think the test script needs the replace all "in Resources" to "in Sources" section anymore.
I ran a test with the changed criteria and noticed that the 'AppDelegate.m' and 'Plain.cpp' entries no longer exist in the final output, as expected.
Hopefully, updating the
findPBXGroupKeycriteria is the solution for what you're trying to do.xavierserrai commentedon Nov 4, 2026
Dears,
The same happens when adding a xcframework using the
<framework src="framework.xcframework" custom="true" embed="true" />tag in config.xml from Apache cordova ios.
That tag, produces:
AF4CFEAF2B3542F2807AB2F5 /* framework.xcframework */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = undefined; includeInIndex = 0; lastKnownFileType = unknown; name = framework.xcframework; path = "path/to/framework.xcframework"; sourceTree = "<group>"; };Setting
lastKnownFileType = unknownwhen it should bewrapper.xcframeworkSeems that in pbxFile.js, the FILETYPE_BY_EXTENSION lacks the xcframework extension.
Also, in pbxProject.js, there are no references to xcframework but to plain framework.
Thanks,
YevheniiKotyrlo commentedon Nov 4, 2026
Yes, it's a bug in
removeSourceFile, and the lookup isn't what causes it. You're right thatfindPBXGroupKey({ name: 'Classes' })finds nothing infull.pbxproj, whoseClassesgroup has only apath, so the gist passed no group and the files went intoPlugins; I've fixed the gist to look it up bypath. ButremoveFromPbxSourcesBuildPhasefinds the entry bylongComment(file), and thein Sourcesorin Resourcesin that comment comes from the file's type, not from the group it's added to: with the fixed lookup on master at 354a163,Plain.cpp in Sourcesstill stays in the Sources build phase, pointing at a build file that's gone. With #201 the gist's removal passes because the comment it computes becomes the one in the project.I've split it out as #202, reproduced through the calls cordova-ios makes for a plugin's
<source-file>, which pass no group either, and without rewriting any comment: a.mmadded by 4.0.0 and removed by master leaves itsPlain.mm in Resourcesentry behind. That leaves #200, retitled to match, as the extension half that #201 resolves. Thanks for switching the.hppcase toaddHeaderFile.[-]`addSourceFile` types `.c`, `.cpp`, `.cc` and `.cxx` files as `unknown`, in build files named for Resources, and `removeSourceFile` misses the Sources entry Xcode writes for them[/-][+]`addSourceFile` types `.c`, `.cpp`, `.cc` and `.cxx` files as `unknown`, in build files named for Resources[/+]