using System; using System.Collections.Generic; using System.IO; using System.Linq; using ImaginationOverflow.UniversalDeepLinking.Editor.Xcode.PBX; namespace ImaginationOverflow.UniversalDeepLinking.Editor.Xcode { 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; // Determines the tree the given path is relative to public enum PBXSourceTree { Absolute, // The path is absolute Source, // The path is relative to the source folder Group, // The path is relative to the folder it's in. This enum is used only internally, // do not use it as function parameter Build, // The path is relative to the build products folder Developer, // The path is relative to the developer folder Sdk // The path is relative to the sdk folder } public class PBXProject { PBXProjectData m_Data = new PBXProjectData(); // convenience accessors for public members of data. This is temporary; will be fixed by an interface change // of PBXProjectData internal PBXContainerItemProxySection containerItems { get { return m_Data.containerItems; } } internal PBXReferenceProxySection references { get { return m_Data.references; } } internal PBXSourcesBuildPhaseSection sources { get { return m_Data.sources; } } internal PBXFrameworksBuildPhaseSection frameworks { get { return m_Data.frameworks; } } internal PBXResourcesBuildPhaseSection resources { get { return m_Data.resources; } } internal PBXCopyFilesBuildPhaseSection copyFiles { get { return m_Data.copyFiles; } } internal PBXShellScriptBuildPhaseSection shellScripts { get { return m_Data.shellScripts; } } internal PBXNativeTargetSection nativeTargets { get { return m_Data.nativeTargets; } } internal PBXTargetDependencySection targetDependencies { get { return m_Data.targetDependencies; } } internal PBXVariantGroupSection variantGroups { get { return m_Data.variantGroups; } } internal XCBuildConfigurationSection buildConfigs { get { return m_Data.buildConfigs; } } internal XCConfigurationListSection buildConfigLists { get { return m_Data.buildConfigLists; } } internal PBXProjectSection project { get { return m_Data.project; } } internal PBXBuildFileData BuildFilesGet(string guid) { return m_Data.BuildFilesGet(guid); } internal void BuildFilesAdd(string targetGuid, PBXBuildFileData buildFile) { m_Data.BuildFilesAdd(targetGuid, buildFile); } internal void BuildFilesRemove(string targetGuid, string fileGuid) { m_Data.BuildFilesRemove(targetGuid, fileGuid); } internal PBXBuildFileData BuildFilesGetForSourceFile(string targetGuid, string fileGuid) { return m_Data.BuildFilesGetForSourceFile(targetGuid, fileGuid); } internal IEnumerable BuildFilesGetAll() { return m_Data.BuildFilesGetAll(); } internal void FileRefsAdd(string realPath, string projectPath, PBXGroupData parent, PBXFileReferenceData fileRef) { m_Data.FileRefsAdd(realPath, projectPath, parent, fileRef); } internal void FileRefsAdd(string realPath, string projectPath, PBXVariantGroupData parent, PBXFileReferenceData fileRef) { m_Data.FileRefsAdd(realPath, projectPath, parent, fileRef); } internal PBXFileReferenceData FileRefsGet(string guid) { return m_Data.FileRefsGet(guid); } internal PBXFileReferenceData FileRefsGetByRealPath(string path, PBXSourceTree sourceTree) { return m_Data.FileRefsGetByRealPath(path, sourceTree); } internal PBXFileReferenceData FileRefsGetByProjectPath(string path) { return m_Data.FileRefsGetByProjectPath(path); } internal void FileRefsRemove(string guid) { m_Data.FileRefsRemove(guid); } internal PBXGroupData GroupsGet(string guid) { return m_Data.GroupsGet(guid); } internal PBXGroupData GroupsGetByChild(string childGuid) { return m_Data.GroupsGetByChild(childGuid); } internal PBXGroupData GroupsGetMainGroup() { return m_Data.GroupsGetMainGroup(); } internal PBXGroupData GroupsGetByProjectPath(string sourceGroup) { return m_Data.GroupsGetByProjectPath(sourceGroup); } internal void GroupsAdd(string projectPath, PBXGroupData parent, PBXGroupData gr) { m_Data.GroupsAdd(projectPath, parent, gr); } internal void GroupsAddDuplicate(PBXGroupData gr) { m_Data.GroupsAddDuplicate(gr); } internal void GroupsRemove(string guid) { m_Data.GroupsRemove(guid); } internal PBXVariantGroupData VariantGroupsGet(string guid) { return m_Data.VariantGroupsGet(guid); } internal PBXVariantGroupData VariantGroupsGetByChild(string childGuid) { return m_Data.VariantGroupsGetByChild(childGuid); } internal PBXVariantGroupData VariantGroupsGetByProjectPath(string sourceGroup) { return m_Data.VariantGroupsGetByProjectPath(sourceGroup); } internal void VariantGroupsAdd(string projectPath, PBXGroupData parent, PBXVariantGroupData gr) { m_Data.VariantGroupsAdd(projectPath, parent, gr); } internal void VariantGroupsAddDuplicate(PBXVariantGroupData gr) { m_Data.VariantGroupsAddDuplicate(gr); } internal void VariantGroupsRemove(string guid) { m_Data.VariantGroupsRemove(guid); } internal FileGUIDListBase BuildSectionAny(PBXNativeTargetData target, string path, bool isFolderRef) { return m_Data.BuildSectionAny(target, path, isFolderRef); } internal FileGUIDListBase BuildSectionAny(string sectionGuid) { return m_Data.BuildSectionAny(sectionGuid); } /// /// Returns the path to PBX project in the given Unity build path. This function can only /// be used in Unity-generated projects /// /// The project build path /// The path to the PBX project file that can later be opened via ReadFromFile function public static string GetPBXProjectPath(string buildPath) { if (buildPath.EndsWith(".xcodeproj")) return PBXPath.Combine(buildPath, "project.pbxproj"); else { return PBXPath.Combine(buildPath, "Unity-iPhone.xcodeproj/project.pbxproj"); } } /// /// Returns the default main target name in Unity project. /// The returned target name can then be used to retrieve the GUID of the target via TargetGuidByName /// function. This function can only be used in Unity-generated projects. /// /// The default main target name. public static string GetUnityTargetName() { return "Unity-iPhone"; } /// /// Returns the default test target name in Unity project. /// The returned target name can then be used to retrieve the GUID of the target via TargetGuidByName /// function. This function can only be used in Unity-generated projects. /// /// The default test target name. public static string GetUnityTestTargetName() { return "Unity-iPhone Tests"; } /// /// Returns the GUID of the project. The project GUID identifies a project-wide native target which /// is used to set project-wide properties. This GUID can be passed to any functions that accepts /// target GUIDs as parameters. /// /// The GUID of the project. public string ProjectGuid() { return project.project.guid; } /// /// Returns the GUID of the native target with the given name. /// In projects produced by Unity the main target can be retrieved via GetUnityTargetName function, /// whereas the test target name can be retrieved by GetUnityTestTargetName function. /// /// The name of the native target. /// The GUID identifying the native target. public string TargetGuidByName(string name) { foreach (var entry in nativeTargets.GetEntries()) if (entry.Value.name == name) return entry.Key; return null; } /// /// Checks if files with the given extension are known to PBXProject. /// /// Returns true if is the extension is known, false otherwise. /// The file extension (leading dot is not necessary, but accepted). public static bool IsKnownExtension(string ext) { return FileTypeUtils.IsKnownExtension(ext); } /// /// Checks if files with the given extension are known to PBXProject. /// Returns true if the extension is not known by PBXProject. /// /// Returns true if is the extension is known, false otherwise. /// The file extension (leading dot is not necessary, but accepted). public static bool IsBuildable(string ext) { return FileTypeUtils.IsBuildableFile(ext); } // The same file can be referred to by more than one project path. private string AddFileImpl(string path, string projectPath, PBXSourceTree tree, bool isFolderReference) { path = PBXPath.FixSlashes(path); projectPath = PBXPath.FixSlashes(projectPath); if (!isFolderReference && Path.GetExtension(path) != Path.GetExtension(projectPath)) throw new Exception("Project and real path extensions do not match"); string guid = FindFileGuidByProjectPath(projectPath); if (guid == null) guid = FindFileGuidByRealPath(path); if (guid == null) { PBXFileReferenceData fileRef; if (isFolderReference) fileRef = PBXFileReferenceData.CreateFromFolderReference(path, PBXPath.GetFilename(projectPath), tree); else fileRef = PBXFileReferenceData.CreateFromFile(path, PBXPath.GetFilename(projectPath), tree); PBXGroupData parent = CreateSourceGroup(PBXPath.GetDirectory(projectPath)); parent.children.AddGUID(fileRef.guid); FileRefsAdd(path, projectPath, parent, fileRef); guid = fileRef.guid; } return guid; } internal PBXBuildFileData FindFrameworkByFileGuid( PBXCopyFilesBuildPhaseData phase, string fileGuid) { foreach (string file in (IEnumerable)phase.files) { PBXBuildFileData pbxBuildFileData = this.BuildFilesGet(file); if (pbxBuildFileData.fileRef == fileGuid) return pbxBuildFileData; } return (PBXBuildFileData)null; } /// /// Adds a new file reference to the list of known files. /// The group structure is automatically created to correspond to the project path. /// To add the file to the list of files to build, pass the returned value to [[AddFileToBuild]]. /// /// The GUID of the added file. It can later be used to add the file for building to targets, etc. /// The physical path to the file on the filesystem. /// The project path to the file. /// The source tree the path is relative to. By default it's [[PBXSourceTree.Source]]. /// The [[PBXSourceTree.Group]] tree is not supported. public string AddFile(string path, string projectPath, PBXSourceTree sourceTree = PBXSourceTree.Source) { if (sourceTree == PBXSourceTree.Group) throw new Exception("sourceTree must not be PBXSourceTree.Group"); return AddFileImpl(path, projectPath, sourceTree, false); } /// /// Adds a new folder reference to the list of known files. /// The group structure is automatically created to correspond to the project path. /// To add the folder reference to the list of files to build, pass the returned value to [[AddFileToBuild]]. /// /// The GUID of the added folder reference. It can later be used to add the file for building to targets, etc. /// The physical path to the folder on the filesystem. /// The project path to the folder. /// The source tree the path is relative to. By default it's [[PBXSourceTree.Source]]. /// The [[PBXSourceTree.Group]] tree is not supported. public string AddFolderReference(string path, string projectPath, PBXSourceTree sourceTree = PBXSourceTree.Source) { if (sourceTree == PBXSourceTree.Group) throw new Exception("sourceTree must not be PBXSourceTree.Group"); return AddFileImpl(path, projectPath, sourceTree, true); } public string AddLocalizedFile(string path, string language) { path = PBXPath.FixSlashes(path); string sourceProjectPath = PBXPath.GetDirectory(PBXPath.GetDirectory(path)); string projectPathForVariantGroup = PBXPath.Combine(sourceProjectPath, PBXPath.GetFilename(path)); string projectPathForFileReference = PBXPath.Combine(PBXPath.GetFilename(PBXPath.GetDirectory(path)), PBXPath.GetFilename(path)); PBXGroupData parent = GroupsGetByProjectPath(sourceProjectPath); if (parent == null) { parent = CreateSourceGroup(PBXPath.GetDirectory(PBXPath.GetDirectory(path))); } PBXVariantGroupData variantGroupData = VariantGroupsGetByProjectPath(projectPathForVariantGroup); if (variantGroupData == null) { variantGroupData = PBXVariantGroupData.Create(PBXPath.GetFilename(path)); parent.children.AddGUID(variantGroupData.guid); VariantGroupsAdd(projectPathForVariantGroup, parent, variantGroupData); PBXBuildFileData buildFileData = PBXBuildFileData.CreateFromFile(variantGroupData.guid, false, null); string targetGUID = TargetGuidByName(PBXProject.GetUnityTargetName()); BuildFilesAdd(targetGUID, buildFileData); BuildSectionAny(nativeTargets[targetGUID], Path.GetExtension(path), false).files.AddGUID(buildFileData.guid); } PBXFileReferenceData fileReferenceData = FileRefsGetByProjectPath(projectPathForFileReference); if (fileReferenceData == null) { fileReferenceData = PBXFileReferenceData.CreateFromFile(projectPathForFileReference, language, PBXSourceTree.Group); fileReferenceData.UpdateProps(); fileReferenceData.SetFileTypeByExtension(Path.GetExtension(path)); fileReferenceData.UpdateVars(); variantGroupData.children.AddGUID(fileReferenceData.guid); FileRefsAdd(path, projectPathForFileReference, variantGroupData, fileReferenceData); } return fileReferenceData.guid; } public string AddEntitlementFile(string path) { path = PBXPath.FixSlashes(path); PBXFileReferenceData fileReferenceData = FileRefsGetByProjectPath(path); if (fileReferenceData == null) { fileReferenceData = PBXFileReferenceData.CreateFromFile(path, PBXPath.GetFilename(path), PBXSourceTree.Group); fileReferenceData.UpdateProps(); fileReferenceData.SetFileTypeByExtension(Path.GetExtension(path)); fileReferenceData.UpdateVars(); GroupsGetMainGroup().children.AddGUID(fileReferenceData.guid); FileRefsAdd(path, path, GroupsGetMainGroup(), fileReferenceData); } return fileReferenceData.guid; } private void AddBuildFileImpl(string targetGuid, string fileGuid, bool weak, string compileFlags) { PBXNativeTargetData target = nativeTargets[targetGuid]; PBXFileReferenceData fileRef = FileRefsGet(fileGuid); string ext = Path.GetExtension(fileRef.path); if (FileTypeUtils.IsBuildable(ext, fileRef.isFolderReference) && BuildFilesGetForSourceFile(targetGuid, fileGuid) == null) { PBXBuildFileData buildFile = PBXBuildFileData.CreateFromFile(fileGuid, weak, compileFlags); BuildFilesAdd(targetGuid, buildFile); BuildSectionAny(target, ext, fileRef.isFolderReference).files.AddGUID(buildFile.guid); } } /// /// Configures file for building for the given native target. /// A projects containing multiple native targets, a single file or folder reference can be /// configured to be built in all, some or none of the targets. The file or folder reference is /// added to appropriate build section depending on the file extension. /// /// The GUID of the target as returned by [[TargetGuidByName()]]. /// The file guid returned by [[AddFile]] or [[AddFolderReference]]. public void AddFileToBuild(string targetGuid, string fileGuid) { AddBuildFileImpl(targetGuid, fileGuid, false, null); } /// /// Configures file for building for the given native target with specific compiler flags. /// The function is equivalent to [[AddFileToBuild()]] except that compile flags are specified. /// A projects containing multiple native targets, a single file or folder reference can be /// configured to be built in all, some or none of the targets. The file or folder reference is /// added to appropriate build section depending on the file extension. /// /// The GUID of the target as returned by [[TargetGuidByName()]]. /// The file guid returned by [[AddFile]] or [[AddFolderReference]]. /// Compile flags to use. public void AddFileToBuildWithFlags(string targetGuid, string fileGuid, string compileFlags) { AddBuildFileImpl(targetGuid, fileGuid, false, compileFlags); } public void AddDynamicFrameworkToProject(string targetGuid, string frameworkPathInProject) { var fileGuid = FindFileGuidByProjectPath(frameworkPathInProject); // add file reference as embed framework PBXBuildFileData embedFrameworkFileData = PBXBuildFileData.CreateFromFile(fileGuid, false, ""); BuildFilesAdd(targetGuid, embedFrameworkFileData); // add "Embed Frameworks" section // TODO: check if exists PBXCopyFilesBuildPhaseData embedFrameworksSection = PBXCopyFilesBuildPhaseData.Create("Embed Frameworks", "", "10"); embedFrameworksSection.files.AddGUID(embedFrameworkFileData.guid); m_Data.copyFiles.AddEntry(embedFrameworksSection); // add "Embed Frameworks" section to "Build phases" PBXNativeTargetData target = nativeTargets[targetGuid]; target.phases.AddGUID(embedFrameworksSection.guid); } /// /// Configures file for building for the given native target on specific build section. /// The function is equivalent to [[AddFileToBuild()]] except that specific build section is specified. /// A projects containing multiple native targets, a single file or folder reference can be /// configured to be built in all, some or none of the targets. The file or folder reference is /// added to appropriate build section depending on the file extension. /// /// The GUID of the target as returned by [[TargetGuidByName()]]. /// The GUID of the section to add the file to. /// The file guid returned by [[AddFile]] or [[AddFolderReference]]. public void AddFileToBuildSection(string targetGuid, string sectionGuid, string fileGuid) { PBXBuildFileData buildFile = PBXBuildFileData.CreateFromFile(fileGuid, false, null); BuildFilesAdd(targetGuid, buildFile); BuildSectionAny(sectionGuid).files.AddGUID(buildFile.guid); } /// /// Returns compile flags set for the specific file. /// Null is returned if the file has no configured compile flags or the file is not configured for /// building on the given target. /// /// The compile flags for the specified file. /// The GUID of the target as returned by [[TargetGuidByName()]]. /// The GUID of the file. public List GetCompileFlagsForFile(string targetGuid, string fileGuid) { var buildFile = BuildFilesGetForSourceFile(targetGuid, fileGuid); if (buildFile == null) return null; if (buildFile.compileFlags == null) return new List(); return new List(buildFile.compileFlags.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries)); } /// /// Sets the compilation flags for the given file in the given target. /// /// The GUID of the target as returned by [[TargetGuidByName()]]. /// The GUID of the file. /// The list of compile flags or null if the flags should be unset. public void SetCompileFlagsForFile(string targetGuid, string fileGuid, List compileFlags) { var buildFile = BuildFilesGetForSourceFile(targetGuid, fileGuid); if (buildFile == null) return; if (compileFlags == null) buildFile.compileFlags = null; else buildFile.compileFlags = string.Join(" ", compileFlags.ToArray()); } /// /// Adds an asset tag for the given file. /// The asset tags identify resources that will be downloaded via On Demand Resources functionality. /// A request for specific tag will initiate download of all files, configured for that tag. /// /// The GUID of the target as returned by [[TargetGuidByName()]]. /// The GUID of the file. /// The name of the asset tag. public void AddAssetTagForFile(string targetGuid, string fileGuid, string tag) { var buildFile = BuildFilesGetForSourceFile(targetGuid, fileGuid); if (buildFile == null) return; if (!buildFile.assetTags.Contains(tag)) buildFile.assetTags.Add(tag); if (!project.project.knownAssetTags.Contains(tag)) project.project.knownAssetTags.Add(tag); } /// /// Removes an asset tag for the given file. /// The function does nothing if the file is not configured for building in the given target or if /// the asset tag is not present in the list of asset tags configured for file. If the file was the /// last file referring to the given tag across the Xcode project, then the tag is removed from the /// list of known tags. /// /// The GUID of the target as returned by [[TargetGuidByName()]]. /// The GUID of the file. /// The name of the asset tag. public void RemoveAssetTagForFile(string targetGuid, string fileGuid, string tag) { var buildFile = BuildFilesGetForSourceFile(targetGuid, fileGuid); if (buildFile == null) return; buildFile.assetTags.Remove(tag); // remove from known tags if this was the last one foreach (var buildFile2 in BuildFilesGetAll()) { if (buildFile2.assetTags.Contains(tag)) return; } project.project.knownAssetTags.Remove(tag); } /// /// Adds the asset tag to the list of tags to download during initial installation. /// The function does nothing if there are no files that use the given asset tag across the project. /// /// The GUID of the target as returned by [[TargetGuidByName()]]. /// The name of the asset tag. public void AddAssetTagToDefaultInstall(string targetGuid, string tag) { if (!project.project.knownAssetTags.Contains(tag)) return; AddBuildProperty(targetGuid, "ON_DEMAND_RESOURCES_INITIAL_INSTALL_TAGS", tag); } /// /// Removes the asset tag from the list of tags to download during initial installation. /// The function does nothing if the tag is not already configured for downloading during /// initial installation. /// /// The GUID of the target as returned by [[TargetGuidByName()]]. /// The name of the asset tag. public void RemoveAssetTagFromDefaultInstall(string targetGuid, string tag) { UpdateBuildProperty(targetGuid, "ON_DEMAND_RESOURCES_INITIAL_INSTALL_TAGS", null, new[] { tag }); } /// /// Removes an asset tag. /// Removes the given asset tag from the list of configured asset tags for all files on all targets, /// the list of asset tags configured for initial installation and the list of known asset tags in /// the Xcode project. /// /// The name of the asset tag. public void RemoveAssetTag(string tag) { foreach (var buildFile in BuildFilesGetAll()) buildFile.assetTags.Remove(tag); foreach (var targetGuid in nativeTargets.GetGuids()) RemoveAssetTagFromDefaultInstall(targetGuid, tag); project.project.knownAssetTags.Remove(tag); } /// /// Checks if the project contains a file with the given physical path. /// The search is performed across all absolute source trees. /// /// Returns true if the project contains the file, false otherwise. /// The physical path of the file. public bool ContainsFileByRealPath(string path) { return FindFileGuidByRealPath(path) != null; } /// /// Checks if the project contains a file with the given physical path. /// /// Returns true if the project contains the file, false otherwise. /// The physical path of the file. /// The source tree path is relative to. The [[PBXSourceTree.Group]] tree is not supported. public bool ContainsFileByRealPath(string path, PBXSourceTree sourceTree) { if (sourceTree == PBXSourceTree.Group) throw new Exception("sourceTree must not be PBXSourceTree.Group"); return FindFileGuidByRealPath(path, sourceTree) != null; } /// /// Checks if the project contains a file with the given project path. /// /// Returns true if the project contains the file, false otherwise. /// The project path of the file. public bool ContainsFileByProjectPath(string path) { return FindFileGuidByProjectPath(path) != null; } /// /// Checks whether the given system framework is a dependency of a target. /// The function assumes system frameworks are located in System/Library/Frameworks folder in the SDK source tree. /// /// Returns true if the given framework is a dependency of the given target, /// false otherwise. /// The GUID of the target as returned by [[TargetGuidByName()]]. /// The name of the framework. The extension of the filename must be ".framework". public bool ContainsFramework(string targetGuid, string framework) { var fileGuid = FindFileGuidByRealPath("System/Library/Frameworks/" + framework, PBXSourceTree.Sdk); if (fileGuid == null) return false; var buildFile = BuildFilesGetForSourceFile(targetGuid, fileGuid); return (buildFile != null); } /// /// Adds a system framework dependency for the specified target. /// The function assumes system frameworks are located in System/Library/Frameworks folder in the SDK source tree. /// The framework is added to Frameworks logical folder in the project. /// /// The GUID of the target as returned by [[TargetGuidByName()]]. /// The name of the framework. The extension of the filename must be ".framework". /// true if the framework is optional (i.e. weakly linked) required, /// false if the framework is required. public void AddFrameworkToProject(string targetGuid, string framework, bool weak) { string fileGuid = AddFile("System/Library/Frameworks/" + framework, "Frameworks/" + framework, PBXSourceTree.Sdk); AddBuildFileImpl(targetGuid, fileGuid, weak, null); } /// /// Removes a system framework dependency for the specified target. /// The function assumes system frameworks are located in System/Library/Frameworks folder in the SDK source tree. /// /// The GUID of the target as returned by [[TargetGuidByName()]]. /// The name of the framework. The extension of the filename must be ".framework". public void RemoveFrameworkFromProject(string targetGuid, string framework) { var fileGuid = FindFileGuidByRealPath("System/Library/Frameworks/" + framework, PBXSourceTree.Sdk); if (fileGuid == null) return; BuildFilesRemove(targetGuid, fileGuid); } // Allow user to add a Capability public bool AddCapability(string targetGuid, PBXCapabilityType capability, string entitlementsFilePath = null, bool addOptionalFramework = false) { // If the capability requires entitlements then you have to provide the name of it or we don't add the capability. if (capability.requiresEntitlements && entitlementsFilePath == "") { throw new Exception("Couldn't add the Xcode Capability " + capability.id + " to the PBXProject file because this capability requires an entitlement file."); } var p = project.project; // If an entitlement with a different name was added for another capability // we don't add this capacity. if (p.entitlementsFile != null && entitlementsFilePath != null && p.entitlementsFile != entitlementsFilePath) { if (p.capabilities.Count > 0) throw new Exception("Attention, it seems that you have multiple entitlements file. Only one will be added the Project : " + p.entitlementsFile); return false; } // Add the capability only if it doesn't already exist. if (p.capabilities.Contains(new PBXCapabilityType.TargetCapabilityPair(targetGuid, capability))) { throw new Exception("This capability has already been added. Method ignored"); } p.capabilities.Add(new PBXCapabilityType.TargetCapabilityPair(targetGuid, capability)); // Add the required framework. if (capability.framework != "" && !capability.optionalFramework || (capability.framework != "" && capability.optionalFramework && addOptionalFramework)) { AddFrameworkToProject(targetGuid, capability.framework, false); } // Finally add the entitlement code signing if it wasn't added before. if (entitlementsFilePath != null && p.entitlementsFile == null) { p.entitlementsFile = entitlementsFilePath; AddFileImpl(entitlementsFilePath, entitlementsFilePath, PBXSourceTree.Source, false); SetBuildProperty(targetGuid, "CODE_SIGN_ENTITLEMENTS", PBXPath.FixSlashes(entitlementsFilePath)); } return true; } // The Xcode project needs a team set to be able to complete code signing or to add some capabilities. public void SetTeamId(string targetGuid, string teamId) { SetBuildProperty(targetGuid, "DEVELOPMENT_TEAM", teamId); project.project.teamIDs.Add(targetGuid, teamId); } /// /// Finds a file with the given physical path in the project, if any. /// /// The GUID of the file if the search succeeded, null otherwise. /// The physical path of the file. /// The source tree path is relative to. The [[PBXSourceTree.Group]] tree is not supported. public string FindFileGuidByRealPath(string path, PBXSourceTree sourceTree) { if (sourceTree == PBXSourceTree.Group) throw new Exception("sourceTree must not be PBXSourceTree.Group"); path = PBXPath.FixSlashes(path); var fileRef = FileRefsGetByRealPath(path, sourceTree); if (fileRef != null) return fileRef.guid; return null; } /// /// Finds a file with the given physical path in the project, if any. /// The search is performed across all absolute source trees. /// /// The GUID of the file if the search succeeded, null otherwise. /// The physical path of the file. public string FindFileGuidByRealPath(string path) { path = PBXPath.FixSlashes(path); foreach (var tree in FileTypeUtils.AllAbsoluteSourceTrees()) { string res = FindFileGuidByRealPath(path, tree); if (res != null) return res; } return null; } /// /// Finds a file with the given project path in the project, if any. /// /// The GUID of the file if the search succeeded, null otherwise. /// The project path of the file. public string FindFileGuidByProjectPath(string path) { path = PBXPath.FixSlashes(path); var fileRef = FileRefsGetByProjectPath(path); if (fileRef != null) return fileRef.guid; return null; } /// /// Removes given file from the list of files to build for the given target. /// /// The GUID of the target as returned by [[TargetGuidByName()]]. /// The GUID of the file or folder reference. public void RemoveFileFromBuild(string targetGuid, string fileGuid) { var buildFile = BuildFilesGetForSourceFile(targetGuid, fileGuid); if (buildFile == null) return; BuildFilesRemove(targetGuid, fileGuid); string buildGuid = buildFile.guid; if (buildGuid != null) { foreach (var section in sources.GetEntries()) section.Value.files.RemoveGUID(buildGuid); foreach (var section in resources.GetEntries()) section.Value.files.RemoveGUID(buildGuid); foreach (var section in copyFiles.GetEntries()) section.Value.files.RemoveGUID(buildGuid); foreach (var section in frameworks.GetEntries()) section.Value.files.RemoveGUID(buildGuid); } } /// /// Removes the given file from project. /// The file is removed from the list of files to build for each native target and also removed /// from the list of known files. /// /// The GUID of the file or folder reference. public void RemoveFile(string fileGuid) { if (fileGuid == null) return; // remove from parent PBXGroupData parent = GroupsGetByChild(fileGuid); if (parent != null) parent.children.RemoveGUID(fileGuid); RemoveGroupIfEmpty(parent); // remove actual file foreach (var target in nativeTargets.GetEntries()) RemoveFileFromBuild(target.Value.guid, fileGuid); FileRefsRemove(fileGuid); } void RemoveGroupIfEmpty(PBXGroupData gr) { if (gr.children.Count == 0 && gr != GroupsGetMainGroup()) { // remove from parent PBXGroupData parent = GroupsGetByChild(gr.guid); parent.children.RemoveGUID(gr.guid); RemoveGroupIfEmpty(parent); // remove actual group GroupsRemove(gr.guid); } } private void RemoveGroupChildrenRecursive(PBXGroupData parent) { List children = new List(parent.children); parent.children.Clear(); foreach (string guid in children) { PBXFileReferenceData file = FileRefsGet(guid); if (file != null) { foreach (var target in nativeTargets.GetEntries()) RemoveFileFromBuild(target.Value.guid, guid); FileRefsRemove(guid); continue; } PBXGroupData gr = GroupsGet(guid); if (gr != null) { RemoveGroupChildrenRecursive(gr); GroupsRemove(gr.guid); continue; } } } internal void RemoveFilesByProjectPathRecursive(string projectPath) { projectPath = PBXPath.FixSlashes(projectPath); PBXGroupData gr = GroupsGetByProjectPath(projectPath); if (gr == null) return; RemoveGroupChildrenRecursive(gr); RemoveGroupIfEmpty(gr); } // Returns null on error internal List GetGroupChildrenFiles(string projectPath) { projectPath = PBXPath.FixSlashes(projectPath); PBXGroupData gr = GroupsGetByProjectPath(projectPath); if (gr == null) return null; var res = new List(); foreach (var guid in gr.children) { PBXFileReferenceData fileRef = FileRefsGet(guid); if (fileRef != null) res.Add(fileRef.name); } return res; } // Returns an empty dictionary if no group or files are found internal HashSet GetGroupChildrenFilesRefs(string projectPath) { projectPath = PBXPath.FixSlashes(projectPath); PBXGroupData gr = GroupsGetByProjectPath(projectPath); if (gr == null) return new HashSet(); HashSet res = new HashSet(); foreach (var guid in gr.children) { PBXFileReferenceData fileRef = FileRefsGet(guid); if (fileRef != null) res.Add(fileRef.path); } return res == null ? new HashSet() : res; } internal HashSet GetFileRefsByProjectPaths(IEnumerable paths) { HashSet ret = new HashSet(); foreach (string path in paths) { string fixedPath = PBXPath.FixSlashes(path); var fileRef = FileRefsGetByProjectPath(fixedPath); if (fileRef != null) ret.Add(fileRef.path); } return ret; } private PBXGroupData GetPBXGroupChildByName(PBXGroupData group, string name) { foreach (string guid in group.children) { var gr = GroupsGet(guid); if (gr != null && gr.name == name) return gr; } return null; } /// Creates source group identified by sourceGroup, if needed, and returns it. /// If sourceGroup is empty or null, root group is returned private PBXGroupData CreateSourceGroup(string sourceGroup) { sourceGroup = PBXPath.FixSlashes(sourceGroup); if (sourceGroup == null || sourceGroup == "") return GroupsGetMainGroup(); PBXGroupData gr = GroupsGetByProjectPath(sourceGroup); if (gr != null) return gr; // the group does not exist -- create new gr = GroupsGetMainGroup(); var elements = PBXPath.Split(sourceGroup); string projectPath = null; foreach (string pathEl in elements) { if (projectPath == null) projectPath = pathEl; else projectPath += "/" + pathEl; PBXGroupData child = GetPBXGroupChildByName(gr, pathEl); if (child != null) gr = child; else { PBXGroupData newGroup = PBXGroupData.Create(pathEl, pathEl, PBXSourceTree.Group); gr.children.AddGUID(newGroup.guid); GroupsAdd(projectPath, gr, newGroup); gr = newGroup; } } return gr; } /// /// Adds an external project dependency to the project. /// /// The path to the external Xcode project (the .xcodeproj file). /// The project path to the new project. /// The source tree the path is relative to. The [[PBXSourceTree.Group]] tree is not supported. internal void AddExternalProjectDependency(string path, string projectPath, PBXSourceTree sourceTree) { if (sourceTree == PBXSourceTree.Group) throw new Exception("sourceTree must not be PBXSourceTree.Group"); path = PBXPath.FixSlashes(path); projectPath = PBXPath.FixSlashes(projectPath); // note: we are duplicating products group for the project reference. Otherwise Xcode crashes. PBXGroupData productGroup = PBXGroupData.CreateRelative("Products"); GroupsAddDuplicate(productGroup); // don't use GroupsAdd here PBXFileReferenceData fileRef = PBXFileReferenceData.CreateFromFile(path, Path.GetFileName(projectPath), sourceTree); FileRefsAdd(path, projectPath, null, fileRef); CreateSourceGroup(PBXPath.GetDirectory(projectPath)).children.AddGUID(fileRef.guid); project.project.AddReference(productGroup.guid, fileRef.guid); } /** This function must be called only after the project the library is in has been added as a dependency via AddExternalProjectDependency. projectPath must be the same as the 'path' parameter passed to the AddExternalProjectDependency. remoteFileGuid must be the guid of the referenced file as specified in PBXFileReference section of the external project TODO: what. is remoteInfo entry in PBXContainerItemProxy? Is in referenced project name or referenced library name without extension? */ internal void AddExternalLibraryDependency(string targetGuid, string filename, string remoteFileGuid, string projectPath, string remoteInfo) { PBXNativeTargetData target = nativeTargets[targetGuid]; filename = PBXPath.FixSlashes(filename); projectPath = PBXPath.FixSlashes(projectPath); // find the products group to put the new library in string projectGuid = FindFileGuidByRealPath(projectPath); if (projectGuid == null) throw new Exception("No such project"); string productsGroupGuid = null; foreach (var proj in project.project.projectReferences) { if (proj.projectRef == projectGuid) { productsGroupGuid = proj.group; break; } } if (productsGroupGuid == null) throw new Exception("Malformed project: no project in project references"); PBXGroupData productGroup = GroupsGet(productsGroupGuid); // verify file extension string ext = Path.GetExtension(filename); if (!FileTypeUtils.IsBuildableFile(ext)) throw new Exception("Wrong file extension"); // create ContainerItemProxy object var container = PBXContainerItemProxyData.Create(projectGuid, "2", remoteFileGuid, remoteInfo); containerItems.AddEntry(container); // create a reference and build file for the library string typeName = FileTypeUtils.GetTypeName(ext); var libRef = PBXReferenceProxyData.Create(filename, typeName, container.guid, "BUILT_PRODUCTS_DIR"); references.AddEntry(libRef); PBXBuildFileData libBuildFile = PBXBuildFileData.CreateFromFile(libRef.guid, false, null); BuildFilesAdd(targetGuid, libBuildFile); BuildSectionAny(target, ext, false).files.AddGUID(libBuildFile.guid); // add to products folder productGroup.children.AddGUID(libRef.guid); } /// /// Creates a new native target. /// Target-specific build configurations are automatically created for each known build configuration name. /// Note, that this is a requirement that follows from the structure of Xcode projects, not an implementation /// detail of this function. The function creates a product file reference in the "Products" project folder /// which refers to the target artifact that is built via this target. /// /// The GUID of the new target. /// The name of the new target. /// The file extension of the target artifact (leading dot is not necessary, but accepted). /// The type of the target. For example: /// "com.apple.product-type.app-extension" - App extension, /// "com.apple.product-type.application.watchapp2" - WatchKit 2 application public string AddTarget(string name, string ext, string type) { var buildConfigList = XCConfigurationListData.Create(); buildConfigLists.AddEntry(buildConfigList); // create build file reference string fullName = name + "." + FileTypeUtils.TrimExtension(ext); var productFileRef = AddFile(fullName, "Products/" + fullName, PBXSourceTree.Build); var newTarget = PBXNativeTargetData.Create(name, productFileRef, type, buildConfigList.guid); nativeTargets.AddEntry(newTarget); project.project.targets.Add(newTarget.guid); foreach (var buildConfigName in BuildConfigNames()) AddBuildConfigForTarget(newTarget.guid, buildConfigName); return newTarget.guid; } private IEnumerable GetAllTargetGuids() { var targets = new List(); targets.Add(project.project.guid); targets.AddRange(nativeTargets.GetGuids()); return targets; } /// /// Returns the file reference of the artifact created by building target. /// /// The file reference of the artifact created by building target. /// The GUID of the target as returned by [[TargetGuidByName()]]. public string GetTargetProductFileRef(string targetGuid) { return nativeTargets[targetGuid].productReference; } /// /// Sets up a dependency between two targets. /// /// The GUID of the target that is depending on the dependency. /// The GUID of the dependency target internal void AddTargetDependency(string targetGuid, string targetDependencyGuid) { string dependencyName = nativeTargets[targetDependencyGuid].name; var containerProxy = PBXContainerItemProxyData.Create(project.project.guid, "1", targetDependencyGuid, dependencyName); containerItems.AddEntry(containerProxy); var targetDependency = PBXTargetDependencyData.Create(targetDependencyGuid, containerProxy.guid); targetDependencies.AddEntry(targetDependency); nativeTargets[targetGuid].dependencies.AddGUID(targetDependency.guid); } // Returns the GUID of the new configuration // targetGuid can be either native target or the project target. private string AddBuildConfigForTarget(string targetGuid, string name) { if (BuildConfigByName(targetGuid, name) != null) { throw new Exception(String.Format("A build configuration by name {0} already exists for target {1}", targetGuid, name)); } var buildConfig = XCBuildConfigurationData.Create(name); buildConfigs.AddEntry(buildConfig); buildConfigLists[GetConfigListForTarget(targetGuid)].buildConfigs.AddGUID(buildConfig.guid); return buildConfig.guid; } private void RemoveBuildConfigForTarget(string targetGuid, string name) { var buildConfigGuid = BuildConfigByName(targetGuid, name); if (buildConfigGuid == null) return; buildConfigs.RemoveEntry(buildConfigGuid); buildConfigLists[GetConfigListForTarget(targetGuid)].buildConfigs.RemoveGUID(buildConfigGuid); } /// /// Returns the GUID of build configuration with the given name for the specific target. /// Null is returned if such configuration does not exist. /// /// The GUID of the build configuration or null if it does not exist. /// The GUID of the target as returned by [[TargetGuidByName()]]. /// The name of the build configuration. public string BuildConfigByName(string targetGuid, string name) { foreach (string guid in buildConfigLists[GetConfigListForTarget(targetGuid)].buildConfigs) { var buildConfig = buildConfigs[guid]; if (buildConfig != null && buildConfig.name == name) return buildConfig.guid; } return null; } /// /// Returns the names of the build configurations available in the project. /// The number and names of the build configurations is a project-wide setting. Each target has the /// same number of build configurations and the names of these build configurations is the same. /// In other words, [[BuildConfigByName()]] will succeed for all targets in the project and all /// build configuration names returned by this function. /// /// An array of build config names. public IEnumerable BuildConfigNames() { var names = new List(); // We use the project target to fetch the build configs foreach (var guid in buildConfigLists[project.project.buildConfigList].buildConfigs) names.Add(buildConfigs[guid].name); return names; } /// /// Creates a new set of build configurations for all targets in the project. /// The number and names of the build configurations is a project-wide setting. Each target has the /// same number of build configurations and the names of these build configurations is the same. /// The created configurations are initially empty. Care must be taken to fill them with reasonable /// defaults. /// The function throws an exception if a build configuration with the given name already exists. /// /// The name of the build configuration. public void AddBuildConfig(string name) { foreach (var targetGuid in GetAllTargetGuids()) AddBuildConfigForTarget(targetGuid, name); } /// /// Removes all build configurations with the given name from all targets in the project. /// The number and names of the build configurations is a project-wide setting. Each target has the /// same number of build configurations and the names of these build configurations is the same. /// The function does nothing if the build configuration with the specified name does not exist. /// /// The name of the build configuration. public void RemoveBuildConfig(string name) { foreach (var targetGuid in GetAllTargetGuids()) RemoveBuildConfigForTarget(targetGuid, name); } /// /// Creates a new sources build phase for given target. /// The new phase is placed at the end of the list of build phases configured for the target. /// /// Returns the GUID of the new phase. /// The GUID of the target as returned by [[TargetGuidByName()]]. public string AddSourcesBuildPhase(string targetGuid) { var phase = PBXSourcesBuildPhaseData.Create(); sources.AddEntry(phase); nativeTargets[targetGuid].phases.AddGUID(phase.guid); return phase.guid; } /// /// Creates a new resources build phase for given target. /// The new phase is placed at the end of the list of build phases configured for the target. /// /// Returns the GUID of the new phase. /// The GUID of the target as returned by [[TargetGuidByName()]]. public string AddResourcesBuildPhase(string targetGuid) { var phase = PBXResourcesBuildPhaseData.Create(); resources.AddEntry(phase); nativeTargets[targetGuid].phases.AddGUID(phase.guid); return phase.guid; } /// /// Creates a new frameworks build phase for given target. /// The new phase is placed at the end of the list of build phases configured for the target. /// /// Returns the GUID of the new phase. /// The GUID of the target as returned by [[TargetGuidByName()]]. public string AddFrameworksBuildPhase(string targetGuid) { var phase = PBXFrameworksBuildPhaseData.Create(); frameworks.AddEntry(phase); nativeTargets[targetGuid].phases.AddGUID(phase.guid); return phase.guid; } public void AddFileToBuildPhase(string isa, string fileGuid) { var buildPhase = frameworks.GetObjects().FirstOrDefault(f => f.isa == isa); if (buildPhase == null) throw new Exception("Build Phase not found " + isa); buildPhase.files.AddGUID(fileGuid); } /// /// Creates a new copy files build phase for given target. /// The new phase is placed at the end of the list of build phases configured for the target. /// /// Returns the GUID of the new phase. /// The GUID of the target as returned by [[TargetGuidByName()]]. /// The name of the phase. /// The destination path. /// The "subfolder spec". The following usages are known: /// - "13" for embedding app extension content /// - "16" for embedding watch content public string AddCopyFilesBuildPhase(string targetGuid, string name, string dstPath, string subfolderSpec) { var phase = copyFiles.GetObjects().FirstOrDefault(f => f.name == name); if (phase != null) return phase.guid; phase = PBXCopyFilesBuildPhaseData.Create(name, dstPath, subfolderSpec); copyFiles.AddEntry(phase); nativeTargets[targetGuid].phases.AddGUID(phase.guid); return phase.guid; } internal string GetConfigListForTarget(string targetGuid) { if (targetGuid == project.project.guid) return project.project.buildConfigList; else return nativeTargets[targetGuid].buildConfigList; } // Sets the baseConfigurationReference key for a XCBuildConfiguration. // If the argument is null, the base configuration is removed. internal void SetBaseReferenceForConfig(string configGuid, string baseReference) { buildConfigs[configGuid].baseConfigurationReference = baseReference; } /// /// Adds a value to build property list in all build configurations for the specified target. /// Duplicate build properties are ignored. Values for names "LIBRARY_SEARCH_PATHS" and /// "FRAMEWORK_SEARCH_PATHS" are quoted if they contain spaces. /// /// The GUID of the target as returned by [[TargetGuidByName()]]. /// The name of the build property. /// The value of the build property. public void AddBuildProperty(string targetGuid, string name, string value) { foreach (string guid in buildConfigLists[GetConfigListForTarget(targetGuid)].buildConfigs) AddBuildPropertyForConfig(guid, name, value); } /// /// Adds a value to build property list in all build configurations for the specified targets. /// Duplicate build properties are ignored. Values for names "LIBRARY_SEARCH_PATHS" and /// "FRAMEWORK_SEARCH_PATHS" are quoted if they contain spaces. /// /// The GUIDs of the target as returned by [[TargetGuidByName()]]. /// The name of the build property. /// The value of the build property. public void AddBuildProperty(IEnumerable targetGuids, string name, string value) { foreach (string t in targetGuids) AddBuildProperty(t, name, value); } /// /// Adds a value to build property list of the given build configuration /// Duplicate build properties are ignored. Values for names "LIBRARY_SEARCH_PATHS" and /// "FRAMEWORK_SEARCH_PATHS" are quoted if they contain spaces. /// /// The GUID of the build configuration as returned by [[BuildConfigByName()]]. /// The name of the build property. /// The value of the build property. public void AddBuildPropertyForConfig(string configGuid, string name, string value) { buildConfigs[configGuid].AddProperty(name, value); } /// /// Adds a value to build property list of the given build configurations /// Duplicate build properties are ignored. Values for names "LIBRARY_SEARCH_PATHS" and /// "FRAMEWORK_SEARCH_PATHS" are quoted if they contain spaces. /// /// The GUIDs of the build configurations as returned by [[BuildConfigByName()]]. /// The name of the build property. /// The value of the build property. public void AddBuildPropertyForConfig(IEnumerable configGuids, string name, string value) { foreach (string guid in configGuids) AddBuildPropertyForConfig(guid, name, value); } /// /// Adds a value to build property list in all build configurations for the specified target. /// Duplicate build properties are ignored. Values for names "LIBRARY_SEARCH_PATHS" and /// "FRAMEWORK_SEARCH_PATHS" are quoted if they contain spaces. /// /// The GUID of the target as returned by [[TargetGuidByName()]]. /// The name of the build property. /// The value of the build property. public void SetBuildProperty(string targetGuid, string name, string value) { foreach (string guid in buildConfigLists[GetConfigListForTarget(targetGuid)].buildConfigs) SetBuildPropertyForConfig(guid, name, value); } /// /// Adds a value to build property list in all build configurations for the specified targets. /// Duplicate build properties are ignored. Values for names "LIBRARY_SEARCH_PATHS" and /// "FRAMEWORK_SEARCH_PATHS" are quoted if they contain spaces. /// /// The GUIDs of the target as returned by [[TargetGuidByName()]]. /// The name of the build property. /// The value of the build property. public void SetBuildProperty(IEnumerable targetGuids, string name, string value) { foreach (string t in targetGuids) SetBuildProperty(t, name, value); } public void SetBuildPropertyForConfig(string configGuid, string name, string value) { buildConfigs[configGuid].SetProperty(name, value); } public void SetBuildPropertyForConfig(IEnumerable configGuids, string name, string value) { foreach (string guid in configGuids) SetBuildPropertyForConfig(guid, name, value); } internal void RemoveBuildProperty(string targetGuid, string name) { foreach (string guid in buildConfigLists[GetConfigListForTarget(targetGuid)].buildConfigs) RemoveBuildPropertyForConfig(guid, name); } internal void RemoveBuildProperty(IEnumerable targetGuids, string name) { foreach (string t in targetGuids) RemoveBuildProperty(t, name); } internal void RemoveBuildPropertyForConfig(string configGuid, string name) { buildConfigs[configGuid].RemoveProperty(name); } internal void RemoveBuildPropertyForConfig(IEnumerable configGuids, string name) { foreach (string guid in configGuids) RemoveBuildPropertyForConfig(guid, name); } internal void RemoveBuildPropertyValueList(string targetGuid, string name, IEnumerable valueList) { foreach (string guid in buildConfigLists[GetConfigListForTarget(targetGuid)].buildConfigs) RemoveBuildPropertyValueListForConfig(guid, name, valueList); } internal void RemoveBuildPropertyValueList(IEnumerable targetGuids, string name, IEnumerable valueList) { foreach (string t in targetGuids) RemoveBuildPropertyValueList(t, name, valueList); } internal void RemoveBuildPropertyValueListForConfig(string configGuid, string name, IEnumerable valueList) { buildConfigs[configGuid].RemovePropertyValueList(name, valueList); } internal void RemoveBuildPropertyValueListForConfig(IEnumerable configGuids, string name, IEnumerable valueList) { foreach (string guid in configGuids) RemoveBuildPropertyValueListForConfig(guid, name, valueList); } /// Interprets the value of the given property as a set of space-delimited strings, then /// removes strings equal to items to removeValues and adds strings in addValues. public void UpdateBuildProperty(string targetGuid, string name, IEnumerable addValues, IEnumerable removeValues) { foreach (string guid in buildConfigLists[GetConfigListForTarget(targetGuid)].buildConfigs) UpdateBuildPropertyForConfig(guid, name, addValues, removeValues); } public void UpdateBuildProperty(IEnumerable targetGuids, string name, IEnumerable addValues, IEnumerable removeValues) { foreach (string t in targetGuids) UpdateBuildProperty(t, name, addValues, removeValues); } public void UpdateBuildPropertyForConfig(string configGuid, string name, IEnumerable addValues, IEnumerable removeValues) { var config = buildConfigs[configGuid]; if (config != null) { if (removeValues != null) foreach (var v in removeValues) config.RemovePropertyValue(name, v); if (addValues != null) foreach (var v in addValues) config.AddProperty(name, v); } } public void UpdateBuildPropertyForConfig(IEnumerable configGuids, string name, IEnumerable addValues, IEnumerable removeValues) { foreach (string guid in configGuids) UpdateBuildProperty(guid, name, addValues, removeValues); } internal string ShellScriptByName(string targetGuid, string name) { foreach (var phase in nativeTargets[targetGuid].phases) { var script = shellScripts[phase]; if (script != null && script.name == name) return script.guid; } return null; } internal void AppendShellScriptBuildPhase(string targetGuid, string name, string shellPath, string shellScript) { PBXShellScriptBuildPhaseData shellScriptPhase = PBXShellScriptBuildPhaseData.Create(name, shellPath, shellScript); shellScripts.AddEntry(shellScriptPhase); nativeTargets[targetGuid].phases.AddGUID(shellScriptPhase.guid); } internal void AppendShellScriptBuildPhase(IEnumerable targetGuids, string name, string shellPath, string shellScript) { PBXShellScriptBuildPhaseData shellScriptPhase = PBXShellScriptBuildPhaseData.Create(name, shellPath, shellScript); shellScripts.AddEntry(shellScriptPhase); foreach (string guid in targetGuids) { nativeTargets[guid].phases.AddGUID(shellScriptPhase.guid); } } public void ReadFromFile(string path) { ReadFromString(File.ReadAllText(path)); } public void ReadFromString(string src) { TextReader sr = new StringReader(src); ReadFromStream(sr); } public void ReadFromStream(TextReader sr) { m_Data.ReadFromStream(sr); } public void WriteToFile(string path) { File.WriteAllText(path, WriteToString()); } public void WriteToStream(TextWriter sw) { sw.Write(WriteToString()); } public string WriteToString() { return m_Data.WriteToString(); } internal PBXProjectObjectData GetProjectInternal() { return project.project; } /* * Allows the setting of target attributes in the project section such as Provisioning Style and Team ID for each target * * The Target Attributes are structured like so: * attributes = { * TargetAttributes = { * 1D6058900D05DD3D006BFB54 = { * DevelopmentTeam = Z6SFPV59E3; * ProvisioningStyle = Manual; * }; * 5623C57217FDCB0800090B9E = { * DevelopmentTeam = Z6SFPV59E3; * ProvisioningStyle = Manual; * TestTargetID = 1D6058900D05DD3D006BFB54; * }; * }; * }; */ internal void SetTargetAttributes(string key, string value) { PBXElementDict properties = project.project.GetPropertiesRaw(); PBXElementDict attributes; PBXElementDict targetAttributes; if (properties.Contains("attributes")) { attributes = properties["attributes"] as PBXElementDict; } else { attributes = properties.CreateDict("attributes"); } if (attributes.Contains("TargetAttributes")) { targetAttributes = attributes["TargetAttributes"] as PBXElementDict; } else { targetAttributes = attributes.CreateDict("TargetAttributes"); } foreach (KeyValuePair target in nativeTargets.GetEntries()) { PBXElementDict targetAttributesRaw; if (targetAttributes.Contains(target.Key)) { targetAttributesRaw = targetAttributes[target.Key].AsDict(); } else { targetAttributesRaw = targetAttributes.CreateDict(target.Key); } targetAttributesRaw.SetString(key, value); } project.project.UpdateVars(); } public void KnownRegionAdd(string knownRegion) { project.project.knownRegions.Add(knownRegion); project.project.UpdateProps(); } public void KnownRegionRemove(string knownRegion) { project.project.knownRegions.Remove(knownRegion); project.project.UpdateProps(); } public void KnownRegionClear() { project.project.knownRegions.Clear(); project.project.UpdateProps(); } } } // namespace UnityModule.iOS.Xcode