using System; using System.Collections.Generic; using System.IO; using System.Text; using ImaginationOverflow.UniversalDeepLinking.Editor.Xcode.PBX; namespace ImaginationOverflow.UniversalDeepLinking.Editor.Xcode { using PBXBuildFileSection = KnownSectionBase; using PBXFileReferenceSection = KnownSectionBase; using PBXGroupSection = KnownSectionBase; using PBXContainerItemProxySection = KnownSectionBase; using PBXReferenceProxySection = KnownSectionBase; using PBXSourcesBuildPhaseSection = KnownSectionBase; using PBXFrameworksBuildPhaseSection= KnownSectionBase; using PBXResourcesBuildPhaseSection = KnownSectionBase; using PBXCopyFilesBuildPhaseSection = KnownSectionBase; using PBXShellScriptBuildPhaseSection = KnownSectionBase; using PBXVariantGroupSection = KnownSectionBase; using PBXNativeTargetSection = KnownSectionBase; using PBXTargetDependencySection = KnownSectionBase; using XCBuildConfigurationSection = KnownSectionBase; using XCConfigurationListSection = KnownSectionBase; using UnknownSection = KnownSectionBase; internal class PBXProjectData { private Dictionary m_Section = null; private PBXElementDict m_RootElements = null; private PBXElementDict m_UnknownObjects = null; private string m_ObjectVersion = null; private List m_SectionOrder = null; private Dictionary m_UnknownSections; private PBXBuildFileSection buildFiles = null; // use BuildFiles* methods instead of manipulating directly private PBXFileReferenceSection fileRefs = null; // use FileRefs* methods instead of manipulating directly private PBXGroupSection groups = null; // use Groups* methods instead of manipulating directly public PBXContainerItemProxySection containerItems = null; public PBXReferenceProxySection references = null; public PBXSourcesBuildPhaseSection sources = null; public PBXFrameworksBuildPhaseSection frameworks = null; public PBXResourcesBuildPhaseSection resources = null; public PBXCopyFilesBuildPhaseSection copyFiles = null; public PBXShellScriptBuildPhaseSection shellScripts = null; public PBXNativeTargetSection nativeTargets = null; public PBXTargetDependencySection targetDependencies = null; public PBXVariantGroupSection variantGroups = null; public XCBuildConfigurationSection buildConfigs = null; public XCConfigurationListSection buildConfigLists = null; public PBXProjectSection project = null; // FIXME: create a separate PBXObject tree to represent these relationships // A build file can be represented only once in all *BuildPhaseSection sections, thus // we can simplify the cache by not caring about the file extension private Dictionary> m_FileGuidToBuildFileMap = null; private Dictionary m_ProjectPathToFileRefMap = null; private Dictionary m_FileRefGuidToProjectPathMap = null; private Dictionary> m_RealPathToFileRefMap = null; private Dictionary m_ProjectPathToGroupMap = null; private Dictionary m_ProjectPathToVariantGroupMap = null; private Dictionary m_GroupGuidToProjectPathMap = null; private Dictionary m_VariantGroupGuidToProjectPathMap = null; private Dictionary m_GuidToParentGroupMap = null; private Dictionary m_GuidToParentVariantGroupMap = null; public PBXBuildFileData BuildFilesGet(string guid) { return buildFiles[guid]; } // targetGuid is the guid of the target that contains the section that contains the buildFile public void BuildFilesAdd(string targetGuid, PBXBuildFileData buildFile) { if (!m_FileGuidToBuildFileMap.ContainsKey(targetGuid)) m_FileGuidToBuildFileMap[targetGuid] = new Dictionary(); m_FileGuidToBuildFileMap[targetGuid][buildFile.fileRef] = buildFile; buildFiles.AddEntry(buildFile); } public void BuildFilesRemove(string targetGuid, string fileGuid) { var buildFile = BuildFilesGetForSourceFile(targetGuid, fileGuid); if (buildFile != null) { m_FileGuidToBuildFileMap[targetGuid].Remove(buildFile.fileRef); buildFiles.RemoveEntry(buildFile.guid); } } public PBXBuildFileData BuildFilesGetForSourceFile(string targetGuid, string fileGuid) { if (!m_FileGuidToBuildFileMap.ContainsKey(targetGuid)) return null; if (!m_FileGuidToBuildFileMap[targetGuid].ContainsKey(fileGuid)) return null; return m_FileGuidToBuildFileMap[targetGuid][fileGuid]; } public IEnumerable BuildFilesGetAll() { return buildFiles.GetObjects(); } public void FileRefsAdd(string realPath, string projectPath, PBXGroupData parent, PBXFileReferenceData fileRef) { fileRefs.AddEntry(fileRef); m_ProjectPathToFileRefMap.Add(projectPath, fileRef); m_FileRefGuidToProjectPathMap.Add(fileRef.guid, projectPath); if (!m_RealPathToFileRefMap.ContainsKey(fileRef.tree)) { m_RealPathToFileRefMap[fileRef.tree] = new Dictionary(); } m_RealPathToFileRefMap[fileRef.tree].Add(realPath, fileRef); m_GuidToParentGroupMap.Add(fileRef.guid, parent); } public void FileRefsAdd(string realPath, string projectPath, PBXVariantGroupData parent, PBXFileReferenceData fileRef) { fileRefs.AddEntry(fileRef); m_ProjectPathToFileRefMap.Add(projectPath, fileRef); m_FileRefGuidToProjectPathMap.Add(fileRef.guid, projectPath); if (!m_RealPathToFileRefMap.ContainsKey(fileRef.tree)) { m_RealPathToFileRefMap[fileRef.tree] = new Dictionary(); } m_RealPathToFileRefMap[fileRef.tree].Add(realPath, fileRef); m_GuidToParentVariantGroupMap.Add(fileRef.guid, parent); } public PBXFileReferenceData FileRefsGet(string guid) { return fileRefs[guid]; } public PBXFileReferenceData FileRefsGetByRealPath(string path, PBXSourceTree sourceTree) { if (m_RealPathToFileRefMap[sourceTree].ContainsKey(path)) return m_RealPathToFileRefMap[sourceTree][path]; return null; } public PBXFileReferenceData FileRefsGetByProjectPath(string path) { if (m_ProjectPathToFileRefMap.ContainsKey(path)) return m_ProjectPathToFileRefMap[path]; return null; } public void FileRefsRemove(string guid) { PBXFileReferenceData fileRef = fileRefs[guid]; fileRefs.RemoveEntry(guid); m_ProjectPathToFileRefMap.Remove(m_FileRefGuidToProjectPathMap[guid]); m_FileRefGuidToProjectPathMap.Remove(guid); foreach (var tree in FileTypeUtils.AllAbsoluteSourceTrees()) m_RealPathToFileRefMap[tree].Remove(fileRef.path); m_GuidToParentGroupMap.Remove(guid); m_GuidToParentVariantGroupMap.Remove(guid); } public PBXGroupData GroupsGet(string guid) { return groups[guid]; } public PBXGroupData GroupsGetByChild(string childGuid) { return m_GuidToParentGroupMap[childGuid]; } public PBXGroupData GroupsGetMainGroup() { return groups[project.project.mainGroup]; } /// Returns the source group identified by sourceGroup. If sourceGroup is empty or null, /// root group is returned. If no group is found, null is returned. public PBXGroupData GroupsGetByProjectPath(string sourceGroup) { if (m_ProjectPathToGroupMap.ContainsKey(sourceGroup)) return m_ProjectPathToGroupMap[sourceGroup]; return null; } public void GroupsAdd(string projectPath, PBXGroupData parent, PBXGroupData gr) { m_ProjectPathToGroupMap.Add(projectPath, gr); m_GroupGuidToProjectPathMap.Add(gr.guid, projectPath); m_GuidToParentGroupMap.Add(gr.guid, parent); groups.AddEntry(gr); } public void GroupsAddDuplicate(PBXGroupData gr) { groups.AddEntry(gr); } public void GroupsRemove(string guid) { m_ProjectPathToGroupMap.Remove(m_GroupGuidToProjectPathMap[guid]); m_GroupGuidToProjectPathMap.Remove(guid); m_GuidToParentGroupMap.Remove(guid); groups.RemoveEntry(guid); } public PBXVariantGroupData VariantGroupsGet(string guid) { return variantGroups[guid]; } public PBXVariantGroupData VariantGroupsGetByChild(string childGuid) { return m_GuidToParentVariantGroupMap[childGuid]; } /// Returns the source group identified by sourceGroup. If sourceGroup is empty or null, /// root group is returned. If no group is found, null is returned. public PBXVariantGroupData VariantGroupsGetByProjectPath(string sourceGroup) { if (m_ProjectPathToVariantGroupMap.ContainsKey(sourceGroup)) return m_ProjectPathToVariantGroupMap[sourceGroup]; return null; } public void VariantGroupsAdd(string projectPath, PBXGroupData parent, PBXVariantGroupData gr) { m_ProjectPathToVariantGroupMap.Add(projectPath, gr); m_VariantGroupGuidToProjectPathMap.Add(gr.guid, projectPath); m_GuidToParentGroupMap.Add(gr.guid, parent); variantGroups.AddEntry(gr); } public void VariantGroupsAddDuplicate(PBXVariantGroupData gr) { variantGroups.AddEntry(gr); } public void VariantGroupsRemove(string guid) { m_ProjectPathToVariantGroupMap.Remove(m_VariantGroupGuidToProjectPathMap[guid]); m_VariantGroupGuidToProjectPathMap.Remove(guid); m_GuidToParentGroupMap.Remove(guid); variantGroups.RemoveEntry(guid); } public FileGUIDListBase BuildSectionAny(PBXNativeTargetData target, string path, bool isFolderRef) { string ext = Path.GetExtension(path); var phase = FileTypeUtils.GetFileType(ext, isFolderRef); switch (phase) { case PBXFileType.Framework: foreach (var guid in target.phases) if (frameworks.HasEntry(guid)) return frameworks[guid]; break; case PBXFileType.Resource: foreach (var guid in target.phases) if (resources.HasEntry(guid)) return resources[guid]; break; case PBXFileType.Source: foreach (var guid in target.phases) if (sources.HasEntry(guid)) return sources[guid]; break; case PBXFileType.CopyFile: foreach (var guid in target.phases) if (copyFiles.HasEntry(guid)) return copyFiles[guid]; break; } return null; } public FileGUIDListBase BuildSectionAny(string sectionGuid) { if (frameworks.HasEntry(sectionGuid)) return frameworks[sectionGuid]; if (resources.HasEntry(sectionGuid)) return resources[sectionGuid]; if (sources.HasEntry(sectionGuid)) return sources[sectionGuid]; if (copyFiles.HasEntry(sectionGuid)) return copyFiles[sectionGuid]; throw new Exception(String.Format("The given GUID {0} does not refer to a known build section", sectionGuid)); } void RefreshBuildFilesMapForBuildFileGuidList(Dictionary mapForTarget, FileGUIDListBase list) { foreach (string guid in list.files) { var buildFile = buildFiles[guid]; mapForTarget[buildFile.fileRef] = buildFile; } } void RefreshMapsForGroupChildren(string projectPath, string realPath, PBXSourceTree realPathTree, PBXGroupData parent) { var children = new List(parent.children); foreach (string guid in children) { PBXFileReferenceData fileRef = fileRefs[guid]; string pPath; string rPath; PBXSourceTree rTree; if (fileRef != null) { pPath = PBXPath.Combine(projectPath, fileRef.name); PBXPath.Combine(realPath, realPathTree, fileRef.path, fileRef.tree, out rPath, out rTree); if (!m_ProjectPathToFileRefMap.ContainsKey(pPath)) { m_ProjectPathToFileRefMap.Add(pPath, fileRef); } if (!m_FileRefGuidToProjectPathMap.ContainsKey(fileRef.guid)) { m_FileRefGuidToProjectPathMap.Add(fileRef.guid, pPath); } if (!m_RealPathToFileRefMap[rTree].ContainsKey(rPath)) { m_RealPathToFileRefMap[rTree].Add(rPath, fileRef); } if (!m_GuidToParentGroupMap.ContainsKey(guid)) { m_GuidToParentGroupMap.Add(guid, parent); } continue; } PBXGroupData gr = groups[guid]; if (gr != null) { pPath = PBXPath.Combine(projectPath, gr.name); PBXPath.Combine(realPath, realPathTree, gr.path, gr.tree, out rPath, out rTree); if (!m_ProjectPathToGroupMap.ContainsKey(pPath)) { m_ProjectPathToGroupMap.Add(pPath, gr); } if (!m_GroupGuidToProjectPathMap.ContainsKey(gr.guid)) { m_GroupGuidToProjectPathMap.Add(gr.guid, pPath); } if (!m_GuidToParentGroupMap.ContainsKey(guid)) { m_GuidToParentGroupMap.Add(guid, parent); } RefreshMapsForGroupChildren(pPath, rPath, rTree, gr); } } } void RefreshMapsForVariantGroupChildren(string projectPath, string realPath, PBXSourceTree realPathTree, PBXVariantGroupData parent) { var children = new List(parent.children); foreach (string guid in children) { PBXFileReferenceData fileRef = fileRefs[guid]; string pPath; string rPath; PBXSourceTree rTree; if (fileRef != null) { pPath = PBXPath.Combine(projectPath, fileRef.name); PBXPath.Combine(realPath, realPathTree, fileRef.path, fileRef.tree, out rPath, out rTree); if (!m_ProjectPathToFileRefMap.ContainsKey(pPath)) { m_ProjectPathToFileRefMap.Add(pPath, fileRef); } if (!m_FileRefGuidToProjectPathMap.ContainsKey(fileRef.guid)) { m_FileRefGuidToProjectPathMap.Add(fileRef.guid, pPath); } if (!m_RealPathToFileRefMap[rTree].ContainsKey(rPath)) { m_RealPathToFileRefMap[rTree].Add(rPath, fileRef); } if (!m_GuidToParentVariantGroupMap.ContainsKey(guid)) { m_GuidToParentVariantGroupMap.Add(guid, parent); } continue; } PBXVariantGroupData gr = variantGroups[guid]; if (gr != null) { pPath = PBXPath.Combine(projectPath, gr.name); PBXPath.Combine(realPath, realPathTree, gr.path, gr.tree, out rPath, out rTree); if (!m_ProjectPathToVariantGroupMap.ContainsKey(pPath)) { m_ProjectPathToVariantGroupMap.Add(pPath, gr); } if (!m_VariantGroupGuidToProjectPathMap.ContainsKey(gr.guid)) { m_VariantGroupGuidToProjectPathMap.Add(gr.guid, pPath); } if (!m_GuidToParentVariantGroupMap.ContainsKey(guid)) { m_GuidToParentVariantGroupMap.Add(guid, parent); } RefreshMapsForVariantGroupChildren(pPath, rPath, rTree, gr); } } } void RefreshAuxMaps() { foreach (var targetEntry in nativeTargets.GetEntries()) { var map = new Dictionary(); foreach (string phaseGuid in targetEntry.Value.phases) { if (frameworks.HasEntry(phaseGuid)) RefreshBuildFilesMapForBuildFileGuidList(map, frameworks[phaseGuid]); if (resources.HasEntry(phaseGuid)) RefreshBuildFilesMapForBuildFileGuidList(map, resources[phaseGuid]); if (sources.HasEntry(phaseGuid)) RefreshBuildFilesMapForBuildFileGuidList(map, sources[phaseGuid]); if (copyFiles.HasEntry(phaseGuid)) RefreshBuildFilesMapForBuildFileGuidList(map, copyFiles[phaseGuid]); } m_FileGuidToBuildFileMap[targetEntry.Key] = map; } RefreshMapsForGroupChildren("", "", PBXSourceTree.Source, GroupsGetMainGroup()); } public void Clear() { buildFiles = new PBXBuildFileSection("PBXBuildFile"); fileRefs = new PBXFileReferenceSection("PBXFileReference"); groups = new PBXGroupSection("PBXGroup"); containerItems = new PBXContainerItemProxySection("PBXContainerItemProxy"); references = new PBXReferenceProxySection("PBXReferenceProxy"); sources = new PBXSourcesBuildPhaseSection("PBXSourcesBuildPhase"); frameworks = new PBXFrameworksBuildPhaseSection("PBXFrameworksBuildPhase"); resources = new PBXResourcesBuildPhaseSection("PBXResourcesBuildPhase"); copyFiles = new PBXCopyFilesBuildPhaseSection("PBXCopyFilesBuildPhase"); shellScripts = new PBXShellScriptBuildPhaseSection("PBXShellScriptBuildPhase"); nativeTargets = new PBXNativeTargetSection("PBXNativeTarget"); targetDependencies = new PBXTargetDependencySection("PBXTargetDependency"); variantGroups = new PBXVariantGroupSection("PBXVariantGroup"); buildConfigs = new XCBuildConfigurationSection("XCBuildConfiguration"); buildConfigLists = new XCConfigurationListSection("XCConfigurationList"); project = new PBXProjectSection(); m_UnknownSections = new Dictionary(); m_Section = new Dictionary { { "PBXBuildFile", buildFiles }, { "PBXFileReference", fileRefs }, { "PBXGroup", groups }, { "PBXContainerItemProxy", containerItems }, { "PBXReferenceProxy", references }, { "PBXSourcesBuildPhase", sources }, { "PBXFrameworksBuildPhase", frameworks }, { "PBXResourcesBuildPhase", resources }, { "PBXCopyFilesBuildPhase", copyFiles }, { "PBXShellScriptBuildPhase", shellScripts }, { "PBXNativeTarget", nativeTargets }, { "PBXTargetDependency", targetDependencies }, { "PBXVariantGroup", variantGroups }, { "XCBuildConfiguration", buildConfigs }, { "XCConfigurationList", buildConfigLists }, { "PBXProject", project }, }; m_RootElements = new PBXElementDict(); m_UnknownObjects = new PBXElementDict(); m_ObjectVersion = null; m_SectionOrder = new List{ "PBXBuildFile", "PBXContainerItemProxy", "PBXCopyFilesBuildPhase", "PBXFileReference", "PBXFrameworksBuildPhase", "PBXGroup", "PBXNativeTarget", "PBXProject", "PBXReferenceProxy", "PBXResourcesBuildPhase", "PBXShellScriptBuildPhase", "PBXSourcesBuildPhase", "PBXTargetDependency", "PBXVariantGroup", "XCBuildConfiguration", "XCConfigurationList" }; m_FileGuidToBuildFileMap = new Dictionary>(); m_ProjectPathToFileRefMap = new Dictionary(); m_FileRefGuidToProjectPathMap = new Dictionary(); m_RealPathToFileRefMap = new Dictionary>(); foreach (var tree in FileTypeUtils.AllAbsoluteSourceTrees()) m_RealPathToFileRefMap.Add(tree, new Dictionary()); m_ProjectPathToGroupMap = new Dictionary(); m_ProjectPathToVariantGroupMap = new Dictionary(); m_GroupGuidToProjectPathMap = new Dictionary(); m_VariantGroupGuidToProjectPathMap = new Dictionary(); m_GuidToParentGroupMap = new Dictionary(); m_GuidToParentVariantGroupMap = new Dictionary(); } private void BuildCommentMapForBuildFiles(GUIDToCommentMap comments, List guids, string sectName) { foreach (var guid in guids) { var buildFile = BuildFilesGet(guid); if (buildFile != null) { var fileRef = FileRefsGet(buildFile.fileRef); if (fileRef != null) comments.Add(guid, String.Format("{0} in {1}", fileRef.name, sectName)); else { var reference = references[buildFile.fileRef]; if (reference != null) comments.Add(guid, String.Format("{0} in {1}", reference.path, sectName)); } } } } private GUIDToCommentMap BuildCommentMap() { GUIDToCommentMap comments = new GUIDToCommentMap(); // buildFiles are handled below // filerefs are handled below foreach (var e in groups.GetObjects()) comments.Add(e.guid, e.name); foreach (var e in containerItems.GetObjects()) comments.Add(e.guid, "PBXContainerItemProxy"); foreach (var e in references.GetObjects()) comments.Add(e.guid, e.path); foreach (var e in sources.GetObjects()) { comments.Add(e.guid, "Sources"); BuildCommentMapForBuildFiles(comments, e.files, "Sources"); } foreach (var e in resources.GetObjects()) { comments.Add(e.guid, "Resources"); BuildCommentMapForBuildFiles(comments, e.files, "Resources"); } foreach (var e in frameworks.GetObjects()) { comments.Add(e.guid, "Frameworks"); BuildCommentMapForBuildFiles(comments, e.files, "Frameworks"); } foreach (var e in copyFiles.GetObjects()) { string sectName = e.name; if (sectName == null) sectName = "CopyFiles"; comments.Add(e.guid, sectName); BuildCommentMapForBuildFiles(comments, e.files, sectName); } foreach (var e in shellScripts.GetObjects()) comments.Add(e.guid, "ShellScript"); foreach (var e in targetDependencies.GetObjects()) comments.Add(e.guid, "PBXTargetDependency"); foreach (var e in nativeTargets.GetObjects()) { comments.Add(e.guid, e.name); comments.Add(e.buildConfigList, String.Format("Build configuration list for PBXNativeTarget \"{0}\"", e.name)); } foreach (var e in variantGroups.GetObjects()) comments.Add(e.guid, e.name); foreach (var e in buildConfigs.GetObjects()) comments.Add(e.guid, e.name); foreach (var e in project.GetObjects()) { comments.Add(e.guid, "Project object"); comments.Add(e.buildConfigList, "Build configuration list for PBXProject \"Unity-iPhone\""); // FIXME: project name is hardcoded } foreach (var e in fileRefs.GetObjects()) comments.Add(e.guid, e.name); if (m_RootElements.Contains("rootObject") && m_RootElements["rootObject"] is PBXElementString) comments.Add(m_RootElements["rootObject"].AsString(), "Project object"); return comments; } private static PBXElementDict ParseContent(string content) { TokenList tokens = Lexer.Tokenize(content); var parser = new Parser(tokens); TreeAST ast = parser.ParseTree(); return Serializer.ParseTreeAST(ast, tokens, content); } public void ReadFromStream(TextReader sr) { Clear(); m_RootElements = ParseContent(sr.ReadToEnd()); if (!m_RootElements.Contains("objects")) throw new Exception("Invalid PBX project file: no objects element"); var objects = m_RootElements["objects"].AsDict(); m_RootElements.Remove("objects"); m_RootElements.SetString("objects", "OBJMARKER"); if (m_RootElements.Contains("objectVersion")) { m_ObjectVersion = m_RootElements["objectVersion"].AsString(); m_RootElements.Remove("objectVersion"); } var allGuids = new List(); string prevSectionName = null; foreach (var kv in objects.values) { allGuids.Add(kv.Key); var el = kv.Value; if (!(el is PBXElementDict) || !el.AsDict().Contains("isa")) { m_UnknownObjects.values.Add(kv.Key, el); continue; } var dict = el.AsDict(); var sectionName = dict["isa"].AsString(); if (m_Section.ContainsKey(sectionName)) { var section = m_Section[sectionName]; section.AddObject(kv.Key, dict); } else { UnknownSection section; if (m_UnknownSections.ContainsKey(sectionName)) section = m_UnknownSections[sectionName]; else { section = new UnknownSection(sectionName); m_UnknownSections.Add(sectionName, section); } section.AddObject(kv.Key, dict); // update section order if (!m_SectionOrder.Contains(sectionName)) { int pos = 0; if (prevSectionName != null) { // this never fails, because we already added any previous unknown sections // to m_SectionOrder pos = m_SectionOrder.FindIndex(x => x == prevSectionName); pos += 1; } m_SectionOrder.Insert(pos, sectionName); } } prevSectionName = sectionName; } RepairStructure(allGuids); RefreshAuxMaps(); } public string WriteToString() { var commentMap = BuildCommentMap(); var emptyChecker = new PropertyCommentChecker(); var emptyCommentMap = new GUIDToCommentMap(); // since we need to add custom comments, the serialization is much more complex StringBuilder objectsSb = new StringBuilder(); if (m_ObjectVersion != null) // objectVersion comes right before objects objectsSb.AppendFormat("objectVersion = {0};\n\t", m_ObjectVersion); objectsSb.Append("objects = {"); foreach (string sectionName in m_SectionOrder) { if (m_Section.ContainsKey(sectionName)) m_Section[sectionName].WriteSection(objectsSb, commentMap); else if (m_UnknownSections.ContainsKey(sectionName)) m_UnknownSections[sectionName].WriteSection(objectsSb, commentMap); } foreach (var kv in m_UnknownObjects.values) Serializer.WriteDictKeyValue(objectsSb, kv.Key, kv.Value, 2, false, emptyChecker, emptyCommentMap); objectsSb.Append("\n\t};"); StringBuilder contentSb = new StringBuilder(); contentSb.Append("// !$*UTF8*$!\n"); Serializer.WriteDict(contentSb, m_RootElements, 0, false, new PropertyCommentChecker(new string[]{"rootObject/*"}), commentMap); contentSb.Append("\n"); string content = contentSb.ToString(); content = content.Replace("objects = OBJMARKER;", objectsSb.ToString()); return content; } // This method walks the project structure and removes invalid entries. void RepairStructure(List allGuids) { var guidSet = new Dictionary(); // emulate HashSet on .Net 2.0 foreach (var guid in allGuids) guidSet.Add(guid, false); while (RepairStructureImpl(guidSet) == true) ; } /* Iterates the given guid list and removes all guids that are not in allGuids dictionary. */ static void RemoveMissingGuidsFromGuidList(PBX.GUIDList guidList, Dictionary allGuids) { List guidsToRemove = null; foreach (var guid in guidList) { if (!allGuids.ContainsKey(guid)) { if (guidsToRemove == null) guidsToRemove = new List(); guidsToRemove.Add(guid); } } if (guidsToRemove != null) { foreach (var guid in guidsToRemove) guidList.RemoveGUID(guid); } } /* Removes objects from the given @a section for which @a checker returns true. Also removes the guids of the removed elements from allGuids dictionary. Returns true if any objects were removed. */ static bool RemoveObjectsFromSection(KnownSectionBase section, Dictionary allGuids, Func checker) where T : PBXObjectData, new() { List guidsToRemove = null; foreach (var kv in section.GetEntries()) { if (checker(kv.Value)) { if (guidsToRemove == null) guidsToRemove = new List(); guidsToRemove.Add(kv.Key); } } if (guidsToRemove != null) { foreach (var guid in guidsToRemove) { section.RemoveEntry(guid); allGuids.Remove(guid); } return true; } return false; } // Returns true if changes were done and one should call RepairStructureImpl again bool RepairStructureImpl(Dictionary allGuids) { bool changed = false; // PBXBuildFile changed |= RemoveObjectsFromSection(buildFiles, allGuids, o => (o.fileRef == null || !allGuids.ContainsKey(o.fileRef))); // PBXFileReference / fileRefs not cleaned // PBXGroup changed |= RemoveObjectsFromSection(groups, allGuids, o => o.children == null); foreach (var o in groups.GetObjects()) RemoveMissingGuidsFromGuidList(o.children, allGuids); // PBXContainerItem / containerItems not cleaned // PBXReferenceProxy / references not cleaned // PBXSourcesBuildPhase changed |= RemoveObjectsFromSection(sources, allGuids, o => o.files == null); foreach (var o in sources.GetObjects()) RemoveMissingGuidsFromGuidList(o.files, allGuids); // PBXFrameworksBuildPhase changed |= RemoveObjectsFromSection(frameworks, allGuids, o => o.files == null); foreach (var o in frameworks.GetObjects()) RemoveMissingGuidsFromGuidList(o.files, allGuids); // PBXResourcesBuildPhase changed |= RemoveObjectsFromSection(resources, allGuids, o => o.files == null); foreach (var o in resources.GetObjects()) RemoveMissingGuidsFromGuidList(o.files, allGuids); // PBXCopyFilesBuildPhase changed |= RemoveObjectsFromSection(copyFiles, allGuids, o => o.files == null); foreach (var o in copyFiles.GetObjects()) RemoveMissingGuidsFromGuidList(o.files, allGuids); // PBXShellScriptsBuildPhase changed |= RemoveObjectsFromSection(shellScripts, allGuids, o => o.files == null); foreach (var o in shellScripts.GetObjects()) RemoveMissingGuidsFromGuidList(o.files, allGuids); // PBXNativeTarget changed |= RemoveObjectsFromSection(nativeTargets, allGuids, o => o.phases == null); foreach (var o in nativeTargets.GetObjects()) RemoveMissingGuidsFromGuidList(o.phases, allGuids); // PBXTargetDependency / targetDependencies not cleaned // PBXVariantGroup changed |= RemoveObjectsFromSection(variantGroups, allGuids, o => o.children == null); foreach (var o in variantGroups.GetObjects()) RemoveMissingGuidsFromGuidList(o.children, allGuids); // XCBuildConfiguration / buildConfigs not cleaned // XCConfigurationList changed |= RemoveObjectsFromSection(buildConfigLists, allGuids, o => o.buildConfigs == null); foreach (var o in buildConfigLists.GetObjects()) RemoveMissingGuidsFromGuidList(o.buildConfigs, allGuids); // PBXProject project not cleaned return changed; } } } // namespace UnityModule.iOS.Xcode