//Construct an XcodeObject and fill it with data from the given reader
    public XcodeObject(StreamReader reader, Dictionary <string, XcodeObject> uids)
    {
        m_uniqueIds = uids;

        StringBuilder sb = new StringBuilder();
        //Object definitions are expected to start with an opening brace, eat it
        if(reader.Read() != '{'){
            Debug.LogError("Object didn't start with opening brace");
        }

        bool quoted = false;	//True if Currently inside quotes, ignore special character meanings
        bool escaped = false;	//True if Next character has special meaning ignored
        bool commented = false;	//True if Inside a block comment, ignore everything except end-comment
        bool isLiteralValue = true; //Value is a literal (as opposed to a container of objects)
        string key = ""; //Last key read
        int readChar; //Char read from the stream
        //Now start parsing for real
        while(reader.Peek() != -1){
            //If currently in a comment
            if(commented){
                //Check if we drop out of comment
                readChar = reader.Read();
                if(readChar == '*' && reader.Peek() == '/'){
                    reader.Read(); //Eat the end-of-comment '/'
                    commented = false;
                }
                continue; //Ignore read character
            }
            //Check to see if we should start reading child object instead
            if(!quoted && reader.Peek() == '{'){
                isLiteralValue = false;
                children[key] = new XcodeObject(reader, m_uniqueIds); //Read child data
            }
            //Read next character
            readChar = reader.Read();
            // = : Key finished
            if(!quoted && readChar == '='){
                key = sb.ToString().Trim();
                sb.Length = 0; //Clear contents of stringbuilder
            }
            // ; : Key-value pair finished
            else if(!quoted && readChar == ';'){
                if(isLiteralValue){
                    string value = sb.ToString().Trim();
                    literals[key] = value;
                }
                sb.Length = 0; //Clear contents of stringbuilder
                isLiteralValue = true;
            }
            // } : Object finished
            else if(!quoted && readChar == '}'){
                break;
            }
            // / : Might be the start of a comment
            else if(!quoted && readChar == '/'){
                //Comment starts
                if(reader.Peek() == '*'){
                    reader.Read(); //Eat the *
                    commented = true;
                }
                //Comment didn't start, add the '/'
                else{
                    sb.Append((char)readChar);
                }
            }
            //Ignore whitespace outside quotes
            else if(!quoted && char.IsWhiteSpace((char)readChar)){
                // Do nothing
            }
            // " : Toggle quoted / not
            else if(!escaped && readChar == '"'){
                quoted = !quoted;
                sb.Append((char)readChar);
                escaped = false;
            }
            // \ : "Escape" next char
            else if(!escaped && readChar == '\\'){
                sb.Append((char)readChar);
                escaped = true;
            }
            //Just another character
            else{
                sb.Append((char)readChar);
                escaped = false;
            }
        }
    }
 //frameworkFileName -- filepath relative to SDKs directory (e.g. "System/Library/Frameworks/CoreTelephony.framework")
 //framework is put under the "Frameworks" group and added to the main target
 protected void AddSDKFramework(string frameworkFilename)
 {
     //Make the file reference object
     XcodeObject frameworkObject = new XcodeObject(UniqueIds);
     frameworkObject.literals["isa"] = "PBXFileReference";
     if(frameworkFilename.EndsWith(".dylib")){
         frameworkObject.literals["lastKnownFileType"] = "\"compiled.mach-o.dylib\"";
     }
     else{
         frameworkObject.literals["lastKnownFileType"] = "wrapper.framework";
     }
     frameworkObject.literals["name"] = "\""+Path.GetFileName(frameworkFilename)+"\"";
     frameworkObject.literals["path"] = "\""+frameworkFilename+"\"";
     frameworkObject.literals["sourceTree"] = "SDKROOT";
     string frameworkUId = GetNewUniqueId(frameworkObject);
     //Make appear in frameworks group
     GetGroup("Frameworks").AddLiteralListElement("children", frameworkUId);
     //Make build reference
     XcodeObject buildFile = new XcodeObject(UniqueIds);
     buildFile.literals["isa"] = "PBXBuildFile";
     buildFile.literals["fileRef"] = frameworkUId;
     string buildFileUId = GetNewUniqueId(buildFile);
     //Add build reference to build phase
     MainTargetFrameworkPhase.AddLiteralListElement("files", buildFileUId);
 }
    //Provides unique file IDs and ensures the ObjectRoot knows about them. Don't have more than 2^32 files.
    protected string GetNewUniqueId(XcodeObject requester)
    {
        //Find a non-taken UID
        string uid;
        do{
            uid = m_uidPrefix + m_uidOffset.ToString("X8"); //Hex encode
            m_uidOffset++;
        } while(UniqueIds.ContainsKey(uid));

        //Assign it
        UniqueIds[uid] = requester;
        ObjectRoot.children[uid] = requester;
        return uid;
    }
 //Copes a directory into the XCode project as a framework -- the directory is referenced directly
 //as a framework, its contents are not directly referenced.
 protected void AddFrameworkDirectory(DirectoryInfo srcDir, string destDir, XcodeObject parentGroup)
 {
     //Create XCode file ref
     XcodeObject fileRef = new XcodeObject(UniqueIds);
     fileRef.literals["isa"] = "PBXFileReference";
     fileRef.literals["name"] = "\""+Path.GetFileName(srcDir.Name)+"\"";
     fileRef.literals["path"] = "\""+srcDir.Name+"\"";
     fileRef.literals["sourceTree"] = "\"<group>\"";
     fileRef.literals["lastKnownFileType"] = "wrapper.framework";
     //Add to group
     string fileRefId = GetNewUniqueId(fileRef);
     parentGroup.AddLiteralListElement("children", fileRefId);
     //Create Xcode build file ref
     XcodeObject buildFile = new XcodeObject(UniqueIds);
     buildFile.literals["isa"] = "PBXBuildFile";
     buildFile.literals["fileRef"] = fileRefId;
     string buildFileId = GetNewUniqueId(buildFile);
     //Add build reference to resources phase
     MainTargetFrameworkPhase.AddLiteralListElement("files", buildFileId);
     //Copy framework contents
     DirectoryInfo frameworkDir = new DirectoryInfo(Path.Combine(destDir, srcDir.Name));
     ExternalProcesses.RunProcess("cp", "-a \"" + srcDir.FullName + "\" \"" + frameworkDir.FullName+ "\"");
     //Add path of framework's containing directory to search paths
     AddFrameworkSearchPath(frameworkDir.Parent.FullName);
 }
 protected void AddFile(FileInfo file, string destDir, XcodeObject parentGroup)
 {
     string filePath = Path.Combine(destDir, file.Name);
     if(!File.Exists(filePath)){
         //Create XCode file ref
         XcodeObject fileRef = new XcodeObject(UniqueIds);
         fileRef.literals["isa"] = "PBXFileReference";
         fileRef.literals["name"] = "\""+file.Name+"\"";
         fileRef.literals["path"] = "\""+file.Name+"\"";
         fileRef.literals["sourceTree"] = "\"<group>\"";
         //Add to group
         string fileRefId = GetNewUniqueId(fileRef);
         parentGroup.AddLiteralListElement("children", fileRefId);
         //Create Xcode build file ref
         XcodeObject buildFile = new XcodeObject(UniqueIds);
         buildFile.literals["isa"] = "PBXBuildFile";
         buildFile.literals["fileRef"] = fileRefId;
         string buildFileId = GetNewUniqueId(buildFile);
         //Add build reference to appropriate build phase
         if(IsSource(file.Name)){
             MainTargetSourcePhase.AddLiteralListElement("files", buildFileId);
         }
         else if(IsArchive(file.Name)){
             MainTargetFrameworkPhase.AddLiteralListElement("files", buildFileId);
             AddLibrarySearchPath(destDir);
         }
         else if(!IsHeader(file.Name)){
             MainTargetResourcePhase.AddLiteralListElement("files", buildFileId);
         }
         if(EnableBfgAutomaticReferenceCounting && UsesAutomaticReferenceCounting(file.FullName)){
             var settingsObj = new XcodeObject(UniqueIds);
             buildFile.children["settings"] = settingsObj;
             settingsObj.literals["COMPILER_FLAGS"] = kAutomaticReferenceCountingFlag;
         }
     }
     else{
         Console.WriteLine("Overwrote file " + filePath);
     }
     //Copy disk file
     file.CopyTo(filePath, true);
 }
 //Copies a directory and its contents into the XCode project recursively and adds it to the main target.
 protected void AddDirectoryRecursively(string sourceDir, string destDir, XcodeObject parentGroup)
 {
     DirectoryInfo sourceDirInfo = new DirectoryInfo(sourceDir);
     //Create files
     foreach(FileInfo file in sourceDirInfo.GetFiles()){
         if(IsIgnored(file.Name)){
             continue;
         }
         else{
             AddFile(file, destDir, parentGroup);
         }
     }
     //Recurse
     foreach(DirectoryInfo subDir in sourceDirInfo.GetDirectories()){
         if(IsIgnored(subDir.Name)){
             // ...
         }
         else if(IsBundle(subDir.Name)){
             AddBundle(subDir, destDir, parentGroup);
         }
         else if(IsFrameworkDirectory(subDir.Name)){
             AddFrameworkDirectory(subDir, destDir, parentGroup);
         }
         else{
             XcodeObject groupObj = null;
             DirectoryInfo destSubDir = new DirectoryInfo(Path.Combine(destDir, subDir.Name));
             //Set up the dest directory
             if(!destSubDir.Exists || parentGroup.GetGroup(destSubDir.Name) == null){
                 destSubDir.Create();
                 //Make containing Xcode group
                 groupObj = new XcodeObject(UniqueIds);
                 groupObj.literals["isa"] = "PBXGroup";
                 groupObj.literals["sourceTree"] = "\"<group>\"";
                 groupObj.literals["children"] = "()";
                 groupObj.literals["path"] = "\""+destSubDir.Name+"\"";
                 string groupObjId = GetNewUniqueId(groupObj);
                 parentGroup.AddLiteralListElement("children", groupObjId);
             }
             else{
                 groupObj = parentGroup.GetGroup(destSubDir.Name);
             }
             AddDirectoryRecursively(subDir.FullName, Path.Combine(destDir, subDir.Name), groupObj);
         }
     }
 }
 //Adds a directory to the XCode project as a bundle -- the directory is referenced directly as a file, its contents are copied unreferenced
 protected void AddBundle(DirectoryInfo srcDir, string destDir, XcodeObject parentGroup)
 {
     //Create XCode file ref
     XcodeObject fileRef = new XcodeObject(UniqueIds);
     fileRef.literals["isa"] = "PBXFileReference";
     fileRef.literals["name"] = "\""+srcDir.Name+"\"";
     fileRef.literals["path"] = "\""+srcDir.Name+"\"";
     fileRef.literals["sourceTree"] = "\"<group>\"";
     //Add to group
     string fileRefId = GetNewUniqueId(fileRef);
     parentGroup.AddLiteralListElement("children", fileRefId);
     //Create Xcode build file ref
     XcodeObject buildFile = new XcodeObject(UniqueIds);
     buildFile.literals["isa"] = "PBXBuildFile";
     buildFile.literals["fileRef"] = fileRefId;
     string buildFileId = GetNewUniqueId(buildFile);
     //Add build reference to resources phase
     MainTargetResourcePhase.AddLiteralListElement("files", buildFileId);
     DirectoryInfo bundleDir = new DirectoryInfo(Path.Combine(destDir, srcDir.Name));
     AddBundleContentsRecursively(srcDir, bundleDir);
 }
    public override bool PostprocessBuild(BuildTarget target, string buildPath, string dataPath)
    {
        if (!PathsValid) {
            Debug.LogError("Paths not valid: " + m_errorMessage);
            return false;
        }
        BuildPath = Path.GetFullPath(buildPath);
        try{
            UniqueIds = new Dictionary<string, XcodeObject>();

            //Load pbxproj file
            Console.WriteLine("Loading .xcodeproj at " + buildPath + "/" + kProjectPath);
            StreamReader reader = new StreamReader(buildPath + "/" + kProjectPath);
            reader.ReadLine(); //Eat explicit encoding header
            XcodeObject proj = new XcodeObject(reader, UniqueIds);
            reader.Close();

            //Setup important properties
            FindRequiredFrameworks();
            ObjectRoot = proj.children["objects"];
            ObjectRoot.DefinePreExistingObjects();
            ProjectObject = ObjectRoot.children[proj.literals["rootObject"]];
            RootGroup = ObjectRoot.children[ProjectObject.literals["mainGroup"]];
            List<XcodeObject> mainTargetBuildPhases = ProjectObject.ParseIDList("targets")[0].ParseIDList("buildPhases");
            MainTargetFrameworkPhase = mainTargetBuildPhases.Find(delegate(XcodeObject obj){return obj.literals["isa"] == "PBXFrameworksBuildPhase";});
            MainTargetResourcePhase = mainTargetBuildPhases.Find(delegate(XcodeObject obj){return obj.literals["isa"] == "PBXResourcesBuildPhase";});
            MainTargetSourcePhase = mainTargetBuildPhases.Find(delegate(XcodeObject obj){return obj.literals["isa"] == "PBXSourcesBuildPhase";});

            //Remove secondary targets (currently only the non-functional simulator target)
            string[] buildTargetIDs = XcodeObject.SplitPCSVList(ProjectObject.literals["targets"]);
            ProjectObject.literals.Remove("targets");
            ProjectObject.AddLiteralListElement("targets", buildTargetIDs[0]);

            //Add required frameworks
            Console.WriteLine("Adding required frameworks");
            foreach(string frameworkPath in RequiredFrameworks){
                Console.WriteLine(">\t" + frameworkPath);
                AddSDKFramework(frameworkPath);
            }

            //Add files
            Console.WriteLine("Adding specified directories and frameworks");
            for(int i = 0; i < m_paths.Length; i++){
                if (!m_paths[i].isFrameworkLink) {
                    Console.WriteLine("DIR>\t"+m_paths[i].path);
                    AddDirectoryRecursively(m_paths[i].path, buildPath, RootGroup);
                }
                else if (!RequiredFrameworks.Contains(m_paths[i].path)) {
                    Console.WriteLine("FWK>\t"+m_paths[i].path);
                    AddSDKFramework(m_paths[i].path);
                }
            }

            //Save modified file
            Console.WriteLine("Saving .xcodeproj file");
            StreamWriter writer = new StreamWriter(buildPath + "/" + kProjectPath); //Overwrite .pbxproj file
            writer.Write("// !$*UTF8*$!\n"); //Explicit format header
            proj.Save(writer, 0);
            writer.Close();
        }
        catch(Exception e){
            Debug.LogError(e.ToString());
            return false;
        }
        return true;
    }