Singularity/Assets/Plugins/ImaginationOverflow/UniversalDeepLinking/Editor/Xcode/PBXProjectData.cs
2024-05-06 11:45:45 -07:00

819 lines
36 KiB
C#

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<PBXBuildFileData>;
using PBXFileReferenceSection = KnownSectionBase<PBXFileReferenceData>;
using PBXGroupSection = KnownSectionBase<PBXGroupData>;
using PBXContainerItemProxySection = KnownSectionBase<PBXContainerItemProxyData>;
using PBXReferenceProxySection = KnownSectionBase<PBXReferenceProxyData>;
using PBXSourcesBuildPhaseSection = KnownSectionBase<PBXSourcesBuildPhaseData>;
using PBXFrameworksBuildPhaseSection= KnownSectionBase<PBXFrameworksBuildPhaseData>;
using PBXResourcesBuildPhaseSection = KnownSectionBase<PBXResourcesBuildPhaseData>;
using PBXCopyFilesBuildPhaseSection = KnownSectionBase<PBXCopyFilesBuildPhaseData>;
using PBXShellScriptBuildPhaseSection = KnownSectionBase<PBXShellScriptBuildPhaseData>;
using PBXVariantGroupSection = KnownSectionBase<PBXVariantGroupData>;
using PBXNativeTargetSection = KnownSectionBase<PBXNativeTargetData>;
using PBXTargetDependencySection = KnownSectionBase<PBXTargetDependencyData>;
using XCBuildConfigurationSection = KnownSectionBase<XCBuildConfigurationData>;
using XCConfigurationListSection = KnownSectionBase<XCConfigurationListData>;
using UnknownSection = KnownSectionBase<PBXObjectData>;
internal class PBXProjectData
{
private Dictionary<string, SectionBase> m_Section = null;
private PBXElementDict m_RootElements = null;
private PBXElementDict m_UnknownObjects = null;
private string m_ObjectVersion = null;
private List<string> m_SectionOrder = null;
private Dictionary<string, UnknownSection> 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<string, Dictionary<string, PBXBuildFileData>> m_FileGuidToBuildFileMap = null;
private Dictionary<string, PBXFileReferenceData> m_ProjectPathToFileRefMap = null;
private Dictionary<string, string> m_FileRefGuidToProjectPathMap = null;
private Dictionary<PBXSourceTree, Dictionary<string, PBXFileReferenceData>> m_RealPathToFileRefMap = null;
private Dictionary<string, PBXGroupData> m_ProjectPathToGroupMap = null;
private Dictionary<string, PBXVariantGroupData> m_ProjectPathToVariantGroupMap = null;
private Dictionary<string, string> m_GroupGuidToProjectPathMap = null;
private Dictionary<string, string> m_VariantGroupGuidToProjectPathMap = null;
private Dictionary<string, PBXGroupData> m_GuidToParentGroupMap = null;
private Dictionary<string, PBXVariantGroupData> 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<string, PBXBuildFileData>();
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<PBXBuildFileData> 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<string, PBXFileReferenceData>();
}
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<string, PBXFileReferenceData>();
}
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<string, PBXBuildFileData> 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<string>(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<string>(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<string, PBXBuildFileData>();
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<string, UnknownSection>();
m_Section = new Dictionary<string, SectionBase>
{
{ "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<string>{
"PBXBuildFile", "PBXContainerItemProxy", "PBXCopyFilesBuildPhase", "PBXFileReference",
"PBXFrameworksBuildPhase", "PBXGroup", "PBXNativeTarget", "PBXProject", "PBXReferenceProxy",
"PBXResourcesBuildPhase", "PBXShellScriptBuildPhase", "PBXSourcesBuildPhase", "PBXTargetDependency",
"PBXVariantGroup", "XCBuildConfiguration", "XCConfigurationList"
};
m_FileGuidToBuildFileMap = new Dictionary<string, Dictionary<string, PBXBuildFileData>>();
m_ProjectPathToFileRefMap = new Dictionary<string, PBXFileReferenceData>();
m_FileRefGuidToProjectPathMap = new Dictionary<string, string>();
m_RealPathToFileRefMap = new Dictionary<PBXSourceTree, Dictionary<string, PBXFileReferenceData>>();
foreach (var tree in FileTypeUtils.AllAbsoluteSourceTrees())
m_RealPathToFileRefMap.Add(tree, new Dictionary<string, PBXFileReferenceData>());
m_ProjectPathToGroupMap = new Dictionary<string, PBXGroupData>();
m_ProjectPathToVariantGroupMap = new Dictionary<string, PBXVariantGroupData>();
m_GroupGuidToProjectPathMap = new Dictionary<string, string>();
m_VariantGroupGuidToProjectPathMap = new Dictionary<string, string>();
m_GuidToParentGroupMap = new Dictionary<string, PBXGroupData>();
m_GuidToParentVariantGroupMap = new Dictionary<string, PBXVariantGroupData>();
}
private void BuildCommentMapForBuildFiles(GUIDToCommentMap comments, List<string> 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>();
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<string> allGuids)
{
var guidSet = new Dictionary<string, bool>(); // 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<string, bool> allGuids)
{
List<string> guidsToRemove = null;
foreach (var guid in guidList)
{
if (!allGuids.ContainsKey(guid))
{
if (guidsToRemove == null)
guidsToRemove = new List<string>();
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<T>(KnownSectionBase<T> section,
Dictionary<string, bool> allGuids,
Func<T, bool> checker) where T : PBXObjectData, new()
{
List<string> guidsToRemove = null;
foreach (var kv in section.GetEntries())
{
if (checker(kv.Value))
{
if (guidsToRemove == null)
guidsToRemove = new List<string>();
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<string, bool> 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