using System; using System.Collections.Generic; using System.ComponentModel; using System.IO; #if UNITY_XCODE_API_BUILD using UnityEditor.iOS.Xcode.PBX; #else using UnityEditor.iOS.Xcode.Custom.PBX; #endif #if UNITY_XCODE_API_BUILD namespace UnityEditor.iOS.Xcode #else namespace UnityEditor.iOS.Xcode.Custom #endif { using PBXBuildFileSection = KnownSectionBase; using PBXFileReferenceSection = KnownSectionBase; using PBXGroupSection = KnownSectionBase; using PBXContainerItemProxySection = KnownSectionBase; using PBXReferenceProxySection = KnownSectionBase; using PBXSourcesBuildPhaseSection = KnownSectionBase; using PBXFrameworksBuildPhaseSection= KnownSectionBase; using PBXResourcesBuildPhaseSection = KnownSectionBase; using PBXCopyFilesBuildPhaseSection = KnownSectionBase; using PBXShellScriptBuildPhaseSection = KnownSectionBase; using PBXVariantGroupSection = KnownSectionBase; using PBXNativeTargetSection = KnownSectionBase; using PBXTargetDependencySection = KnownSectionBase; using XCBuildConfigurationSection = KnownSectionBase; using XCConfigurationListSection = KnownSectionBase; using UnknownSection = KnownSectionBase; // 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 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 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) { 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; } /// /// 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); } 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); } /// /// 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 WarningException("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 WarningException("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 internal 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; } /// /// 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); } /// /// Returns the GUID of sources build phase for the given target. /// /// Returns the GUID of the existing phase or null if it does not exist. /// The GUID of the target as returned by [[TargetGuidByName()]]. public string GetSourcesBuildPhaseByTarget(string targetGuid) { var target = nativeTargets[targetGuid]; foreach (var phaseGuid in target.phases) { var phaseAny = BuildSectionAny(phaseGuid); if (phaseAny is PBXSourcesBuildPhaseData) return phaseGuid; } return null; } /// /// Creates a new sources build phase for given target. /// If the target already has sources build phase configured for it, the function returns the /// existing phase. 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 phaseGuid = GetSourcesBuildPhaseByTarget(targetGuid); if (phaseGuid != null) return phaseGuid; var phase = PBXSourcesBuildPhaseData.Create(); sources.AddEntry(phase); nativeTargets[targetGuid].phases.AddGUID(phase.guid); return phase.guid; } /// /// Returns the GUID of resources build phase for the given target. /// /// Returns the GUID of the existing phase or null if it does not exist. /// The GUID of the target as returned by [[TargetGuidByName()]]. public string GetResourcesBuildPhaseByTarget(string targetGuid) { var target = nativeTargets[targetGuid]; foreach (var phaseGuid in target.phases) { var phaseAny = BuildSectionAny(phaseGuid); if (phaseAny is PBXResourcesBuildPhaseData) return phaseGuid; } return null; } /// /// Creates a new resources build phase for given target. /// If the target already has resources build phase configured for it, the function returns the /// existing phase. 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 phaseGuid = GetResourcesBuildPhaseByTarget(targetGuid); if (phaseGuid != null) return phaseGuid; var phase = PBXResourcesBuildPhaseData.Create(); resources.AddEntry(phase); nativeTargets[targetGuid].phases.AddGUID(phase.guid); return phase.guid; } /// /// Returns the GUID of frameworks build phase for the given target. /// /// Returns the GUID of the existing phase or null if it does not exist. /// The GUID of the target as returned by [[TargetGuidByName()]]. public string GetFrameworksBuildPhaseByTarget(string targetGuid) { var target = nativeTargets[targetGuid]; foreach (var phaseGuid in target.phases) { var phaseAny = BuildSectionAny(phaseGuid); if (phaseAny is PBXFrameworksBuildPhaseData) return phaseGuid; } return null; } /// /// Creates a new frameworks build phase for given target. /// If the target already has frameworks build phase configured for it, the function returns the /// existing phase. 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 phaseGuid = GetFrameworksBuildPhaseByTarget(targetGuid); if (phaseGuid != null) return phaseGuid; var phase = PBXFrameworksBuildPhaseData.Create(); frameworks.AddEntry(phase); nativeTargets[targetGuid].phases.AddGUID(phase.guid); return phase.guid; } /// /// Returns the GUID of matching copy files build phase for the given target. /// The parameters of existing copy files build phase are matched to the arguments of this /// function and the GUID of the phase is returned only if a matching build phase is found. /// /// Returns the GUID of the matching phase or null if it does not exist. /// 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: /// "10" for embedding frameworks; /// "13" for embedding app extension content; /// "16" for embedding watch content public string GetCopyFilesBuildPhaseByTarget(string targetGuid, string name, string dstPath, string subfolderSpec) { var target = nativeTargets[targetGuid]; foreach (var phaseGuid in target.phases) { var phaseAny = BuildSectionAny(phaseGuid); if (phaseAny is PBXCopyFilesBuildPhaseData) { var copyPhase = (PBXCopyFilesBuildPhaseData) phaseAny; if (copyPhase.name == name && copyPhase.dstPath == dstPath && copyPhase.dstSubfolderSpec == subfolderSpec) { return phaseGuid; } } } return null; } /// /// Creates a new copy files build phase for given target. /// If the target already has copy files build phase with the same name, dstPath and subfolderSpec /// configured for it, the function returns the existing phase. /// 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: /// "10" for embedding frameworks; /// "13" for embedding app extension content; /// "16" for embedding watch content public string AddCopyFilesBuildPhase(string targetGuid, string name, string dstPath, string subfolderSpec) { var phaseGuid = GetCopyFilesBuildPhaseByTarget(targetGuid, name, dstPath, subfolderSpec); if (phaseGuid != null) return phaseGuid; var 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; } internal PBXBuildFileData FindFrameworkByFileGuid(PBXCopyFilesBuildPhaseData phase, string fileGuid) { foreach (string buildFileDataGuid in phase.files) { var buildFile = BuildFilesGet(buildFileDataGuid); if (buildFile.fileRef == fileGuid) return buildFile; } return null; } /// /// 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(); } } } // namespace UnityEditor.iOS.Xcode